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

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

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

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

  1. Различия всегда представляют собой единичные байты, которые 00 в одном изображении и FF в другом.
  2. Если кто-то меняет PixelFormat за LockBits в Format32bppRgb или же Format32bppArgbСравнение успешно.
  3. Если пройти BitmapData вернулся первым LockBits вызовите как 4-й аргумент для второго, сравнение успешно.
  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);
}

2 ответа

Решение

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

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

Вот что дает вам случайное поведение во время сравнения... ложные данные в области Паддинга.

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

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

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

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

Другие вопросы по тегам