为什么位图比较不等于自身?

时间:2012-08-30 20:47:48

标签: c# gdi+ memcmp

这里有点令人费解。以下代码是一个小测试应用程序的一部分,用于验证代码更改未引入回归。为了加快速度,我们使用了memcmp appears to be the fastest way of comparing two images of equal size(毫不奇怪)。

但是,我们有一些测试图像显示出一个相当令人惊讶的问题:位图数据上的memcmp告诉我们它们不相等,但是,逐像素比较没有发现任何差异一点都不我的印象是,在LockBits上使用Bitmap时,您会得到图像的实际原始字节。对于24 bpp位图,有点难以想象像素相同但底层像素 data 不是的条件。

一些令人惊讶的事情:

  1. 差异始终单个字节,在一个图像中为00,在另一个图像中为FF
  2. 如果将PixelFormat LockBits更改为Format32bppRgbFormat32bppArgb,则比较成功。
  3. 如果将第一个BitmapData调用返回的LockBits作为第四个参数传递给第二个参数,则比较成功。
  4. 如上所述,逐像素比较也成功。
  5. 我有点难过,因为坦白说我无法想象为什么会发生这种情况。

    (简化)代码如下。只需使用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);
    }
    

2 个答案:

答案 0 :(得分:7)

看看这个,它图示了一个LockBits缓冲区 - 它显示了Strides的行和Padide可以出现在Stride的末尾(如果需要的话)。

步幅可能与32位(即单词)边界对齐(为了提高效率)......并且步幅末端的额外未使用空间是使下一个Stride对齐。

这就是在比较期间给你的随机行为......填充区域中的虚假数据。

当你使用自然字对齐的Format32bppRgb和Format32bppArgb时,我猜你最后没有任何额外的未使用位,这就是它工作的原因。

答案 1 :(得分:4)

只是一个有根据的猜测:

24位(3字节)在32/64位硬件上有点尴尬。

使用这种格式时,必须将刷新的缓冲区刷新为4个字节的倍数,将1个或多个字节保留为“不关心”。它们可以包含随机数据,软件也没有义务将它们归零。这将使memcmp失败。