我已经尝试在 C# 中创建自己的 Floyd-Steinberg 抖动实现有一段时间了,但它似乎无法正常工作。以下是它对 Windows XP 背景执行的操作的示例
原图:
我的输出:
所需输出:
正如你所看到的,有这些奇怪的波浪图案。它与我输入的大多数图像执行非常相似的操作,当我在调色板中添加更多颜色时,它的行为也类似。我完全不知道为什么会发生这种情况,特别是因为我之前在 Java 中做过这件事,而且效果很好。这是代码:
public static void ditheredImage(Bitmap source)
{
double ri = 7 / 16d;
double rd = 1 / 16d;
double d = 5 / 16d;
double ld = 3 / 16d;
double[,] errRow = new double[source.Width, 3];
double[] right = new double[3];
Bitmap result = new Bitmap(source.Width, source.Height);
List<Color> palette = new List<Color>();
palette.Add(Color.FromArgb(0, 0, 0));
palette.Add(Color.FromArgb(255, 255, 255));
for (int j = 0; j < source.Height; j++)
{
double[,] newErrRow = new double[source.Width, 3];
bool forward = j % 2 == 0;
for (int i = (forward ? 0 : source.Width - 1); forward ? i < source.Width : i >= 0; i += (forward ? 1 : -1))
{
int x = (int)((double)(i) / source.Width * source.Width);
int y = (int)((double)(j) / source.Height * source.Height);
Color c = source.GetPixel(x, y);
double dR = c.R / 255d + right[0] + errRow[i, 0];
double dG = c.G / 255d + right[1] + errRow[i, 1];
double dB = c.B / 255d + right[2] + errRow[i, 2];
Color curCol = Color.FromArgb((int)clamp(dR * 255, 0, 255), (int)clamp(dG * 255, 0, 255), (int)clamp(dB * 255, 0, 255));
Color closestPalCol = closestPaletteColor(palette, curCol);
result.SetPixel(i, j, closestPalCol);
double qR = closestPalCol.R / 255d;
double qG = closestPalCol.G / 255d;
double qB = closestPalCol.B / 255d;
double rDif = dR - qR;
double gDif = dG - qG;
double bDif = dB - qB;
if (i < source.Width - 1)
{
right[0] = rDif * ri;
right[1] = gDif * ri;
right[2] = bDif * ri;
newErrRow[i + 1, 0] += rDif * rd;
newErrRow[i + 1, 1] += gDif * rd;
newErrRow[i + 1, 2] += bDif * rd;
}
if (i > 0)
{
newErrRow[i - 1, 0] += rDif * ld;
newErrRow[i - 1, 1] += gDif * ld;
newErrRow[i - 1, 2] += bDif * ld;
}
newErrRow[i, 0] += rDif * d;
newErrRow[i, 1] += gDif * d;
newErrRow[i, 2] += bDif * d;
}
errRow = newErrRow;
}
result.Save(@"PATH_TO_SAVED_IMAGE", ImageFormat.Png);
}
public static Color closestPaletteColor(List<Color> palette, Color c)
{
Color result = palette[0];
int dif = 765;
foreach(Color pc in palette)
{
int cdif = Math.Abs(pc.R - c.R) + Math.Abs(pc.G - c.G) + Math.Abs(pc.B - c.B);
if (cdif < dif)
{
dif = cdif;
result = pc;
}
}
return result;
}
public static double clamp(double value, double min, double max)
{
return (value < min) ? min : (value > max) ? max : value;
}
我想通了。我在错误的地方进行了夹紧。这是更正后的代码:
public static void ditheredImage(Bitmap source)
{
double ri = 7 / 16d;
double rd = 1 / 16d;
double d = 5 / 16d;
double ld = 3 / 16d;
double[,] errRow = new double[source.Width, 3];
double[] right = new double[3];
Bitmap result = new Bitmap(source.Width, source.Height);
List<Color> palette = new List<Color>();
palette.Add(Color.FromArgb(0, 0, 0));
palette.Add(Color.FromArgb(255, 255, 255));
for (int j = 0; j < source.Height; j++)
{
double[,] newErrRow = new double[source.Width, 3];
bool forward = j % 2 == 0;
for (int i = (forward ? 0 : source.Width - 1); forward ? i < source.Width : i >= 0; i += (forward ? 1 : -1))
{
int x = (int)((double)(i) / source.Width * source.Width);
int y = (int)((double)(j) / source.Height * source.Height);
Color c = source.GetPixel(x, y);
double dR = clamp(c.R / 255d + right[0] + errRow[i, 0], 0, 1); //CLAMPING SHIFTED HERE
double dG = clamp(c.G / 255d + right[1] + errRow[i, 1], 0, 1); //AND HERE
double dB = clamp(c.B / 255d + right[2] + errRow[i, 2], 0, 1); //AND HERE
Color curCol = Color.FromArgb((int)(dR * 255), (int)(dG * 255), (int)(dB * 255)); // THIS IS WHERE THE MISTAKE WAS
Color closestPalCol = closestPaletteColor(palette, curCol);
result.SetPixel(i, j, closestPalCol);
double qR = closestPalCol.R / 255d;
double qG = closestPalCol.G / 255d;
double qB = closestPalCol.B / 255d;
double rDif = dR - qR;
double gDif = dG - qG;
double bDif = dB - qB;
if (i < source.Width - 1)
{
right[0] = rDif * ri;
right[1] = gDif * ri;
right[2] = bDif * ri;
newErrRow[i + 1, 0] += rDif * rd;
newErrRow[i + 1, 1] += gDif * rd;
newErrRow[i + 1, 2] += bDif * rd;
}
if (i > 0)
{
newErrRow[i - 1, 0] += rDif * ld;
newErrRow[i - 1, 1] += gDif * ld;
newErrRow[i - 1, 2] += bDif * ld;
}
newErrRow[i, 0] += rDif * d;
newErrRow[i, 1] += gDif * d;
newErrRow[i, 2] += bDif * d;
}
errRow = newErrRow;
}
result.Save(@"PATHTOSAVEDIMAGE", ImageFormat.Png);
}
public static Color closestPaletteColor(List<Color> palette, Color c)
{
Color result = palette[0];
int dif = 765;
foreach(Color pc in palette)
{
int cdif = Math.Abs(pc.R - c.R) + Math.Abs(pc.G - c.G) + Math.Abs(pc.B - c.B);
if (cdif < dif)
{
dif = cdif;
result = pc;
}
}
return result;
}
public static double clamp(double value, double min, double max)
{
return (value < min) ? min : (value > max) ? max : value;
}
现在输出:
我仍然应该对点的重复模式做一些事情,显然有一种引入噪音的方法可以防止这种情况发生,但至少现在它没有做任何奇怪的事情。