2006 年 2 月 13 日 21 時 26 分

カラーに挑戦


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


[写真] [写真]


今日は 3 ビット 8 色使ってみよう。
8 色あると、色付き減色への道が開ける。

白黒の場合、明度値という 1 次元の値だけ見ればよかった。
基本を 8 ビットで考えた場合、0~255 までの値しかない。
直線定規の目盛りのように考えることができる。

しかし、RGB カラーとなると 3 次元の世界になる。
RGB それぞれに独立した明度があるため、
単純に定規は表現できず、3 次元空間で考える必要がある。

数学でやった、XYZ の 3 次元座標空間を考えればいい。
ぞれを、RGB に置き換えれば色空間の完成だ。
ルービックキューブやさいころなどを持っている場合は、
それを眺めてみればイメージが沸くかもしれない。

8 ビットの場合、各座標は 0~255 の範囲の整数値を取る。
色は、空間内の任意の点として表現できるのだ。

フルカラーの画像の場合、空間内の点が自由に使えるが、
カラーテーブルを使った画像の場合、
空間内でいくつかの点を決めて、
その点のみを使って画像を構成することになる。

今日は 3 ビットなので 8 色しかない。
RGB 色空間は立方体なので頂点が 8 個ある。
丁度それらを使って色を割り当てることにしよう。
RGB それぞれが 0 か 255 の値を取るので、
色要素ごとに独立して 2 値化の計算を行うことができる。


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

# 32 ビット整数値から RGB 配列に分解しておく。
foreach my $line (@$data) {
    foreach my $pixel (@$line) {
        $pixel = [ unpack_rgb($pixel) ];
    }
}

# 高さと幅を取得。
my $height = @$data;
my $width = @{$data->[0]};

# 行ごとの処理。
for (my $yy = 0; $yy < $height; ++$yy) {

    # ピクセルごとの処理。
    for (my $xx = 0; $xx < $width; ++$xx) {

        # 色要素ごとの処理。
        for (my $ee = 0; $ee < 3; ++$ee) {

            # 元の明度値 0~255 に対して、
            my $src = $data->[$yy][$xx][$ee];

            # 128 を閾値として最明値か最暗値か決める。
            my $dest = $src >= 128 ? 255 : 0;

            # 本来の値との不足分を求め、
            my $diff = $src - $dest;

            # 右、左下、下、右下のピクセルに責任転嫁する。

            # Floyd & Steinberg による誤差配列。

            # - - -
            # - x 7
            # 3 5 1

            $data->[$yy][$xx + 1][$ee] += $diff * 7 / 16
                if $xx + 1 < $width;

            $data->[$yy + 1][$xx - 1][$ee] += $diff * 3 / 16
                if $yy + 1 < $height and $xx - 1 >= 0;

            $data->[$yy + 1][$xx][$ee] += $diff * 5 / 16
                if $yy + 1 < $height;

            $data->[$yy + 1][$xx + 1][$ee] += $diff * 1 / 16
                if $yy + 1 < $height and $xx + 1 < $width;

            # 計算結果は、0 か 1 として格納する。
            $data->[$yy][$xx][$ee] = $dest == 0 ? 0 : 1;

        }

    }

}

# 0-1 配列からカラーテーブルインデックスに変換する。
# $red | $blue << 1 | $green << 2 で求められる。
foreach my $line (@$data) {
    foreach my $px (@$line) {
        $px = $px->[0] | $px->[1] << 1 | $px->[2] << 2;
    }
}

# カラーテーブルを定義。
my @colors = ();

foreach my $bb (0, 255) {
    foreach my $gg (0, 255) {
        foreach my $rr (0, 255) {
            push(@colors, pack_rgb($rr, $gg, $bb));
        }
    }
}

# 残り 8 色は未使用。
push(@colors, (0) x 8);

# 4 ビットの BMP を書き出す。
write_bmp($data, 4, \@colors);


誤差拡散がうまく働いていることが分かる。
RGB それぞれは 0 か 255 しか取っていないにも拘らず、
元の色合いが想像できる位の画質となってるのは驚きだ。

誤差拡散による色の散らばりが激しいので、
写真は JPEG の最高品質で出力している。
(でないと劣化してしまうため)
なので、環境によっては表示できないかもしれない。



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