2006 年 2 月 14 日 19 時 50 分

Web セーフカラー


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


[写真] [写真] [写真]


今日はもっと贅沢に色を使ってみよう。

基本的に減色を必要とするのは、
表示する端末のデータのサイズに制限がある、
または通信速度があまり速くない、
そして、表示できる色数が限られている環境などだ。

インターネット上で配信することを考えた場合、
昔は、通信回線の速度がそれほど速くなかったので、
画像などのバイナリデータは、大きな負担となっていた。

ADSL が普及している今でも、
Web サイトに配置するデータ容量に制限がある場合や、
携帯電話向けの画像、そしてアクセスが激しい画像など、
減色することによって効率が良くなる場合もある。

今日は、Web セーフカラーと呼ばれる 216 色を使い、
8 ビット 256 色の形式に減色処理をしてみよう。

Web セーフカラーは、216 色のカラーテーブルを持つ。
環境によって画像が表示できない現象を避けるため、
RGB それぞれを 6 段階使って汎用的に定義している。

昨日のように座標空間やさいころで考えてみよう。
8 ビットの場合、各座標は 0~255 の範囲の整数値を取る。
RGB それぞれに 6 段階というのは、
空間のそれぞれの面にそって 5 分割することに相当する。

5 x 5 のルービックキューブを持っているなら、
まさに 5 分割そのものなので見てほしい。
(植木算で考えるので、6 分割ではない)

そうした場合、以下の座標位置が求まる。
0, 51, 102, 153, 204, 255

各辺に 6 つの座標値を取るわけなので、
頂点数は、6 x 6 x 6 = 216 となる。
これが 216 色の根拠である。

これら頂点に当たる色を使って定義すれば、
Web セーフカラーを持ったカラーテーブルができあがる。

では処理をしてみよう。
Web セーフカラーの場合、カラーテーブルの色が、
3 次元色空間にきれいに並んでいるため、
昨日同様、色要素ごとに独立して計算を行うことができる。

今日は 6 段階に分けることになるので、
一昨日同様に閾値のことを考えてみると、
元の値が 51 ずつ並んでいるので、閾値も 51 ずつ並ぶので、
$new = int(($old + 25) / 51) * 51 で算出できる事となる。


# 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];

            # 0~5 の 6 レベルに分ける。
            my $index = int(($src + 25) / 51);

            # 誤差拡散によっては範囲外になるので丸める。
            $index = limit($index, 0, 5);

            # 8 ビットに戻して出力値を求める。
            my $dest = $index * 51;

            # 本来の値との不足分を求め、
            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~5 で格納する。
            $data->[$yy][$xx][$ee] = $index;

        }

    }

}

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

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

for (my $bb = 0; $bb <= 255; $bb += 51) {
    for (my $gg = 0; $gg <= 255; $gg += 51) {
        for (my $rr = 0; $rr <= 255; $rr += 51) {
            push(@colors, pack_rgb($rr, $gg, $bb));
        }
    }
}

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

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


さて、どうだろうか。確かにきれいになってはいるが、
昨日のような鮮烈な印象はない。
むしろ、216 色もつかっておきながらこの程度か
という印象を受けてしまうのは俺だけだろうか。

考えてみてみよう。216 色といえども、
RGB それぞれで見てみれば 6 段階しかないのだ。
誤差拡散によってある程度は補われているが、
誤差拡散を外した右の写真を見ると一目瞭然である。

どうやら、色数を増やせばいいというものではなさそうだ。
明日以降、この問題について考えていこう。



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