2006 年 2 月 12 日 19 時 9 分

色を増やす


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


どうだろう。白黒の時よりは表現力が向上した。
誤差拡散を使わなかった場合の写真も載せておくが、
誤差拡散によって大幅に品質が向上していることも分かる。



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