【问题标题】:Why does a bitmap compare not equal to itself?为什么位图比较不等于自身?
【发布时间】:2012-08-30 20:47:48
【问题描述】:

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

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

一些令人惊讶的事情:

  1. 区别总是单个字节,在一个图像中是 00,在另一个图像中是 FF
  2. 如果将LockBitsPixelFormat 更改为Format32bppRgbFormat32bppArgb,则比较成功。
  3. 如果将第一个LockBits 调用返回的BitmapData 作为第四个参数传递给第二个调用,则比较成功。
  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);
}

【问题讨论】:

  • 有谁知道为什么#3 也有效?

标签: c# gdi+ memcmp


【解决方案1】:

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

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

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

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

【讨论】:

  • 补充答案:您必须分别比较每条扫描线,width x height 个字节。您不能只对整个像素值数组执行memcmp,因为每条扫描线可能有 1、2 或 3 个未使用的字节。
【解决方案2】:

只是一个有根据的猜测:

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

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

【讨论】:

  • 当我打印数据的长度来比较它等于width × height × 3,所以我猜这个猜测不申请。 编辑,没关系。显然,并非总是如此。 133×12 的图像产生 4800 的长度。最后似乎有一点填充。不过,数据本身似乎已经打包好了。
  • @Joey 要确认这一点,请尝试遍历每一行并 memcmp 步幅。如果这没有失败,那么 Henk 是正确的。
  • @Joey 更正评论:您不比较整个 Stride,而是比较 Width * 3。
  • @Joey 将 PixelFormat 设置为 32-bpp 值的原因是因为步幅将是四的偶数倍。
  • 听起来不错;我的第一张测试图片(尺寸完全匹配)是 40 像素宽,很合适。
猜你喜欢
  • 2010-11-11
  • 2021-08-07
  • 2011-09-16
  • 2011-04-07
  • 2015-10-30
  • 1970-01-01
  • 1970-01-01
  • 2011-11-10
相关资源
最近更新 更多