所以这里有一些背景。我正在开发这款名为 ShiftOS 的游戏,它发生在一个操作系统中,该操作系统最初是 80 年代磨坊操作系统的基本运行,没有太多功能。
我正在尝试添加一个机制,用户必须从二进制(2 色)颜色深度开始,并且只能在屏幕上显示黑白。然后他们必须将颜色深度从 1 位升级到 2 位,再从 4 位升级到 24 位。这是一个非常简洁的机制,但在实践中似乎非常困难。
当然,这个时候的旧系统至少尝试过让图像看起来不错,但当然它们受到工程师提供的调色板的限制,所以他们不得不抖动图像以排列像素,使其看起来像图像使用了更多颜色,而实际上它只能使用 2。
所以我查找了一些不错的抖动算法并开始学习 Floyd-Steinberg 算法,并很快将其移植到C#
和System.Drawing
.
这是我使用的代码。
var bmp = new Bitmap(source.Width, source.Height);
var sourceBmp = (Bitmap)source;
int error = 0;
for (int y = 0; y < bmp.Height; y++)
{
for (int x = 0; x < bmp.Width; x++)
{
Color c = sourceBmp.GetPixel(x, y);
int gray = ((c.R + c.G + c.B) / 3);
if (gray >= 127)
{
error = gray - 255;
bmp.SetPixel(x, y, Color.White);
}
else
{
error = gray;
bmp.SetPixel(x, y, Color.Black);
}
/*
* Pixel error diffusion map: Floyd-Steinberg. Thanks to Wikipedia.
*
* pixel[x + 1][y ] := pixel[x + 1][y ] + quant_error * 7 / 16
* pixel[x - 1][y + 1] := pixel[x - 1][y + 1] + quant_error * 3 / 16
* pixel[x ][y + 1] := pixel[x ][y + 1] + quant_error * 5 / 16
* pixel[x + 1][y + 1] := pixel[x + 1][y + 1] + quant_error * 1 / 16
*/
if(x - 1 >= 0 && y + 1 != bmp.Height)
{
var bottomRightColor = sourceBmp.GetPixel(x - 1, y + 1);
int bottomRightGray = ((bottomRightColor.R + bottomRightColor.G + bottomRightColor.B) / 3) + ((error * 3) / 16);
if (bottomRightGray < 0)
bottomRightGray = 0;
if (bottomRightGray > 255)
bottomRightGray = 255;
sourceBmp.SetPixel(x - 1, y + 1, Color.FromArgb(bottomRightGray, bottomRightGray, bottomRightGray));
}
if (x + 1 != sourceBmp.Width)
{
var rightColor = sourceBmp.GetPixel(x + 1, y);
int rightGray = ((rightColor.R + rightColor.G + rightColor.B) / 3) + ((error * 7) / 16);
if (rightGray < 0)
rightGray = 0;
if (rightGray > 255)
rightGray = 255;
sourceBmp.SetPixel(x + 1, y, Color.FromArgb(rightGray, rightGray, rightGray));
}
if (x + 1 != sourceBmp.Width && y + 1 != sourceBmp.Height)
{
var bottomRightColor = sourceBmp.GetPixel(x + 1, y + 1);
int bottomRightGray = ((bottomRightColor.R + bottomRightColor.G + bottomRightColor.B) / 3) + ((error) / 16);
if (bottomRightGray < 0)
bottomRightGray = 0;
if (bottomRightGray > 255)
bottomRightGray = 255;
sourceBmp.SetPixel(x + 1, y + 1, Color.FromArgb(bottomRightGray, bottomRightGray, bottomRightGray));
}
if (y + 1 != sourceBmp.Height)
{
var bottomColor = sourceBmp.GetPixel(x, y + 1);
int bottomGray = ((bottomColor.R + bottomColor.G + bottomColor.B) / 3) + ((error * 5) / 16);
if (bottomGray < 0)
bottomGray = 0;
if (bottomGray > 255)
bottomGray = 255;
sourceBmp.SetPixel(x, y + 1, Color.FromArgb(bottomGray, bottomGray, bottomGray));
}
}
}
请注意,这source
是Image
通过参数传递给函数的。
此代码运行良好,但问题是,抖动发生在单独的线程上,以最大程度地减少游戏中的减速/滞后,并且在发生抖动时,会显示操作系统的常规 24 位颜色/图像。如果抖动不需要这么长时间,这会很好。
但是我注意到该代码在此代码中的算法非常慢,并且根据我正在抖动的图像的大小,抖动过程可能需要超过一分钟!
我已经应用了我能想到的所有优化——例如在与游戏线程不同的线程中运行事物,并在线程完成时调用赋予函数的动作,但这只会节省一点时间(如果有的话)。
所以我想知道是否有任何进一步的优化可以使这个操作更快,如果可能的话总共几秒钟。我还想指出,当抖动操作发生时,我有明显的系统滞后——鼠标有时甚至会抖动和跳跃。对于那些必须拥有 60FPS PC 大师赛的家伙来说,这并不酷。