好的,所以...我们有两个主题要在这里解决。首先是优化,然后是你的错误。我将从优化开始。
最快的方法是直接在内存中工作,但老实说,这有点笨拙。我通常使用的次优选择是将原始图像数据字节从图像对象中复制出来。这将使您最终得到四个重要的数据:
- 宽度,您可以从图像中获取。
- 高度,您可以从图像中获得。
- 字节数组,包含图像字节。
- 步幅,为您提供图像上每一行使用的字节数。
(从技术上讲,还有第五种格式,即像素格式,但我们这里只是将其强制为 32bpp,因此我们不必在此过程中考虑到这一点。)
请注意,从技术上讲,步幅不仅仅是每个像素使用的字节数乘以图像宽度。它向上舍入到 4 个字节的下一个倍数。在处理 32 位 ARGB 内容时,这并不是一个真正的问题,因为 32 位 是 4 个字节,但一般来说,最好使用步幅,而不仅仅是乘以宽度,并且编写所有代码,假设可以在每行后面添加字节。如果您曾经使用这种系统处理 24 位 RGB 内容,您会感谢我的。
但是,当检查图像的内容时,您显然应该只检查包含像素数据的确切范围,而不是完整的步幅。
获得这些东西的方法很简单:在图像上使用LockBits,告诉它以每像素 32 位的 ARGB 数据公开图像(如果需要,它实际上会转换它),获取行步长,然后使用Marshal.Copy 将整个图像内容复制到一个字节数组中。
Int32 width = image.Width;
Int32 height = image.Height;
BitmapData sourceData = image.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
Int32 stride = sourceData.Stride;
Byte[] data = new Byte[stride * height];
Marshal.Copy(sourceData.Scan0, data, 0, data.Length);
image.UnlockBits(sourceData);
如前所述,这是强制为 32 位 ARGB 格式的。如果您想使用此系统以图像中的原始格式获取数据,只需将PixelFormat.Format32bppArgb 更改为image.PixelFormat。
现在,您必须意识到,LockBits 是一个相当繁重的操作,它将数据以请求的像素格式复制到新内存中,在那里可以读取或(如果未指定为只读为我在这里做了)编辑。使这比您的方法更优化的原因是,很简单,GetPixel 执行LockBits 操作每次您请求单个像素值时。因此,您将 LockBits 的调用数量从数千个减少到只有一个。
不管怎样,现在,至于你的功能。
在我看来,第一种方法是完全没有必要的;你应该在你得到的任何图像上运行第二个。它的输出为您提供图像的最后一条白线,因此如果该值等于height-1,您就完成了,如果不是,您将立即获得进一步处理所需的值。毕竟,第一个功能与第二个功能完全相同。它检查一条线上的所有像素是否都是白色的。唯一的区别是它只处理最后一行。
所以,进入第二种方法。这就是事情出错的地方。您将白色像素的数量设置为“当前像素索引加一”,而不是增加它以检查所有像素是否匹配,这意味着该方法会遍历所有像素但只真正检查 last 像素上排是白色的。由于您的图像在最后一行的末尾确实有一个白色像素,因此它在一行后中止。
另外,当你发现一个像素不匹配时,你应该立即中止对该行的扫描,就像你的第一个方法一样;在那之后继续走那条线就没有意义了。
所以,让我们修复第二个函数,并重写它以使用“字节数组”、“步幅”、“宽度”和“高度”的集合,而不是图像。我还添加了“白色”颜色作为参数,以使其更易于重复使用,因此将其从 GetLastWhiteLine 更改为 GetLastClearLine。
一个通用的可用性说明:如果您在高度和宽度上进行迭代,请实际调用您的循环变量y 和x;它使您的代码中的内容更加清晰。
我在代码 cmets 中解释了使用的系统。
private static Int32 GetLastClearLine(Byte[] sourceData, Int32 stride, Int32 width, Int32 height, Color checkColor)
{
// Get color as UInt32 in advance.
UInt32 checkColVal = (UInt32)checkColor.ToArgb();
// Use MemoryStream with BinaryReader since it can read UInt32 from a byte array directly.
using (MemoryStream ms = new MemoryStream(sourceData))
using (BinaryReader sr = new BinaryReader(ms))
{
for (Int32 y = height - 1; y >= 0; --y)
{
// Set position in the memory stream to the start of the current row.
ms.Position = stride * y;
Int32 matchingPixels = 0;
// Read UInt32 pixels for the whole row length.
for (Int32 x = 0; x < width; ++x)
{
// Read a UInt32 for one whole 32bpp ARGB pixel.
UInt32 colorVal = sr.ReadUInt32();
// Compare with check value.
if (colorVal == checkColVal)
matchingPixels++;
else
break;
}
// Test if full line matched the given color.
if (matchingPixels == width)
return y;
}
}
return -1;
}
不过,这可以简化;循环变量x 已经包含了你需要的值,所以如果你只是在循环之前之前声明它,你可以在循环之后检查循环停止时它有什么值,没有必要增加第二个变量。而且,老实说,可以直接比较从流中读取的值,而无需使用 colorVal 变量。制作 y-loop 的内容:
{
ms.Position = stride * y;
Int32 x;
for (x = 0; x < width; ++x)
if (sr.ReadUInt32() != checkColVal)
break;
if (x == width)
return y;
}
对于您的示例图像,这让我得到了 178 的值,当我签入 Gimp 时这是正确的。