このアーカイブは同期化されません。 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]);
}
}