Почему растровое сравнение не равно самому себе?
Это немного озадачивает здесь. Следующий код является частью небольшого тестового приложения для проверки того, что изменения кода не привели к регрессии. Чтобы сделать это быстро, мы использовали 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);
}
Ответы
Ответ 1
Взгляните на это, что наглядно иллюстрирует буфер LockBits - он показывает строки строк и где Padding может появиться в конце Stride (если это необходимо).
Шаг, вероятно, выровнен с 32-битной (то есть словом) границей (для целей эффективности)... и лишнее неиспользуемое пространство в конце шага должно выровнять следующий Stride.
Итак, что дает вам случайное поведение во время сравнения... ложные данные в области заполнения.
Когда вы используете Format32bppRgb и Format32bppArgb, которые, естественно, выравниваются по слову, поэтому, я думаю, у вас нет лишних неиспользуемых бит в конце, поэтому он работает.
Ответ 2
Просто образованное предположение:
24 бита (3 байта) немного неудобны на аппаратном уровне 32/64 бит.
В этом формате должны быть буферы, которые выгружаются до нескольких байтов, оставляя 1 или более байтов как "не заботясь". Они могут содержать случайные данные, и программное обеспечение не считает необходимым их обнулить. Это приведет к сбою memcmp.