2006年07月13日

画像の拡大縮小その1,ニアレストネイバー法

画像の拡大縮小(リサンプリング Resamplling とも)アルゴリズムはPhotoshopがそうであるように、代表的なものが3つあります。
ニアレストネイバー法、バイリニア法、バイキュービック法です。普通はバイキュービック法で処理してしまえば特に問題ないんですが、バイキュービック法(バイリニア法も)は補間されてしまう為、ドット絵やアイコンなど、拡大時に元のドットをそのまま拡大したいような場合はニアレストネイバー法が適しています。
そもそも拡大時の補間はそこに本来無かったものを想像で作り出すということになるので、写実的というか、事実に忠実な拡大というのはニアレストネイバー法なんじゃないかという気がします。
ニアレストネイバー法は最近傍画素法とも言われるように、単純に縮小先のピクセルを縮小元でどこになるか計算し、それに一番近いピクセルの色を採用します。

前置きが長くなりましたがそのニアレストネイバー法のアルゴリズムをDelphiで実現した関数です。

// =============================================================================
// TBitmapをニアレストネイバー、最近傍画素法(一番近いピクセルから取得)でリサイズします。
// pf8bit, pf24bit, pf32bitのピクセルフォーマットのみ処理できます。
// 処理は速いですが品質はあまりよくありません。
// 拡大時にモザイク上の方がいい場合はこれが方が適しています。
// コピー元ピクセル値の決定時には四捨五入(Round関数)を使っています。
//
// MakeProgressEvent
//   プログレスイベントを発生させる場合、Trueを指定します。
//   イベントはSrcのOnProgressEventを介して発生します。従ってSrc.OnProgressが
//   設定されていない場合、Trueを指定しても何も起こりません。
//
// 成功の場合、新しいビットマップを生成して返します。失敗ならnilを返します。
// =============================================================================
function KIBResizeNearest(Src: TBitmap; const NewWidth, NewHeight: Integer;
  MakeProgressEvent: Boolean = False): TBitmap;
const
  // プログレスイベントで使用するメッセージ
  MSG = 'サイズ変更(ニアレストネイバー法)';
  // プログレスイベントで使用するゼロ矩形
  ZERO_RECT: TRect = (Left: 0; Top: 0; Right: 0; Bottom: 0);
var
  Dst: TBitmap;
  PixelFormat: TPixelFormat;
  SrcWidth, SrcHeight, OnePixelSize: Integer;
  SrcScanLineBuffer, DstScanLineBuffer: array of Pointer;
  x, y: Integer;
  pDst, pSrc: PByte;
  SrcX, SrcY: Integer;
begin
  // 少しでも速くするため、元画像の大きさとピクセルフォーマットを取得
  PixelFormat := Src.PixelFormat;
  SrcWidth := Src.Width;
  SrcHeight := Src.Height;

  // ピクセルフォーマットのチェック
  if not (PixelFormat in [pf8bit, pf24bit, pf32bit]) then
    raise Exception.Create('未対応のピクセルフォーマットです');

  // ピクセルフォーマットから1ピクセルの大きさを取得
  // コンパイラの警告を避ける為にとりあえず1を設定しておく
  OnePixelSize := 1;
  case PixelFormat of
    pf8bit: OnePixelSize := 1;
    pf24bit: OnePixelSize := 3;
    pf32bit: OnePixelSize := 4;
  end;

  // プログレスイベントが発生可能かどうかをチェック
  MakeProgressEvent := MakeProgressEvent and Assigned(Src.OnProgress);

  // 開始イベントを発生
  if MakeProgressEvent then
    Src.OnProgress(Src, psStarting, 0, False, ZERO_RECT, MSG);

  Dst := TBitmap.Create;
  try
    // pf8bitの場合、パレットをコピーする必要がある
    if PixelFormat = pf8bit then Dst.Assign(Src);

    // 新しい画像の大きさとピクセルフォーマットを設定する
    Dst.PixelFormat := PixelFormat;
    Dst.Width := NewWidth;
    Dst.Height := NewHeight;

    // スキャンラインの結果をバッファに(この方が速いらしい)
    SetLength(SrcScanLineBuffer, Src.Height);
    for x := 0 to Src.Height - 1 do SrcScanLineBuffer[x] := Src.ScanLine[x];
    SetLength(DstScanLineBuffer, Dst.Height);
    for x := 0 to Dst.Height - 1 do DstScanLineBuffer[x] := Dst.ScanLine[x];

    // ピクセル毎に処理
    for y := 0 to NewHeight - 1 do
    begin
      // コピー先のスキャンラインをバッファから取得
      pDst := DstScanLineBuffer[y];

      for x := 0 to NewWidth - 1 do
      begin
        // コピー元の座標を取得する
        SrcX := Round(x / NewWidth * SrcWidth);
        SrcY := Round(y / NewHeight * SrcHeight);

        // 座標がオーバーしている場合への対応
        if SrcX < 0 then SrcX := 0;
        if SrcY < 0 then SrcY := 0;
        if SrcX >= SrcWidth then SrcX := SrcWidth - 1;
        if SrcY >= SrcHeight then SrcY := SrcHeight - 1;

        // コピー元を取得
        pSrc := SrcScanLineBuffer[SrcY];
        Inc(pSrc, SrcX * OnePixelSize);

        // コピー
        CopyMemory(pDst, pSrc, OnePixelSize);

        // 次のピクセルへ
        Inc(pDst, OnePixelSize);
      end;

      // 一行終わる毎に処理中イベントを発生
      if MakeProgressEvent then
        Src.OnProgress(
          Src, psRunning, y * 100 div NewHeight, False, ZERO_RECT, MSG);
    end;

    // 完了イベントを発生
    if MakeProgressEvent then
      Src.OnProgress(Src, psEnding, 100, False, ZERO_RECT, MSG);

    // ここまで来れば処理は成功
    Result := Dst;
  except
    Dst.Free;
    raise; // 例外を再生成
  end;
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
※ブログオーナーが承認したコメントのみ表示されます。

この記事へのトラックバック
×

この広告は90日以上新しい記事の投稿がないブログに表示されております。