誤差拡散法を使ってディザリング
その中でも一番単純なもの
さらにかんたんにするためにグレースケールの画像を使って試した
アプリダウンロード先
0から128未満なら黒
128以上255以下なら白
左上
ピクセルから右へ見ていって、
閾値で白か黒に変換して、もとの値と変換後の差を右の
ピクセルに足していく
右端まで行ったら差をリセット(0に)して一段下がって左端からを繰り返す
100,90,120,250,60,20,150の場合
0,255,0,255,255,0,0に変換される
処理の流れ
100は128未満なので0に変換、差は100、これを右隣の90に足す
足して190は128以上なので255に変換、差は-45、これを右隣に足す
足して75は128未満なので0に変換、差は75、これを右隣に足す
これを右端に到達するまで繰り返す
右端に達したら差をリセット(0に)してから一段下がって同じことの繰り返し
- 右端に達して一段下がるときに差をリセットすること
- 差が色の値の範囲0から255から溢れた場合にも溢れた分を切り捨てないで持ち越すこと
上の例だと差が溢れているのは4番目の250のところで
3番目で差が70、250+75=325、325は255から70溢れている
最初はこの溢れた70を切り捨てて、次の
ピクセル60はそのまま60で判定していた
渡されたグレースケールのBitmapsourceを右隣への誤差拡散法でディザリング
private BitmapSource Gosakakusan(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);
long p = 0;
int gosa = 0, v;
for (int y = 0; y < h; ++y)
{
gosa = 0;
for (int x = 0; x < w; ++x)
{
p = y * stride + x;
if (pixels[p] < 128)
{
gosa += pixels[p];
pixels[p] = 0;
}
else
{
gosa += (pixels[p] - 255);
pixels[p] = 255;
}
if (p + 1 < pixels.Length && x < w - 1)
{
v = pixels[p + 1] + gosa;
if (v < 0)
{
gosa = pixels[p + 1] + gosa;
v = 0;
}
else if (v > 255)
{
gosa = pixels[p + 1] + gosa - 255;
v = 255;
}
else
{
gosa = 0;
}
pixels[p + 1] = (byte)v;
}
}
}
wb.WritePixels(new Int32Rect(0, 0, w, h), pixels, stride, 0);
return wb;
}
WriteableBitmapのCopyPixelで作った色の配列は方がbyte型で0以下や256以上の値は入らないので、誤差の記録用にint型の別の変数(gosa)
答え合わせは
勘違いしていた失敗例1
左右ループってのは右端から左端へ行く時に誤差をリセットしないで
そのまま持っていくのを表している
切り捨ては0から255の範囲外に溢れた数値を切り捨てってこと
なのでこの結果は二重に間違った処理の結果
なんだけどこれはこれで、ありなんじゃないかなあって思うw
勘違いしていた失敗例2
これもありじゃないかなあ
勘違いしていた失敗例3
リセットありだけど、溢れたのは切り捨て
普通の画像で
128から255のグラデーションの場合
右上のはリセット無しで切り捨てもなしだから
一行前の誤差がどんどん溜まっていって斜めになる
でもある意味誤差を最後の
ピクセルまで全く捨てていないから
全体としては一番正しいのかもw
2色
トマトの花と空
下段のリセットなしの左右ループ組は画像の何もない左下に
縦の線が出てしまっている
右隣だけへの誤差拡散法は左右ループありのほうがいい感じだなあ
薄い背景の中央に濃い■
より引用
誤差拡散って意味では天気図の等圧線みたいなのが出ていない右上がいいかなあ
参照したところ
この記事より古い関連記事
次の記事
デザイン画面
コード
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.IO;
namespace _20180221_誤差拡散
{
<summary>
</summary>
public partial class MainWindow : Window
{
BitmapSource OriginBitmap;
string ImageFileFullPath;
public MainWindow()
{
InitializeComponent();
this.Title = "右隣だけに誤差拡散、グレースケール";
this.AllowDrop = true;
this.Drop += MainWindow_Drop;
Button0.Click += Button0_Click;
Button2.Click += Button2_Click;
Button3.Click += Button3_Click;
Button4.Click += Button4_Click;
Button5.Click += Button5_Click;
}
private void Gosakakusan右隣だけ_左右ループ_切り捨て()
{
var wb = new WriteableBitmap(OriginBitmap);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
var pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
long p = 0;
int v;
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
p = y * stride + x;
if (pixels[p] < 128)
{
v = pixels[p];
pixels[p] = 0;
}
else
{
v = pixels[p] - 255;
pixels[p] = 255;
}
if (p + 1 < pixels.Length)
{
v = pixels[p + 1] + v;
if (v < 0) { v = 0; }
else if (v > 255) { v = 255; }
pixels[p + 1] = (byte)v;
}
}
}
wb.WritePixels(new Int32Rect(0, 0, w, h), pixels, stride, 0);
MyImage.Source = wb;
}
private void Gosakakusan右隣だけ_左右ループ_切り捨てなし()
{
var wb = new WriteableBitmap(OriginBitmap);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
var pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
long p = 0;
int over = 0, v;
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
p = y * stride + x;
if (pixels[p] < 128)
{
over += pixels[p];
pixels[p] = 0;
}
else
{
over += (pixels[p] - 255);
pixels[p] = 255;
}
if (p + 1 < pixels.Length)
{
v = pixels[p + 1] + over;
if (v < 0)
{
over = pixels[p + 1] + over;
v = 0;
}
else if (v > 255)
{
over = pixels[p + 1] + over - 255;
v = 255;
}
else
{
over = 0;
}
pixels[p + 1] = (byte)v;
}
}
}
wb.WritePixels(new Int32Rect(0, 0, w, h), pixels, stride, 0);
MyImage.Source = wb;
}
private void Gosakakusan右隣だけ_左右ループなし_切り捨て()
{
var wb = new WriteableBitmap(OriginBitmap);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
var pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
long p = 0;
int v;
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
p = y * stride + x;
if (pixels[p] < 128)
{
v = pixels[p];
pixels[p] = 0;
}
else
{
v = pixels[p] - 255;
pixels[p] = 255;
}
if (x < w - 1 && p + 1 < pixels.Length)
{
v = pixels[p + 1] + v;
if (v < 0) { v = 0; }
else if (v > 255) { v = 255; }
pixels[p + 1] = (byte)v;
}
}
}
wb.WritePixels(new Int32Rect(0, 0, w, h), pixels, stride, 0);
MyImage.Source = wb;
}
private void Gosakakusan右隣だけ_左右ループなし_切り捨てなし()
{
var wb = new WriteableBitmap(OriginBitmap);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
var pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
long p = 0;
int gosa = 0, v;
for (int y = 0; y < h; ++y)
{
gosa = 0;
for (int x = 0; x < w; ++x)
{
p = y * stride + x;
if (pixels[p] < 128)
{
gosa += pixels[p];
pixels[p] = 0;
}
else
{
gosa += (pixels[p] - 255);
pixels[p] = 255;
}
if (p + 1 < pixels.Length && x < w - 1)
{
v = pixels[p + 1] + gosa;
if (v < 0)
{
gosa = pixels[p + 1] + gosa;
v = 0;
}
else if (v > 255)
{
gosa = pixels[p + 1] + gosa - 255;
v = 255;
}
else
{
gosa = 0;
}
pixels[p + 1] = (byte)v;
}
}
}
wb.WritePixels(new Int32Rect(0, 0, w, h), pixels, stride, 0);
MyImage.Source = wb;
}
#region イベント
private void Button5_Click(object sender, RoutedEventArgs e)
{
if (OriginBitmap == null) { return; }
Gosakakusan右隣だけ_左右ループなし_切り捨てなし();
}
private void Button4_Click(object sender, RoutedEventArgs e)
{
if (OriginBitmap == null) { return; }
Gosakakusan右隣だけ_左右ループなし_切り捨て();
}
private void Button3_Click(object sender, RoutedEventArgs e)
{
if (OriginBitmap == null) { return; }
Gosakakusan右隣だけ_左右ループ_切り捨てなし();
}
private void Button2_Click(object sender, RoutedEventArgs e)
{
if (OriginBitmap == null) { return; }
Gosakakusan右隣だけ_左右ループ_切り捨て();
}
private void Button0_Click(object sender, RoutedEventArgs e)
{
MyImage.Source = OriginBitmap;
}
private void MainWindow_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop) == false) { return; }
string[] filePath = (string[])e.Data.GetData(DataFormats.FileDrop);
OriginBitmap = GetBitmapSourceWithChangePixelFormat2(filePath[0], PixelFormats.Gray8, 96, 96);
if (OriginBitmap == null)
{
MessageBox.Show("not Image");
}
else
{
MyImage.Source = OriginBitmap;
ImageFileFullPath = System.IO.Path.GetFullPath(filePath[0]);
}
}
#endregion
private BitmapSource GetBitmapSourceWithChangePixelFormat2(
string filePath, PixelFormat pixelFormat, double dpiX = 0, double dpiY = 0)
{
BitmapSource source = null;
try
{
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
var bf = BitmapFrame.Create(fs);
var convertedBitmap = new FormatConvertedBitmap(bf, pixelFormat, null, 0);
int w = convertedBitmap.PixelWidth;
int h = convertedBitmap.PixelHeight;
int stride = (w * pixelFormat.BitsPerPixel + 7) / 8;
byte[] pixels = new byte[h * stride];
convertedBitmap.CopyPixels(pixels, stride, 0);
if (dpiX == 0) { dpiX = bf.DpiX; }
if (dpiY == 0) { dpiY = bf.DpiY; }
source = BitmapSource.Create(
w, h, dpiX, dpiY,
convertedBitmap.Format,
convertedBitmap.Palette, pixels, stride);
};
}
catch (Exception)
{
}
return source;
}
}
}
20時49分追記ここから
private BitmapSource GosakakusanKai(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);
int[] iPixels = new int[pixels.Length];
for (int i = 0; i < iPixels.Length; ++i)
{
iPixels[i] = pixels[i];
}
long p = 0;
int gosa = 0;
for (int y = 0; y < h; ++y)
{
gosa = 0;
for (int x = 0; x < w; ++x)
{
p = y * stride + x;
if (iPixels[p] < 128)
{
gosa += iPixels[p];
iPixels[p] = 0;
}
else
{
gosa += (iPixels[p] - 255);
iPixels[p] = 255;
}
if (p + 1 < pixels.Length && x < w - 1)
{
iPixels[p + 1] += gosa;
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;
}
20時49分追記ここまで