Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Comparing Images using GDI+

0.00/5 (No votes)
7 Jul 2005 1  
An article on comparing two images by computing and comparing their hash values.

Introduction

.NET provides some great methods for working with images and bitmaps using the managed GDI+ methods. However, I found myself a bit stuck even with GDI+ when I wanted to compare two images to see if they were identical. I was trying to run some automated tests on our charting component, SimpleChart, and I needed to know if the charts being produced were identical to those in the test specification. To do this, I needed to compare each image being generated by SimpleChart in the test with a reference image that was known to be good. If the two were identical then the test had passed.

Comparing Images

First Attempts

The first step in comparing two images to see if they were identical was to check the size of each. If they don't match then we know almost immediately that the images are not identical. Once that quick test was complete, we needed to look at the actual image content to see if it matched up. Initially, I decided to use GetPixel method of the GDI+ Bitmap class to compare each pixel in the first image with the corresponding pixel in the second image. If at any point, the two pixels did not match then we can safely say that the images are different. If, however, we got to the end of the comparison tests without any mismatches then we can conclude that the two images are indeed identical.

public static CompareResult Compare(Bitmap bmp1, Bitmap bmp2)
{
    CompareResult cr = CompareResult.ciCompareOk;

    //Test to see if we have the same size of image

    if (bmp1.Size != bmp2.Size)
    {
        cr = CompareResult.ciSizeMismatch;
    }
    else
    {
        //Sizes are the same so start comparing pixels

        for (int x = 0; x < bmp1.Width 
             && cr == CompareResult.ciCompareOk; x++)
        {
            for (int y = 0; y < bmp1.Height 
                         && cr == CompareResult.ciCompareOk; y++)
            {
                if (bmp1.GetPixel(x, y) != bmp2.GetPixel(x, y))
                    cr = CompareResult.ciPixelMismatch;
            }
        }
    }
    return cr;
}

This method worked fine but with one major drawback, speed, or rather the lack of it. Comparing two 2000 x 1500 pixel images using this method took over 17 seconds! With over 200 images to compare, this meant that my tests would take nearly an hour to complete and I wasn't prepared to wait that long.

Hash in a Flash

What I needed was a faster method to compare the images to allow the tests to complete in a timely manner. Rather than comparing the individual pixels in each image using GetPixel, I decided that it would be quicker if I could some how compare a 'hash' of each image to see if they were identical. As we know, a hash is a unique value of a fixed size representing a large amount of data, in this case our image data. Hashes of two images should match if and only if the corresponding images also match. Small changes to the image result in large unpredictable changes in the hash.

There are many different hashing algorithms provided by .NET in the System.Security.Cryptography namespace such as SHA1 and MD5 but I decided to use the SHA256Managed class. The ComputeHash method of this class takes a byte array of data as an input parameter and produces a 256 bit hash of that data. By computing and then comparing the hash of each image, I would be quickly able to tell if the images were identical or not.

The only problem now remaining was how to convert the image data stored in the GDI+ Bitmap objects to a suitable form for passing to the ComputeHash method, namely a byte array. Initially, I looked at the LockBits method of the Bitmap class which allowed me access to the individual pixel bytes but it would have meant a journey into the land of unmanaged code and that was somewhere I really didn't want to visit. Instead, GDI+ kindly provides an ImageConvertor class to allow us to convert Image (or Bitmap) objects from one data type to another, such as a byte array.

The final step to see if the images are identical is to compare the two hash values (also stored in byte arrays) to see if they match. Here is the final code:

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Security.Cryptography;

namespace Imagio
{
    public class ComparingImages
    {
        public enum CompareResult
        {
            ciCompareOk,
            ciPixelMismatch,
            ciSizeMismatch
        };

        public static CompareResult Compare(Bitmap bmp1, Bitmap bmp2)
        {
            CompareResult cr = CompareResult.ciCompareOk;

            //Test to see if we have the same size of image

            if (bmp1.Size != bmp2.Size)
            {
                cr = CompareResult.ciSizeMismatch;
            }
            else
            {
                //Convert each image to a byte array

                System.Drawing.ImageConverter ic = 
                       new System.Drawing.ImageConverter();
                byte[] btImage1 = new byte[1];
                btImage1 = (byte[])ic.ConvertTo(bmp1, btImage1.GetType());
                byte[] btImage2 = new byte[1];
                btImage2 = (byte[])ic.ConvertTo(bmp2, btImage2.GetType());
                
                //Compute a hash for each image

                SHA256Managed shaM = new SHA256Managed();
                byte[] hash1 = shaM.ComputeHash(btImage1);
                byte[] hash2 = shaM.ComputeHash(btImage2);

                //Compare the hash values

                for (int i = 0; i < hash1.Length && i < hash2.Length 
                                  && cr == CompareResult.ciCompareOk; i++)
                {
                    if (hash1[i] != hash2[i])
                        cr = CompareResult.ciPixelMismatch;
                }
            }
            return cr;
        }
    }
}

Conclusion

Running this new compare method on a 2000 x 1500 pixel bitmap resulted in a comparison time of 0.28 seconds which meant that my automated testing of 200 SimpleChart images now takes only 56 seconds to complete.

Hashes are normally used as a security tool to see if credentials such as passwords match. Using the same hashing approach, we can also quickly compare two images to see if they too are identical.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here