Click here to Skip to main content
15,879,535 members
Articles / Multimedia / GDI+

Finding a Bitmap contained inside another Bitmap

Rate me:
Please Sign up or sign in to vote.
4.76/5 (22 votes)
10 Mar 2010CPOL4 min read 103.6K   4.7K   44   31
A method to look for a small Bitmap that is contained inside a bigger Bitmap.

BitmapDetector

Introduction

Several days ago, I was trying to find a way to locate a small Bitmap that I knew was contained in another, bigger Bitmap. I found methods on the Internet used to transform a Bitmap, to filter it, to resize/resample... but not what I needed.

After asking for some help here in CodeProject, I learnt how to go through a Bitmap using LockBits() (instead of the GetPixel() and SetPixel() methods, which are much slower), thanks to this article from Christian Graus, and finally, I could make my own (basic) algorithm to perform this search.

Comments, bug reports, and suggestions to improve performance will be highly appreciated since this is my first article here. Thanks to Basiuk for his bug report (the code is already updated to fix it), and to TF_Productions for his suggestions, all of which I hope I could implement in a future.

Background

Nothing special, I think.

Using the code

If you want to use it in your application, you only need the searchBitmap() method:

C#
private Rectangle searchBitmap(Bitmap smallBmp, Bitmap bigBmp, double tolerance)
{
    BitmapData smallData = 
      smallBmp.LockBits(new Rectangle(0, 0, smallBmp.Width, smallBmp.Height), 
               System.Drawing.Imaging.ImageLockMode.ReadOnly, 
               System.Drawing.Imaging.PixelFormat.Format24bppRgb);
    BitmapData bigData = 
      bigBmp.LockBits(new Rectangle(0, 0, bigBmp.Width, bigBmp.Height), 
               System.Drawing.Imaging.ImageLockMode.ReadOnly, 
               System.Drawing.Imaging.PixelFormat.Format24bppRgb);

    int smallStride = smallData.Stride;
    int bigStride = bigData.Stride;

    int bigWidth = bigBmp.Width;
    int bigHeight = bigBmp.Height - smallBmp.Height + 1;
    int smallWidth = smallBmp.Width * 3;
    int smallHeight = smallBmp.Height;

    Rectangle location = Rectangle.Empty;
    int margin = Convert.ToInt32(255.0 * tolerance);

    unsafe
    {
        byte* pSmall = (byte*)(void*)smallData.Scan0;
        byte* pBig = (byte*)(void*)bigData.Scan0;

        int smallOffset = smallStride - smallBmp.Width * 3;
        int bigOffset = bigStride - bigBmp.Width * 3;

        bool matchFound = true;

        for (int y = 0; y < bigHeight; y++)
        {
            for (int x = 0; x < bigWidth; x++)
            {
                byte* pBigBackup = pBig;
                byte* pSmallBackup = pSmall;

                //Look for the small picture.
                for (int i = 0; i < smallHeight; i++)
                {
                    int j = 0;
                    matchFound = true;
                    for (j = 0; j < smallWidth; j++)
                    {
                        //With tolerance: pSmall value should be between margins.
                        int inf = pBig[0] - margin;
                        int sup = pBig[0] + margin;
                        if (sup < pSmall[0] || inf > pSmall[0])
                        {
                            matchFound = false;
                            break;
                        }

                        pBig++;
                        pSmall++;
                    }

                    if (!matchFound) break;

                    //We restore the pointers.
                    pSmall = pSmallBackup;
                    pBig = pBigBackup;

                    //Next rows of the small and big pictures.
                    pSmall += smallStride * (1 + i);
                    pBig += bigStride * (1 + i);
                }

                //If match found, we return.
                if (matchFound)
                {
                    location.X = x;
                    location.Y = y;
                    location.Width = smallBmp.Width;
                    location.Height = smallBmp.Height;
                    break;
                }
                //If no match found, we restore the pointers and continue.
                else
                {
                    pBig = pBigBackup;
                    pSmall = pSmallBackup;
                    pBig += 3;
                }
            }

            if (matchFound) break;

            pBig += bigOffset;
        }
    }

    bigBmp.UnlockBits(bigData);
    smallBmp.UnlockBits(smallData);

    return location;
}

It receives two Bitmaps (the smaller one and the bigger one), and the tolerance that should apply when searching (0.0 meaning exact match). It returns a Rectangle with the location of the smaller image within the bigger one.

If no match is found, the width and the height of the Rectangle will be zero:

C#
Rectangle location = searchBitmap(bitmap1, bitmap2, tolerance);
if (location.Width != 0)
{
     //Do something.
}

Understanding the code

First of all, you should read the above mentioned article from Christian Graus.

What a Bitmap is for us

The key is that you must understand that a Bitmap is no longer an object with pixels ordered in columns and rows, but a series of bytes in your computer memory. A "row" of three pixels of a Bitmap could be seen as, for example, these bytes:

255 0 0 | 0 255 0 | 0 0 255 | X ... X

What we have is:

  • Three bytes per pixel, each one for one color component of the pixel. The order of the bytes is BGR (blue component, green component, red component), due to the fact that PixelFormat.Format24bppRgb shows BGR instead of RGB.
  • The first pixel is BLUE in our example.
  • The second pixel is GREEN in our example.
  • And, the third and last pixel is RED in our example.

They are followed by a variable number of bytes "X ... X", which I have named "offset" in the code, and are used for padding. We are not interested in their contents. You can see how the offset is calculated in the famous article from Christian Graus.

If we had a bitmap with two rows, these would be seen as follows:

255 0 0 | 0 255 0 | 0 0 255 | X ... X | 192 0 192 | 128 128 0 | 0 0 128 | X ... X

So, if we are looking at one "pixel" and want to go to the next row, what we need to do is move forward 9 bytes (3 bytes per pixel, 3 pixels per row) plus the offset. It is done in the following line of code:

C#
pBig += bigStride * (1 + i);

which is equivalent to:

C#
pBig += (bigWidth * 3 + bigOffset) * (1 + i);

Of course, if you want to move to the next pixel, you must move forward 3 bytes (or the offset, if you are at the last pixel of a row):

C#
pBig += 3;
pBig += bigOffset;

The algorithm

Once the previous point is understood, I think the rest is pretty self-explanatory:

  1. The method goes through the bigger Bitmap, "row" by "row", searching for "pixels" that match those of the first "row" of the smaller Bitmap.
  2. When a match is found, we go to the next "row" of the bigger Bitmap and the next "row" of the smaller one (still staying at the same "columns"), and compare them.
  3. If they're not equal (considering the tolerance), we restore the pointers and continue searching from where we are.
  4. If they're equal (considering the tolerance), we repeat step 2 until we have compared all the "rows" of the smaller Bitmap. At that point, if we have a match, we return the corresponding Rectangle.

Sample application

The application is very simple: you choose two bitmaps, choose the tolerance level, and press the "Search" button.

Clipboard02.png

The output will be the Rectangle in which the smaller Bitmap is located inside the bigger one.

The "Auto" check can be useful: it starts searching with tolerance = 0 (exact match), and increases it in a loop, searching again and again until a match is found or the maximum tolerance is reached. Note that, with bigger tolerances, the time increases.

If you want to search only for an exact match, use tolerance = 0.0.

Clipboard03.png

When to use and when not

The above method is useful for finding a small Bitmap (like an icon or a button) inside a bigger one (like a window capture). If you look for exact matches or use low tolerances, you can get the output in a few milliseconds (15-30 ms.), which is much faster than using algorithms with the GetPixel() and SetPixel() methods.

The problem is when you want to look for a big Bitmap contained in a huge Bitmap. The time increases a lot with the size of the images, so be careful choosing the images and the tolerance.

History

  • 31.07.2009 - Posted (first version).
  • 10.03.2010 - Updated source code.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Spain Spain
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionIgnore Color Pin
Kev In 202224-Sep-22 9:09
Kev In 202224-Sep-22 9:09 
QuestionWhy are we not subtracting the width of the small picture? Pin
Quarex11-Sep-22 23:22
Quarex11-Sep-22 23:22 
QuestionDifferent size of the pictures ! Pin
Hafız Toraman5-Apr-21 22:31
Hafız Toraman5-Apr-21 22:31 
GeneralMy vote of 5 Pin
Nicolae Fieraru27-Jan-20 21:13
Nicolae Fieraru27-Jan-20 21:13 
QuestionIts possible to re-write this code in c++? Pin
Member 1411885817-Sep-19 5:01
Member 1411885817-Sep-19 5:01 
QuestionThank You. That's a great job. Pin
Murad Tulunay23-Mar-17 21:29
Murad Tulunay23-Mar-17 21:29 
AnswerRe: Thank You. That's a great job. Pin
Arvins23-Aug-17 18:37
Arvins23-Aug-17 18:37 
PraiseFor Android Code for this Pin
chintak1233215-Jan-17 20:31
chintak1233215-Jan-17 20:31 
PraiseGreat code ! Pin
Member 790025610-Oct-16 7:58
Member 790025610-Oct-16 7:58 
QuestionRotated image search Pin
Member 1250208522-Sep-16 0:06
Member 1250208522-Sep-16 0:06 
QuestionCan I have it in Java? Pin
Member 790025623-Jul-16 22:31
Member 790025623-Jul-16 22:31 
QuestionAdding transparency Pin
Member 114676183-Jan-16 5:57
Member 114676183-Jan-16 5:57 
Questioncan i have this code in vb.net Pin
AHMED CD13-Jul-15 18:39
AHMED CD13-Jul-15 18:39 
AnswerRe: can i have this code in vb.net Pin
dazza00023-Nov-15 15:06
dazza00023-Nov-15 15:06 
GeneralMy vote of 5 Pin
Sturms27-Jan-15 8:57
Sturms27-Jan-15 8:57 
QuestionStraight-forward optimization: KMP Pin
infinite-recursion10-Oct-13 1:58
infinite-recursion10-Oct-13 1:58 
QuestionHandle multiple image finds Pin
Michael McBrien6-Dec-11 20:29
Michael McBrien6-Dec-11 20:29 
AnswerRe: Handle multiple image finds Pin
Italo Gomes6-Jan-14 6:03
Italo Gomes6-Jan-14 6:03 
QuestionHot to fix for another formats .jpg .png .tif? Pin
jgui27-Mar-11 19:34
jgui27-Mar-11 19:34 
GeneralThis is great Pin
Gopher201117-Mar-11 0:10
Gopher201117-Mar-11 0:10 
GeneralError in algorithm Pin
basiuk9-Mar-10 4:09
basiuk9-Mar-10 4:09 
GeneralRe: Error in algorithm [modified] Pin
verence3339-Mar-10 7:39
verence3339-Mar-10 7:39 
GeneralRe: Error in algorithm Pin
basiuk9-Mar-10 20:48
basiuk9-Mar-10 20:48 
GeneralRe: Error in algorithm Pin
verence3339-Mar-10 21:29
verence3339-Mar-10 21:29 
GeneralRe: Error in algorithm Pin
basiuk9-Mar-10 22:02
basiuk9-Mar-10 22:02 

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

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