为什么位图比较不等于自身?
这有点令人费解。 以下代码是一个小测试应用程序的一部分,用于validation代码更改未引入回归。 为了加快速度,我们使用了memcmp
,它似乎是比较两个相同大小图像的最快方法 (不出所料)。
但是,我们有一些测试图像显示出一个相当令人惊讶的问题:位图数据上的memcmp
告诉我们它们不相等,但是,逐像素比较根本没有发现任何差异。 我的印象是,在Bitmap
上使用LockBits
,您会得到图像的实际原始字节。 对于24 bpp位图,有点难以想象像素相同但基础像素数据不相同的情况。
一些令人惊讶的事情:
- 差异始终是单个字节,在一个映像中为
00
在另一个映像中为FF
。 - 如果将
Format32bppRgb
的PixelFormat
更改为Format32bppRgb
或Format32bppArgb
,则比较成功。 - 如果将第一个
LockBits
调用返回的BitmapData
作为第四个参数传递给第二个参数,则比较成功。 - 如上所述,逐像素比较也成功。
我有点难过,因为坦率地说我无法想象为什么会这样。
(简化)代码如下。 只需使用csc /unsafe
编译,并将24bpp的PNG图像作为第一个参数传递。
using System; using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropServices; class Program { public static void Main(string[] args) { Bitmap title = new Bitmap(args[0]); Console.WriteLine(CompareImageResult(title, new Bitmap(title))); } private static string CompareImageResult(Bitmap bmp, Bitmap expected) { string retval = ""; unsafe { var rect = new Rectangle(0, 0, bmp.Width, bmp.Height); var resultData = bmp.LockBits(rect, ImageLockMode.ReadOnly, bmp.PixelFormat); var expectedData = expected.LockBits(rect, ImageLockMode.ReadOnly, expected.PixelFormat); try { if (memcmp(resultData.Scan0, expectedData.Scan0, resultData.Stride * resultData.Height) != 0) retval += "Bitmap data did not match\n"; } finally { bmp.UnlockBits(resultData); expected.UnlockBits(expectedData); } } for (var x = 0; x < bmp.Width; x++) for (var y = 0; y < bmp.Height; y++) if (bmp.GetPixel(x, y) != expected.GetPixel(x, y)) { Console.WriteLine("Pixel diff at {0}, {1}: {2} - {3}", x, y, bmp.GetPixel(x, y), expected.GetPixel(x, y)); retval += "pixel fail"; } return retval != "" ? retval : "success"; } [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)] static extern int memcmp(IntPtr b1, IntPtr b2, long count); }
看看这个,它图示了一个LockBits缓冲区 – 它显示了Strides的行和Padide可以出现在Stride的末尾(如果需要的话)。
步幅可能与32位(即字)边界对齐(为了提高效率)……步幅末端的额外未使用空间是使下一个Stride对齐。
这就是在比较期间给你的随机行为…… Padding区域中的虚假数据。
当你使用自然字对齐的Format32bppRgb和Format32bppArgb时,我猜你最后没有任何额外的未使用位,这就是它工作的原因。
只是一个有根据的猜测:
32位(3字节)在32/64位硬件上有点尴尬。
使用这种格式时,必须将刷新的缓冲区刷新为4个字节的倍数,将1个或多个字节保留为“不关心”。 它们可以包含随机数据,软件也没有义务将它们归零。 这将使memcmp失败。