このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。
今日は、使える色を増やして、2 ビット 4 色にしてみよう。
4 色では、残念ながらカラーを表現するには力不足なので、
まだ色彩を表現するのは困難であるが、
今までの白黒 2 値よりはましになると考えられる。
グレースケールで考える場合、
明度値という 1 次元の値だけ見ればよい。
8 ビットで考えた場合、0~255 までの目盛りしかない、
直線定規のように考えることができる。
色は、目盛り内の任意の点として表現される。
白黒 2 値化の場合は、2 つしか色を選べなかったので、
0~255 の両端の色だけを使っていたが、
今回は 4 色を選んで使うことができる。
両端(白黒)は必ず入れておかないと、
それらの色が表現できなくなるので、
中間として使える 2 色を等間隔に選ぶことにしよう。
植木算的に考えると、255 / (2 + 1) = 85 となるので、
0, 85, 170, 255 を候補として選ぶこととする。
4 色あるので、閾の考え方もややこしくなる。
各明度値は、上の中で最も近い値に変換することになる。
では、その範囲を考えてみよう。
0 と 85 の間は、42 以下は 0、43 以上は 85 となる。
85 と 170 の間は、127 以下は 85、128 以上は 170 となる。
170 と 255 の間は、212 以下は 170、213 以上は 255 となる。
つまり、閾値は 42, 127, 212(もしくは 43, 128, 213)だ。
元の値が 85 ずつ並んでいるので、閾値も 85 ずつ並ぶ。
これに注目して、一発で計算する方法を考えてみると、
$new = int(($old + 42) / 85) * 85 で算出できる事となる。
もちろん、条件分岐を並べてもいいが、
計算式一発で表現できるほうがプログラムがすっきりする。
では、実装してみよう。今回も品質向上のために、
Floyd & Steinberg 法による誤差拡散を含めてみる。
誤差拡散処理は、白黒以外にも適用することができるのだ。
# BMP 読み込み。
my $data = read_bmp;
# とりあえずグレースケールに。
foreach my $line (@$data) {
foreach my $pixel (@$line) {
my ($red, $green, $blue) = unpack_rgb($pixel);
no integer;
$pixel = $red * 0.299 + $green * 0.587 + $blue * 0.114;
$pixel = int($pixel);
}
}
# 高さと幅を取得。
my $height = @$data;
my $width = @{$data->[0]};
# 行ごとの処理。
for (my $yy = 0; $yy < $height; ++$yy) {
# ピクセルごとの処理。
for (my $xx = 0; $xx < $width; ++$xx) {
# 元の明度値 0~255 を、
my $src = $data->[$yy][$xx];
# 0~3 の 4 レベルに分ける。
my $index = int(($src + 42) / 85);
# 誤差拡散によっては範囲外になるので丸める。
$index = limit($index, 0, 3);
# 8 ビットに戻して出力値を求める。
my $dest = $index * 85;
# 本来の値との不足分を求め、
my $diff = $src - $dest;
# 右、左下、下、右下のピクセルに責任転嫁する。
# Floyd & Steinberg による誤差配列。
# - - -
# - x 7
# 3 5 1
$data->[$yy][$xx + 1] += $diff * 7 / 16
if $xx + 1 < $width;
$data->[$yy + 1][$xx - 1] += $diff * 3 / 16
if $yy + 1 < $height and $xx - 1 >= 0;
$data->[$yy + 1][$xx] += $diff * 5 / 16
if $yy + 1 < $height;
$data->[$yy + 1][$xx + 1] += $diff * 1 / 16
if $yy + 1 < $height and $xx + 1 < $width;
# 計算結果は、カラーテーブルの番号として格納する。
$data->[$yy][$xx] = $index;
}
}
# カラーテーブルを 4 つだけ定義。残りは未使用。
my @colors = (
pack_rgb(0, 0, 0),
pack_rgb(85, 85, 85),
pack_rgb(170, 170, 170),
pack_rgb(255, 255, 255),
(0) x 12,
);
# 4 ビットの BMP を書き出す。
write_bmp($data, 4, \@colors);
どうだろう。白黒の時よりは表現力が向上した。
誤差拡散を使わなかった場合の写真も載せておくが、
誤差拡散によって大幅に品質が向上していることも分かる。