このアーカイブは同期化されません。 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 段階しかないのだ。
誤差拡散によってある程度は補われているが、
誤差拡散を外した右の写真を見ると一目瞭然である。
どうやら、色数を増やせばいいというものではなさそうだ。
明日以降、この問題について考えていこう。