Jul 19, 2012

Fast Bitmap comparison - C#

10 comments
Recently i have done bitmap comparison using Bitmap.GetPixel() method to check pixel by pixel using the following code snippet and it works functionality wise pretty well.


        private bool CompareBitmaps(Image left, Image right)
        {
            if (object.Equals(left, right))
                return true;
            if (left == null || right == null)
                return false;
            if (!left.Size.Equals(right.Size) || !left.PixelFormat.Equals(right.PixelFormat))
                return false;

            Bitmap leftBitmap = left as Bitmap;
            Bitmap rightBitmap = right as Bitmap;
            if (leftBitmap == null || rightBitmap == null)
                return true;

            #region Code taking more time for comparison

            for (int col = 0; col < left.Width; col++)
            {
                for (int row = 0; row < left.Height; row++)
                {
                    if (!leftBitmap.GetPixel(col, row).Equals(rightBitmap.GetPixel(col, row)))
                        return false;
               }
            }

            #endregion
            
            return true;
        }

But, here the problem is we are accessing memory directly using Bitmap.GetPixel() and it is taking lot of time for comparison. Finally performance of comparison is becoming very very slow for larger images. Say for 1.5MB size images it is taking around 2.5 sec which is not acceptable.


After spending lot of time, i am able to reduce this comparison time to almost 40 ms using Bitmap.LockBits() and Bitmap.UnlockBits() using the following code.


        private bool CompareBitmaps(Image left, Image right)
        {
            if (object.Equals(left, right))
                return true;
            if (left == null || right == null)
                return false;
            if (!left.Size.Equals(right.Size) || !left.PixelFormat.Equals(right.PixelFormat))
                return false;

            Bitmap leftBitmap = left as Bitmap;
            Bitmap rightBitmap = right as Bitmap;
            if (leftBitmap == null || rightBitmap == null)
                return true;

            #region Optimized code for performance

            int bytes = left.Width * left.Height * (Image.GetPixelFormatSize(left.PixelFormat) / 8);

            bool result = true;
            byte[] b1bytes = new byte[bytes];
            byte[] b2bytes = new byte[bytes];

            BitmapData bmd1 = leftBitmap.LockBits(new Rectangle(0, 0, leftBitmap.Width - 1, leftBitmap.Height - 1), ImageLockMode.ReadOnly, leftBitmap.PixelFormat);
            BitmapData bmd2 = rightBitmap.LockBits(new Rectangle(0, 0, rightBitmap.Width - 1, rightBitmap.Height - 1), ImageLockMode.ReadOnly, rightBitmap.PixelFormat);

            Marshal.Copy(bmd1.Scan0, b1bytes, 0, bytes);
            Marshal.Copy(bmd2.Scan0, b2bytes, 0, bytes);

            for (int n = 0; n <= bytes - 1; n++)
            {
                if (b1bytes[n] != b2bytes[n])
                {
                    result = false;
                    break;
                }
            }

            leftBitmap.UnlockBits(bmd1);
            rightBitmap.UnlockBits(bmd2);

            #endregion

            return result;
        }

That's all it is!
If you feel this is helpful or you like it, Please share this using share buttons available on page.

10 comments :

  1. Anonymous9/04/2013

    Great pure, simple .Net solution to this problem.

    It's a good idea to wrap the LockBits in try-finally block and call UnLockBits in the finally block.

    ReplyDelete
  2. Anonymous4/05/2014

    Nice sir,

    ReplyDelete
  3. Anonymous4/30/2014

    You can probably increase the speed of this even more if instead of marshal.Copy, you could just make the method unsafe and use pointers. additionally, if you don't care about the actual color data, read the file as UIn32 in the loop, instead of bytes.

    The first change would remove the need to copy both bitmaps to arrays, just use them where they already are. The second change reduces the number of cycles in your loop.

    ReplyDelete
    Replies
    1. I will advice against using Uint or int witout the following consideration.

      Using Uint/Int will not give the correct result every time.
      it will compile and it will not give runtime errors, but.. the result may vary

      The method is fine as long as raw byte width is a multiple of 4
      check that Abs(stride) == width

      it can fail for example under the following conditions:
      DataType: 8bit Indexed or 8bit Monochrome
      size = 3x3

      in this case, doing Int comparison the last 8 of the 32bits that is compared will have an undefined value.

      Consider yourself warned :)

      Delete
  4. Anonymous2/18/2016

    BitmapData bmd1 = leftBitmap.LockBits(new Rectangle(0, 0, leftBitmap.Width - 1, leftBitmap.Height - 1), ImageLockMode.ReadOnly, leftBitmap.PixelFormat);
    BitmapData bmd2 = rightBitmap.LockBits(new Rectangle(0, 0, rightBitmap.Width - 1, rightBitmap.Height - 1), ImageLockMode.ReadOnly, rightBitmap.PixelFormat);

    It gives exception when Width or Height equals to 0.

    ReplyDelete
    Replies
    1. Anonymous2/18/2016

      it should be: when Width or Height equals to 1.

      Delete
  5. لدينا مميزات في خدمات كشف تسربات التي تقدمها شركة ركن البيت التي تكون متخصصة فيها فتعاملك مع شركة كشف تسربات المياه بالدمام لديها امكانيات جيدة يساعدك علي التخلص من مشاكل التسريب التي توجد لديك بسهولة دون التعرض للخطر حيث نمتلك في شركة كشف تسربات بالدمام الامكانيات والفنين المتميزين الذين يقدمون الخدمة بتميز فاذا كنت فى حيرة من امر التسريب الذي يوجد لديك فعليك ان تعلم ان خدماتنا منتشرة في جميع انحاء المملكة مثل خدمات شركة كشف تسربات المياه بالرياض التي تحل لك المشاكل المتكررة المتعلقة بالتسربات فلا داعى للقلق من الان لانك سوف تملك فني جيد منزلك يحل لك كل مشاكل التسربات و كيفية القيام بهذه الخدمة وتذكر ان الحل الامثل فى شركة كشف تسربات بالرياض ان توفر كل الامكانيات التى تساعدك علي حل مشكلاتك

    ReplyDelete
  6. Thank you for sharing this.
    Could you please give me a contact email address, as I couldn't find any on this page.
    My question would be related to the type of license you apply for the code you've shared.
    Thanks in advance,
    Alexandra
    alexutaseptembrie@gmail.com

    ReplyDelete