このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。
さて、昨日のフィルタを実装してみよう。
フィルタは、カーネルを定義するだけで色々できるので、
3x3 の汎用カーネルを適用できるようにしてみた。
use strict;
use warnings;
use bmp_io;
use integer;
# $value を $min 以上 $max 以下の範囲に収めて返す。
sub limit ($$$) {
my ($value, $min, $max) = @_;
if ($value < $min) { $value = $min };
if ($value > $max) { $value = $max };
$value;
}
# 32 ビット値を RGB 値に分解する。
sub unpack_rgb ($) {
unpack('xCCC', pack('N', $_[0]));
}
# RGB 値を 32 ビット値に格納する。
sub pack_rgb ($$$) {
unpack('N', pack('xCCC', $_[0], $_[1], $_[2]));
}
# カーネルの定義。今回は中央が 8 で周りが 1 のフィルタ。
# 1 1 1
# 1 8 1
# 1 1 1
# 配列の添え字を 0, 1, 2 ではなく、-1, 0, 1 で使いたい。
# Perl 5 では、マイナスの添え字は配列の最後から数える。
# なので、$kernel->[2][2] = $kernel->[-1][-1] である。
# そのため、上の行を下に左の行を右に移動して定義すれば、
# -1, 0, 1 の添え字でカーネルにアクセスできる。
my $kernel = [
[ 8, 1, 1 ],
[ 1, 1, 1 ],
[ 1, 1, 1 ],
];
# カーネルの重みを計算しておく。上の例では 16。
my $kernel_weight = 0;
map { $kernel_weight += $_ } @$_ foreach @$kernel;
# BMP 読み込み。
my $old = read_bmp;
# 高さと幅と得る。
my $height = @$old;
my $width = @{$old->[0]};
# 変換後の BMP を格納する行の配列。
my $new = [];
# 行ごとの処理。
for (my $yy = 0; $yy < $height; ++$yy) {
# 新しい行を一時的に記憶するピクセル配列。
my $new_line = [];
# ピクセルごとの処理。
for (my $xx = 0; $xx < $width; ++$xx) {
# RGB 要素ごとに累算する。
my ($red, $green, $blue) = (0, 0, 0);
# カーネルを合わせる。
foreach my $ky (-1 .. 1) {
foreach my $kx (-1 .. 1) {
# 計算対象のピクセル。(画像の端を考慮)
my $oy = limit($yy + $ky, 0, $height - 1);
my $ox = limit($xx + $kx, 0, $width - 1);
# ピクセル値を取り出す。
my ($rr, $gg, $bb) = unpack_rgb($old->[$oy][$ox]);
# 累算する。
$red += $rr * $kernel->[$ky][$kx];
$green += $gg * $kernel->[$ky][$kx];
$blue += $bb * $kernel->[$ky][$kx];
}
}
# 割り算のために一時的に浮動小数計算。
no integer;
# 重みで割って、結果を計算する。
$red = int($red / $kernel_weight + 0.5);
$green = int($green / $kernel_weight + 0.5);
$blue = int($blue / $kernel_weight + 0.5);
# 元に戻す。
use integer;
# 1 ピクセル完了。
push @$new_line, pack_rgb($red, $green, $blue);
}
# 1 行完了。
push @$new, $new_line;
}
# 24 ビットで出力する。
write_bmp($new, 24);