2006 年 3 月 8 日 22 時 17 分

線形補間による回転


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


[写真]


今日は、線形補間を適用してみよう。

基本は、拡大縮小の時と同じだ。
新しいピクセルから元のピクセルを参照する際の
計算式を回転に差し替えればいいのだ。

ただ、回転の場合、丁度四角形には収まらないので、
回転後のピクセルに対応する回転前のピクセルが、
範囲外の座標になることが多い。
そのため、境界位置のチェックが必要になる。

線形補間の場合、4 ピクセルを使って計算するため、
回転前のピクセル位置を計算した際に、
その周辺の 4 ピクセル全てが範囲外の場合のみ、
適当な背景色を使うことにしてみよう。

use POSIX;
no integer;

# 回転角を定義。今回は -5 度。
my $angle = -5 * (atan2(0, -1)) / 180;

# 背景色を定義。今回は赤色。
my $bgcolor = pack_rgb(255, 0, 0);

# BMP 読み込み。
my $src = read_bmp;

# 画像の幅と高さを取得。
my $scx = @{$src->[0]};
my $scy = @$src;

# 画像の中心を求める。
my $sox = $scx / 2;
my $soy = $scy / 2;

# 新しい画像の幅と高さを十分に取っておく。
my $dcx = ceil(sqrt($scx * $scx + $scy * $scy));
my $dcy = $dcx;

# 新しい画像の中心を求める。
my $dox = $dcx / 2;
my $doy = $dcy / 2;

# サインとコサインを求めておく。
my $sin = sin($angle);
my $cos = cos($angle);

# 新しい画像のデータを背景色で初期化する。
my $dest = [ map { [ ($bgcolor) x $dcx ] } 1 .. $dcy ];

for (my $dy = 0; $dy < $dcy; ++$dy) {
    for (my $dx = 0; $dx < $dcx; ++$dx) {

        no integer;

        # 新しい点の位置に対応する元の点の位置を求める。
        my $sx = $sox + $cos * ($dx - $dox + 0.5)
                      + $sin * ($dy - $doy + 0.5) - 0.5;
        my $sy = $soy + $cos * ($dy - $doy + 0.5)
                      - $sin * ($dx - $dox + 0.5) - 0.5;

        # 位置を整数化し、最も近い左上の点を得る。
        # 計算結果が負の場合があるので、
        # int 関数は使えない。そこで POSIX::floor を使う。
        my $px = floor($sx);
        my $py = floor($sy);

        # 計算対象の 4 点が全て範囲外なら転送しない。
        # ($px, $py) が (-1, -1) でも右下の点は範囲内。
        next if $px < -1 or $px >= $scx
             or $py < -1 or $py >= $scy;

        # 色を累算する。
        my @rgb = (0, 0, 0);

        # 周辺の 4 点に対して処理をする。
        foreach my $yy ($py, $py + 1) {
            foreach my $xx ($px, $px + 1) {

                # 縦と横の影響力を求め、積を重みとする。
                my $weight = (1 - abs($yy - $sy))
                           * (1 - abs($xx - $sx));

                # 座標が範囲外であれば、範囲内に収める。
                my ($tx, $ty) = ($xx, $yy);
                $tx = 0 if $tx < 0;
                $tx = $scx - 1 if $tx >= $scx;
                $ty = 0 if $ty < 0;
                $ty = $scy - 1 if $ty >= $scy;

                # RGB に分解し、それぞれ重みを掛けて累算する。
                if ($weight) {
                    my @clr = unpack_rgb($src->[$ty][$tx]);
                    $rgb[$_] += $clr[$_] * $weight foreach 0 .. 2;
                }

            }
        }

        # RGB それぞれ累計値を整数化する。
        $rgb[$_] = floor($rgb[$_] + 0.5) foreach 0 .. 2;

        # 32 ビット整数値に戻しておく。
        $dest->[$dy][$dx] = pack_rgb($rgb[0], $rgb[1], $rgb[2]);
    }
}



Copyright (c) 1994-2007 Project Loafer. All rights reserved.