Click here to Skip to main content
Click here to Skip to main content

Comparing Images using GDI+

, 7 Jul 2005
Rate this:
Please Sign up or sign in to vote.
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

Share

About the Author

markrouse
Web Developer
United Kingdom United Kingdom
Mark Rouse works as a software developer for Imagio Technology in Yorkshire, UK
 
When not working on his favourite charting component, SimpleChart, Mark is providing custom application development for his clients using ASP.NET, C# and SQL Server and in particular Web Services.

Comments and Discussions

 
GeneralObject leak Pinmemberthelawnet3-Nov-08 13:03 
QuestionHow to compare images for similarity Pinmemberlexstyles14-Apr-08 10:48 
AnswerRe: How to compare images for similarity Pinmembermarkrouse20-Apr-08 23:53 
GeneralDifferent file format, same picture PinmemberScareCr0w25-Mar-08 15:18 
Very useful piece of code to me! I've been playing around with it a little, but now ran into the following problem:
 
What if I would like to compare say a .PNG file with a .BMP? PNG brings no information loss (like JPG) which makes it suitable for comparing as well. However, comparing a PNG and BMP bytewise isn't possible since of the different file formats. That doesn't mean though that the actual content can't be the same. For instance I'd like to be able to compare a black square of 1 pixel in both PNG and BMP format and end up with the result telling me they're the same. So far I haven't been able to find a way for this (without using GetPixel()).
 
I tried copying a part of the PNG to a Bitmap object using this suggestion from MSDN , which would suit my program since I'm trying to compare a part of an image with another image. However it kind of suprised me that the Bitmap object still contained a PNG header and file format. So probably, I don't want to do a full byte comparisson, but rather something with the actual colours the pixels have, without using the "slow" GetPixel() method.
 
Any help or suggestions would be really appreciated!
GeneralRe: Different file format, same picture PinmemberTenTrinacty21-Jul-09 9:34 
GeneralRe: Different file format, same picture Pinmemberharveyt1-Dec-12 4:57 
Generalhelp me Pinmemberarunbediya17-Feb-08 19:43 
GeneralI found this article very helpful PinmemberGuyThiebaut15-Nov-07 9:26 
GeneralRe: I found this article very helpful Pinmembermarkrouse22-Nov-07 4:52 
GeneralRe: I found this article very helpful PinmemberGuyThiebaut22-Nov-07 4:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141223.1 | Last Updated 8 Jul 2005
Article Copyright 2005 by markrouse
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid