こんにちは。
シュンスケです![]()
前回からまだまだ引き続き減色の話です。
前回は「組織的ディザ法」で減色を行いましたが、次は「誤差分散法」でやってみます。
こちらは前回と比べてイメージはし易いかと思います。以下の様な流れです。
色を近似で算出する(このピクセルにおける色はこの色に決定)
算出された色と、元の色の差分を誤差として算出
誤差を自分の周りのピクセルに対して一定の割合で足し込む
以降各ピクセルは周りからの誤差を反映した状態で近似し、また周りに誤差をばら撒く
つまり、始めの1ピクセル以外は、何らか周りの誤差を反映した状態で近似する色を選ぶという事になります![]()
なお、
における周りというのは、幾つかバリエーションがありますが、
今回は、右1ピクセルと、左下、真下、右下の3ピクセルで合計4ピクセルに誤差を分散させます![]()
(※厳密には右か左か下か上かは、配列への画像データ格納順によります)
というわけで、実装は以下の通り
まずは定数と構造体の定義
// 分散情報
private struct Dispersion
{
public Dispersion(int x, int y, float r)
{
offsetX = x;
offsetY = y;
rate = r;
}
public int offsetX;
public int offsetY;
public float rate;
}
// 分散用パターンテーブル
private static readonly Dispersion[] DISPERSION_TBL = new Dispersion[] {
new Dispersion( 1, 0, 7.0f / 16.0f),
new Dispersion(-1, 1, 3.0f / 16.0f),
new Dispersion( 0, 1, 5.0f / 16.0f),
new Dispersion( 1, 1, 1.0f / 16.0f),
};
今回は他のピクセルに干渉するために、ピクセルごとの処理だけでなく、
それを使用しているメソッドにも変更を加えます。
わかり易さを考えて、誤差を直接足すのではなく、一旦蓄積用の領域に格納するようにしています。
// 1チャンネルの処理
static uint channelFilter(uint org, int x, int y, ref float[,] err)
{
// 蓄積された誤差を加えて近似
var tmp = (err[x, y] + org);
if( tmp < 0 ) {
tmp = 0;
}
if( tmp > 255 ) {
tmp = 255;
}
var c = RGBA4444_COLORS[(int)tmp / 16];
// 誤差を分散して蓄積
float e = tmp - c;
for( int i = 0; i < DISPERSION_TBL.Length; ++i ) {
var p = DISPERSION_TBL[i];
var xIdx = x + p.offsetX;
var yIdx = y + p.offsetY;
var xLen = err.GetLength(0);
var yLen = err.GetLength(1);
if( xIdx < 0 || xIdx >= xLen ||
yIdx < 0 || yIdx >= yLen ) continue;
err[xIdx, yIdx] += (p.rate * e);
}
return c;
}
static void filter(ref byte[] bmp, int w, int h)
{
// 誤差格納用の領域を確保
var errR = new float[w, h];
var errG = new float[w, h];
var errB = new float[w, h];
var errA = new float[w, h];
for( int i = 0; i < w; ++i ) {
for( int j = 0; j < h; ++j ) {
errR[i, j] = 0;
errG[i, j] = 0;
errB[i, j] = 0;
errA[i, j] = 0;
}
}
for( int y = 0; y < h; ++y ) {
for( int x = 0; x < w; ++x ) {
var index = (y * w + x) * PIXEL_BYTES;
uint r = bmp[index + 0];
uint g = bmp[index + 1];
uint b = bmp[index + 2];
uint a = bmp[index + 3];
r = channelFilter(r, x, y, ref errR);
g = channelFilter(g, x, y, ref errG);
b = channelFilter(b, x, y, ref errB);
a = channelFilter(a, x, y, ref errA);
var color = Color.FromArgb((int)a, (int)r, (int)g, (int)b);
bmp[index + 0] = color.R;
bmp[index + 1] = color.G;
bmp[index + 2] = color.B;
bmp[index + 3] = color.A;
}
}
}
その結果作成されたのが、
です。
左が前回、右が今回のものです。
「組織的ディザ法」に比べて、パターンがわかりにくくなっています![]()
アイコンのような記号的な絵より、写真のような画像に向いていそうな気がします。
というわけで、今回はここまでです。
次回はいよいよ最後、良いとこ取りが出来ないか模索します。
では![]()


