昨日の続き
いくつかの誤差拡散法を試してみた
アプリダウンロード先
ここの20180222_.2.zipがそれ
右隣だけに拡散(昨日の)
FloydSteinberg式を小数点以下切り捨て
FloydSteinberg式を普通に
SierraLite
JuJuNi
Atkinson
拡散する値
右隣だけに拡散
FloydSteinberg
右隣へは誤差の7/16を拡散
左下、真下、右下へはそれぞれ
3/16, 5/16, 1/16を拡散
右隣への誤差拡散法と違うのは誤差に小数点が発生するから、誤差を入れる変数にfloat型を使わないと微妙に間違った結果になってしまうこと
最初気づかなくてなんでお手本と違うのかわかんなかった
あと、白黒判定の
閾値も128じゃなくて127.5を使う
8bitグレースケールのBitmapSourceを白黒2値に変換
<summary>
</summary>
<param name="source"></param>
<returns></returns>
private BitmapSource ErrorDiffusionFloydSteinberg(BitmapSource source)
{
var wb = new WriteableBitmap(source);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
var pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
float[] iPixels = new float[pixels.Length];
for (int i = 0; i < iPixels.Length; ++i)
{
iPixels[i] = pixels[i];
}
long p = 0;
float gosa = 0;
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
p = y * stride + x;
if (iPixels[p] < 127.5f)
{
gosa = iPixels[p];
iPixels[p] = 0;
}
else
{
gosa = (iPixels[p] - 255f);
iPixels[p] = 255;
}
if (p + 1 < pixels.Length && x < w - 1)
{
iPixels[p + 1] += (gosa / 16f) * 7f;
}
if (y < h - 1 && x != 0)
{
iPixels[p + stride - 1] += (gosa / 16f) * 3f;
}
if (p + stride < pixels.Length)
{
iPixels[p + stride] += (gosa / 16f) * 5f;
}
if (p + stride + 1 < pixels.Length && x < w - 1)
{
iPixels[p + stride + 1] += (gosa / 16f) * 1f;
}
gosa = 0;
}
}
for (int i = 0; i < pixels.Length; ++i)
{
pixels[i] = (byte)(iPixels[i]);
}
wb.WritePixels(new Int32Rect(0, 0, w, h), pixels, stride, 0);
return wb;
}
その他の誤差拡散法
SierraLite
Jarvis, Judice and Ninke(JaJuNi)
これも代表的な誤差拡散ってことで試したけど
めんどくさいいい
Atkinson
誤差の6/8だけ拡散して残りの2/8は捨て去る大胆な方法
FloydSteinbergとSierraLiteが似ているのは拡散の方法からなんとなくわかるけど
JaJuNiとAtkinsonが似ているなあ、JaJuNiはあんなに処理の手数が多いのに…
それに2/8も誤差を捨て去っているAtkinsonの再現度が高いのが不思議
RGB(254,254,254)
ほぼ真っ白な灰色の場合
Atkinsonで消え去るのはわかるけど
JaJuNiでも消え去ったのは意外
印象では真っ白な画像なんだからきれいに消え去って真っ白になるのは
ある意味正解だよなあ
わずかなグラデーションと
普通のグラデーション
安定のFloydSteinberg
処理の軽さのSierraLite
余計な縞模様が出にくいJaJuNi
処理の順番(方向)での違い
左画像には斜めの縞模様が出ている、上部に特に目立つものと全体にもでている
右画像は上部に規則的な模様が出ているけど、全体では均等にバラけていて良好
普通は
→→→→→→→→→→→→1行下の左端へ
→→→→→→→→→→→→1行下の左端へ
→→→→→→→→→→→→
これの繰り返し
これを
→→→→→→→→→→→↓
↓←←←←←←←←←←←
→→→→→→→→→→→↓
蛇行する
左へ処理をすすめるときは誤差拡散の方向も逆にしたらうまくできた
private BitmapSource ErrorDiffusionFloydSteinbergSerpentine(BitmapSource source)
{
var wb = new WriteableBitmap(source);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
var pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
float[] iPixels = new float[pixels.Length];
for (int i = 0; i < iPixels.Length; ++i)
{
iPixels[i] = pixels[i];
}
long p = 0;
float gosa = 0;
for (int y = 0; y < h; ++y)
{
if (y % 2 == 0)
{
for (int x = 0; x < w; ++x)
{
p = y * stride + x;
if (iPixels[p] < 127.5f)
{
gosa = iPixels[p];
iPixels[p] = 0;
}
else
{
gosa = (iPixels[p] - 255f);
iPixels[p] = 255;
}
if (p + 1 < pixels.Length && x < w - 1)
{
iPixels[p + 1] += (gosa / 16f) * 7f;
}
if (y < h - 1 && x != 0)
{
iPixels[p + stride - 1] += (gosa / 16f) * 3f;
}
if (p + stride < pixels.Length)
{
iPixels[p + stride] += (gosa / 16f) * 5f;
}
if (p + stride + 1 < pixels.Length && x < w - 1)
{
iPixels[p + stride + 1] += (gosa / 16f) * 1f;
}
gosa = 0;
}
}
else
{
for (int x = w - 1; x >= 0; --x)
{
p = y * stride + x;
if (iPixels[p] < 127.5f)
{
gosa = iPixels[p];
iPixels[p] = 0;
}
else
{
gosa = (iPixels[p] - 255f);
iPixels[p] = 255;
}
if (p - 1 < pixels.Length && x != 0)
{
iPixels[p - 1] += (gosa / 16f) * 7f;
}
if (y < h - 1 && x != 0)
{
iPixels[p + stride - 1] += (gosa / 16f) * 1f;
}
if (p + stride < pixels.Length)
{
iPixels[p + stride] += (gosa / 16f) * 5f;
}
if (p + stride + 1 < pixels.Length && x < w - 1)
{
iPixels[p + stride + 1] += (gosa / 16f) * 3f;
}
gosa = 0;
}
}
}
for (int i = 0; i < pixels.Length; ++i)
{
pixels[i] = (byte)(iPixels[i]);
}
wb.WritePixels(new Int32Rect(0, 0, w, h), pixels, stride, 0);
return wb;
}
private BitmapSource ErrorDiffusionSierraLite(BitmapSource source)
{
var wb = new WriteableBitmap(source);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
var pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
float[] iPixels = new float[pixels.Length];
for (int i = 0; i < iPixels.Length; ++i)
{
iPixels[i] = pixels[i];
}
long p = 0;
float gosa = 0;
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
p = y * stride + x;
if (iPixels[p] < 127.5f)
{
gosa = iPixels[p];
iPixels[p] = 0;
}
else
{
gosa = (iPixels[p] - 255f);
iPixels[p] = 255;
}
if (p + 1 < pixels.Length && x < w - 1)
{
iPixels[p + 1] += (gosa / 4f) * 2f;
}
if (y < h - 1 && x != 0)
{
iPixels[p + stride - 1] += (gosa / 4f) * 1f;
}
if (p + stride < pixels.Length)
{
iPixels[p + stride] += (gosa / 4f) * 1f;
}
gosa = 0;
}
}
for (int i = 0; i < pixels.Length; ++i)
{
pixels[i] = (byte)(iPixels[i]);
}
wb.WritePixels(new Int32Rect(0, 0, w, h), pixels, stride, 0);
return wb;
}
private BitmapSource ErrorDiffusionJaJuNi(BitmapSource source)
{
var wb = new WriteableBitmap(source);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
var pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
float[] iPixels = new float[pixels.Length];
for (int i = 0; i < iPixels.Length; ++i)
{
iPixels[i] = pixels[i];
}
long p = 0;
float gosa = 0;
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
p = y * stride + x;
if (iPixels[p] < 127.5f)
{
gosa = iPixels[p];
iPixels[p] = 0;
}
else
{
gosa = (iPixels[p] - 255f);
iPixels[p] = 255;
}
if (p + 1 < pixels.Length && x < w - 1)
{
iPixels[p + 1] += (gosa / 48f) * 7f;
}
if (p + 2 < pixels.Length && x < w - 2)
{
iPixels[p + 2] += (gosa / 48f) * 5f;
}
if (y < h - 1)
{
if (x > 1)
{
iPixels[p + stride - 2] += (gosa / 48f) * 3f;
}
if (x != 0)
{
iPixels[p + stride - 1] += (gosa / 48f) * 5f;
}
iPixels[p + stride] += (gosa / 48f) * 7f;
if (x < w - 1)
{
iPixels[p + stride + 1] += (gosa / 48f) * 5f;
}
if (x < w - 2)
{
iPixels[p + stride + 2] += (gosa / 48f) * 3f;
}
}
if (y < h - 2)
{
long pp = p + (stride * 2);
if (x > 1)
{
iPixels[pp - 2] += (gosa / 48f) * 1f;
}
if (x != 0)
{
iPixels[pp - 1] += (gosa / 48f) * 3f;
}
iPixels[pp] += (gosa / 48f) * 5f;
if (x < w - 1)
{
iPixels[pp + 1] += (gosa / 48f) * 3f;
}
if (x < w - 2)
{
iPixels[pp + 2] += (gosa / 48f) * 1f;
}
}
gosa = 0;
}
}
for (int i = 0; i < pixels.Length; ++i)
{
pixels[i] = (byte)(iPixels[i]);
}
wb.WritePixels(new Int32Rect(0, 0, w, h), pixels, stride, 0);
return wb;
}
private BitmapSource ErrorDiffusionAtkinson(BitmapSource source)
{
var wb = new WriteableBitmap(source);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
var pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
float[] iPixels = new float[pixels.Length];
for (int i = 0; i < iPixels.Length; ++i)
{
iPixels[i] = pixels[i];
}
long p = 0;
float gosa = 0;
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
p = y * stride + x;
if (iPixels[p] < 127.5f)
{
gosa = iPixels[p];
iPixels[p] = 0;
}
else
{
gosa = (iPixels[p] - 255f);
iPixels[p] = 255;
}
if (p + 2 < pixels.Length && x < w - 2)
{
iPixels[p + 2] += (gosa / 8f) * 1f;
}
if (p + 1 < pixels.Length && x < w - 1)
{
iPixels[p + 1] += (gosa / 8f) * 1f;
}
if (y < h - 1 && x != 0)
{
iPixels[p + stride - 1] += (gosa / 8f) * 1f;
}
if (p + stride < pixels.Length)
{
iPixels[p + stride] += (gosa / 8f) * 1f;
}
if (p + stride + 1 < pixels.Length && x < w - 1)
{
iPixels[p + stride + 1] += (gosa / 8f) * 1f;
}
if (y < h - 2)
{
iPixels[p + stride * 2] += (gosa / 8f) * 1f;
}
gosa = 0;
}
}
for (int i = 0; i < pixels.Length; ++i)
{
pixels[i] = (byte)(iPixels[i]);
}
wb.WritePixels(new Int32Rect(0, 0, w, h), pixels, stride, 0);
return wb;
}
参照したところ
関連記事
次
前回
2018/03/02は1週間後