0

I have a an Class that has a Bitmap property. I have implemented an Equals override for the object and I'm having an issue with Bitmap comparison. Here is what I have:

[Serializable]
public class Contact
{
    public string Name { get; set; }
    public Bitmap Photo { get; set; }

    public override bool Equals(object obj)
    {
        if (obj == null) { return false; }
        if (obj.GetType() != typeof(Contact)) { return false; }

        Contact compareContact = obj as Contact;

        if (compareContact.FirstName != this.FirstName) { return false; }

        if (compareContact.Photo != null && this.Photo != null)
        {
            if (!compareContact.Photo.IsEqual(this.Photo)) { return false; }
        }
        else
        {
            if (!(compareContact.Photo == null && this.Photo == null)) { return false; }
        }

        return true;
    }
}

public static class BitmapExtensions
{
    public static bool IsEqual(this Bitmap image1, Bitmap image2)
    {
        if (image1 == null || image2 == null)
        {
            return false;
        }

        byte[] image1Bytes =  image1.ToByteArray();
        byte[] image2Bytes =  image2.ToByteArray();

        bool sequenceEqual = image1Bytes.SequenceEqual(image2Bytes);

        return sequenceEqual;
    }

    public static byte[] ToByteArray(this Bitmap image)
    {
        using (MemoryStream ms = new MemoryStream())
        {
            image.Save(ms, ImageFormat.Bmp);
            return ms.ToArray();
        }
    }
}

And My Test:

[TestClass()]
public class vCardTest
{
    private Contact TestContact()
    {
        Contact contact = new Contact();
        contact.Name = "Joe";
        contact.Photo = new System.Drawing.Bitmap(1, 1);
    }

    [TestMethod]
    public void EqualsTest()
    {
        Contact matchedContact  = Common.Copy<Contact >(TestContact);

        Assert.AreEqual<vCard>(vcf, matchedVCard);
    }

And my Copy Method:

    public static T Copy<T>(T objectToCopy) where T : class
    {
        byte[] serializedObject = objectToCopy.Serialize();
        T copy = serializedObject.Deserialize(typeof(T)) as T;

        return copy;
    }

My Equals function returns false. When I look at the byte arrays for the original bitmap and the copy, I notice two bytes out of 58 within the arrays are off by one. The two byte arrays have the same length.

I'm trying to figure out if the serializing and deserializing the bitmap to make a copy is causing the issue, or something else.

Update

It doesn't appear that the Common.Copy method is causing the issue, this code with byte arrays works successfully:

    [TestMethod()]
    public void ByteArrayCommonCopyTest()
    {
        byte[] byteArray1 = new byte[] { 0, 1, 1, 0, 123, 45, 56, 0 };
        byte[] byteArray2 = Common.Copy<byte[]>(byteArray1);
        bool expected = true;
        bool actual;

        actual = byteArray1.SequenceEqual(byteArray2);

        Assert.AreEqual(expected, actual);
    }

Interestingly, if I update Photo to a new Bitmap with same size, it works:

    [TestMethod]
    public void EqualsTest()
    {
        Contact matchedContact  = Common.Copy<Contact>(TestContact);
        matchedContact.Photo = new System.Drawing.Bitmap(1, 1)

        Assert.AreEqual<vCard>(vcf, matchedVCard);
    }

1 Answers1

0

Here's what you're doing (which I wouldn't do) - you're streaming the image to a bmp format stream which you then compare. The problem here is that in memory and in bmp files, your data gets padded to 4 bytes on a row-by-row basis. Likely the differences are in the padding, which gets ignored in drawing.

Instead, you should write a proper comparator. Two bitmaps are the if:

  • They are the same object

OR

  • The widths, heights, resolutions, and pixel formats match
  • The palettes match
  • The image data is identical

The dimensions are easy to check and should be done first since those tests are cheap.

To compare image data, you get either do a GetPixel() call on each pixel in the image or you can can try to access the image data directly by using LockBits() to get a BitmapData object and extract only the significant bytes in a row for comparison. The latter is doable, but the code needs to be able to correctly answer the question, "Given a bitmap, how many significant bytes are there per row?" for any particular PixelFormat. Even then, you're going to be copying each row into a row buffer then running your byte array comparator on it. Maybe once you get that running, your might be happier with the performance of a P/Invoke to memcmp, or instead of working with arrays do direct pointer arithmetic from the first scan line IntPtr and then do a memcmp:

[DllImport("msvcrt.dll", CallingConvention=CallingConvention.Cdecl)]
static extern int memcmp(IntPtr b1, IntPtr b2, long count);

bool ScanlinesAreTheSame(IntPtr data1, IntPtr data2, int height, long significantBytes, int rowStride)
{
    while (height-- > 0) {
        if (memcmp(data1, data2, significantBytes) != 0) return false;
        data1 = IntPtr.Add(data1, rowStride);
        data2 = IntPtr.Add(data2, rowStride);
    }
    return true;
}
Community
  • 1
  • 1
plinth
  • 48,267
  • 11
  • 78
  • 120
  • Do you know of an example that has an image comparison implemented? I don't want to reinvent the wheel. –  Dec 08 '13 at 19:55
  • Interesting ImageCompare class under the Microsoft.VisualStudio.TestTools.UITesting namespace: http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.testtools.uitesting.imagecomparer.aspx –  Dec 08 '13 at 22:55