Подтвердить что ты не робот

Почему растровое сравнение не равно самому себе?

Это немного озадачивает здесь. Следующий код является частью небольшого тестового приложения для проверки того, что изменения кода не привели к регрессии. Чтобы сделать это быстро, мы использовали memcmp, который представляется самым быстрым способом сравнения двух изображений равного размера (неудивительно).

Однако у нас есть несколько тестовых изображений, которые демонстрируют довольно удивительную проблему: memcmp по данным растрового изображения говорит нам о том, что они не равны, однако сравнение пополам по пикселям не находит никакой разницы, У меня создалось впечатление, что при использовании LockBits на Bitmap вы получаете фактические необработанные байты изображения. Для растрового изображения 24 бит/с немного сложно представить себе условие, когда пиксели одинаковы, но базовые данные пикселя отсутствуют.

Несколько удивительных вещей:

  • Различия - это всегда одиночные байты, которые 00 в одном изображении и FF в другом.
  • Если изменить PixelFormat на LockBits на Format32bppRgb или Format32bppArgb, сравнение будет успешным.
  • Если передать BitmapData, возвращенный первым вызовом LockBits в качестве 4-го аргумента ко второму, сравнение будет успешным.
  • Как отмечалось выше, сравнение пополам также успешно выполняется.

Я немного в тупике, потому что, честно говоря, я не могу представить, почему это происходит.

(Уменьшенный) Код ниже. Просто скомпилируйте с csc /unsafe и передайте изображение PNG 24bpp в качестве первого аргумента.

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);
}
4b9b3361

Ответ 1

Взгляните на это, что наглядно иллюстрирует буфер LockBits - он показывает строки строк и где Padding может появиться в конце Stride (если это необходимо).

Шаг, вероятно, выровнен с 32-битной (то есть словом) границей (для целей эффективности)... и лишнее неиспользуемое пространство в конце шага должно выровнять следующий Stride.

Итак, что дает вам случайное поведение во время сравнения... ложные данные в области заполнения.

Когда вы используете Format32bppRgb и Format32bppArgb, которые, естественно, выравниваются по слову, поэтому, я думаю, у вас нет лишних неиспользуемых бит в конце, поэтому он работает.

Ответ 2

Просто образованное предположение:

24 бита (3 байта) немного неудобны на аппаратном уровне 32/64 бит.

В этом формате должны быть буферы, которые выгружаются до нескольких байтов, оставляя 1 или более байтов как "не заботясь". Они могут содержать случайные данные, и программное обеспечение не считает необходимым их обнулить. Это приведет к сбою memcmp.