6

这在这里有点令人费解。以下代码是一个小测试应用程序的一部分,用于验证代码更改没有引入回归。为了让它更快,我们使用memcmp了这似乎是比较两个相同大小的图像的最快方法(不足为奇)。

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

一些令人惊讶的事情:

  1. 差异始终00是一个图像和另一个图像中的单个字节FF
  2. 如果将 for 更改为PixelFormator ,则比较成功。LockBitsFormat32bppRgbFormat32bppArgb
  3. 如果将BitmapData第一次调用返回的LockBits值作为第四个参数传递给第二个,则比较成功。
  4. 如上所述,逐像素比较也成功。

我在这里有点难过,因为坦率地说,我无法想象为什么会发生这种情况。

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

2 回答 2

7

看一下这个,它以图形方式说明了一个 LockBits 缓冲区 - 它显示了 Strides 的行以及 Padding 可以出现在 Stride 末尾的位置(如果需要)。

一个步幅可能与 32 位(即字)边界对齐(出于效率目的)......并且步幅末尾的额外未使用空间是为了使下一个步幅对齐。

所以这就是在比较过程中给你随机行为的原因......填充区域中的虚假数据。

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

于 2012-08-30T20:58:11.303 回答
4

只是一个有根据的猜测:

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

使用这种格式,肯定会有缓冲区被刷新为 4 个字节的倍数,留下 1 个或更多字节为 'don't care' 。它们可以包含随机数据,并且软件不需要将它们归零。这将使 memcmp 失败。

于 2012-08-30T20:53:10.380 回答