Skip to main content

EvoLisa - Comparing two images: performance tuning

It looks like the program is rather slow. Using DotTrace, I took a look at where the bottleneck is.

The method that calculates the 'fitnesse', how closely the created image resembles the original image, is where most of the time goes to. Can we improve that?

So what happens in this function? We compare each pixel of both images to compare their ARGB values. The better they match, the better the result! How can be compare this?

First, I tried using the naive approach: compare each pixel using a bitmaps GetPixel(x, y). Do that however, and you'll quickly see how excruciatingly slow that is. Especially considering that even with an image of 333x333 you're already comparing a million pixels each time you compare two images!

So, instead I tried using a 'FastBitmap'. It's a project I found which wraps the Bitmap and provides much faster Get and SetPixel performance. Using this instead, the performance went up by an order of magnitude!

But we can still do better. Taking a look at the code, it's using the 'LockBits' to get the entire Bitmap in memory. It's then using pointer magic and some basic multiplications to get each pixel. But these multiplications happen for each of the million pixels.

In this case however, we don't have to reinvent the wheel. Someone else already solved this problem for us! Dan Bystrom provided some code to make the fitnesse function even faster. Before, using the FastBitmap each GetPixel would do a multiplication like this:
private void LockBitmap()
{
    GraphicsUnit unit = GraphicsUnit.Pixel;
    RectangleF boundsF = Subject.GetBounds(ref unit);
    Rectangle bounds = new Rectangle(
        (int)boundsF.X,
        (int)boundsF.Y,
        (int)boundsF.Width,
            (int)boundsF.Height);
    SubjectWidth = (int)boundsF.Width * sizeof(PixelData);
    if (SubjectWidth % 4 != 0)
    {
        SubjectWidth = 4 * (SubjectWidth / 4 + 1);
    }
    bitmapData = Subject.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
    pBase = (Byte*)bitmapData.Scan0.ToPointer();
}

private PixelData* PixelAt(int x, int y)
{
    return (PixelData*)(pBase + y * SubjectWidth + x * sizeof(PixelData));
}

Now, we no longer have to use multiplication on each line! The code now looks like this:
public long CalculateFitness(Population pop)
{
    long fitnesse = 0;

    var picture = pop.GetPicture();
    BitmapData bd = picture.LockBits(
        new Rectangle(0, 0, Settings.ScreenWidth, Settings.ScreenHeight),
        ImageLockMode.ReadOnly,
        PixelFormat.Format32bppArgb);
    BitmapData obd = _bitmap.LockBits(
        new Rectangle(0, 0, Settings.ScreenWidth, Settings.ScreenHeight),
        ImageLockMode.ReadOnly,
        PixelFormat.Format32bppArgb);

    unchecked
    {
        unsafe
        {
            //Shamelessly copied from https://danbystrom.se/2008/12/14/improving-performance/
            Pixel* p1 = (Pixel*)bd.Scan0.ToPointer();
            Pixel* p2 = (Pixel*)obd.Scan0.ToPointer();
            var size = Settings.ScreenWidth * Settings.ScreenHeight;
            for (int i = size; i > 0; i--, p1++, p2++)
            {
                int r = p1->R - p2->R;
                int g = p1->G - p2->G;
                int b = p1->B - p2->B;
                fitnesse += r * r + g * g + b * b;
            }                    
        }
    }

    _bitmap.UnlockBits(obd);
    picture.UnlockBits(bd);

    return fitnesse;
}

Usually you don't need this kind of micro optimisation but in this case, because this code is done so many times, the performance gain is enormous! And to finish, here's a 200 polygon image of Mila Kunis:

Comments

Popular posts from this blog

EvoLisa - My own version

It's an old idea by now, can we recreate the Mona Lisa with fifty polygons by using random changes? The idea and original implementation by Roger Johansson can be found here . So, how does it work? You start with an source image. Then you create an empty image. We keep on doing small changes to this image. In my case this is one of these changes: Recolor our polygon Change the position of one of the polygon points Add or remove a new point to our polygon Add or remove a new polygon Switch two polygons After each action, we take a look and check if the newly changed image looks more like the original image by comparing each pixel. If it is, we continue using this one. If it's not, we discard the changes. Looks like a fun project! So I built my own version from scratch eight years ago, reusing some of the same ideas. Here's my result using the Mona Lisa: Mona Lisa with 50 polygons: You can see that it's the Mona Lisa but the details around the eyes and mou

You can leave your hat on

You always think that these things only happen to others. You may even think that they should have used better protection. And then one day you get this mail in your box from your guild leader: Hi there, I dont know if this email will arrive, but I will try anyway. I saw that your chars came online today. They are selling all your gear and equipment and they are not answering on guild chat. I think you are being hacked. Suddenly it's not someone else who has been hacked. It's you! So I quickly took a  look in my second mailbox account and yes: someone merged my account to a battle.net account. My account has been frozen for half a year so someone must have hacked it and have reactivated it. Next thing I did was checking my characters and my armory page looked like this: Kind of them to put on my Christmas hat, it's the season!. Everything that a vendor will accept is sold however. After seeing that picture I  really started to worry. They reactivated my accou