Comparing Pictures Question

Hey all,

I’m trying to do some comparison of images stored in a database. It’s a long story but my current code is very inefficient. I have a number of icons assigned to buttons in various places in my app. Currently, I save the icon along with the button information into the app’s database. There are many places where buttons could have the same icon. So I’m storing the same icon multiple times which doesn’t really make sense and it makes the database unnecessarily large. What I am working on now is instead saving all the icons into a separate table and storing an index to that Icon for every button. The goal is that if the same icon is used in 5 places, I have one copy of the icon and not 5.

These icons are user configurable so the user decides what buttons have what image.

I’m having problems though comparing images. It turns out that the same PNG image can have string values that vary in length and content from another PNG image that is exactly the same! I’m not entirely sure why but that seems to be the case.

I’ve tried numerous different ways of doing it but I get inconsistent results. I’ve tried converting the pictures to memory blocks and then the memory block to string. I’ve tried putting the image data into a string right from the database using NativeValue, etc.

So what’s the best way to compare two pictures/images and determine if they are the same?

I’m quite stuck on this right now!

Its actually a complex task.

A poor mans image comparison would be to use a difference blending mode and then evaluate the resulting image. Black pixels indicate no change and white pixels indicate the opposite.

So how can you have two graphics that are physically the exact same image when I view them with my eyes. But their byte representation is different. I’ve seen two images that are the same have a one byte difference. Others are much different but still the same.

That’s kind of the point of image compression. If you don’t want to jump into the rabbit hole this will lead you down, could you use a user configurable “images list” and store references instead?

Would an MD5 or SHA1 hash of the image tell you if they’re the different? I’d think the 1 byte difference would change the hash. Or are you looking for something more subtle?

A decoded JPG Would never be the same as decoded PNG. Since JPG is lossy format. So a hash would not help you.

Only way to compare across formats would be to decode them and do some sort of analysis with some given tolerance factor which decides on what can slide through as same and what cannot.

1 Like

If it is exactly the same PNG data then you should be able to use a hash.

If not I probably wouldn’t bother trying to remove the duplicates automatically and just leave it to the user to manage.

We have

GMImageMBS.Hash as String

In MBS Plugins to compare pictures and even catch those resized, recompressed or color matched.

For preventing exact duplicates use SHA256.

The different blending mode is used to compare the pixels, not the stored data. If it returns a solid black image, it’s pixels are identical.

Sam,

That would probably work quite well for this task. The images are simple.

Do you have a pointer to and example?

The equation is really simple abs( pixelA - pixelB ) So you can do this with two RGBSurfaces if you would like.

If you’d like better performance and x-plat, look at https://www.einhugur.com/Html/PictureEffectsInfo.html

If you’re targeting the Mac only, you can use the MBS plugin or declares to set the blending mode of a Xojo graphics object.

I am not at my “work” computer right now, so I can’t copy paste the declares, CGContextSetBlendingMode( g.CGContextInstance, CGBlendingMode.difference). I’ll get to the office in a bit.

Sam,

Thanks. I was thinking it was something pretty simple like this. These are icons - so not millions of colors, etc. Subtracting out the pixels should be sufficient and pretty fast - the icons are small too.

I’ll try this out!

1 Like
#if targetMacOS then
  declare Sub CGContextDrawImage lib "CoreGraphics" ( CGContextInstance as integer, rect as NSRect, image as Ptr )
  declare Sub CGContextSetBlendMode lib "CoreGraphics" ( CGContextInstance as integer, mode as integer )
  declare Sub CGImageRelease lib "CoreGraphics" ( imageRef as Ptr )
  
  // --- Obtain a CGImage Ref
  Dim p as ptr             = appleCarinCars.CopyOSHandle( picture.handleType.MacCGImage )
  
  // --- Obtain a CGContext Instance
  Dim CGContext as integer = g.handle( g.handleTypeCGContextRef )
  
  // --- Create a rect to draw into
  Dim r as NSRect
  r.origin.y               = g.height - appleCarinCars.height
  r.area.width             = appleCarinCars.width
  r.area.height            = appleCarinCars.height
  
  // --- Draw the image
  CGContextDrawImage( CGContext, r, p )
  
  // --- Set the blend mode to difference
  CGContextSetBlendMode( CGContext, 10 )
  
  // --- Draw the second image
  CGContextDrawImage( CGContext, r, p )
  
  // --- Now release all images
  CGImageRelease p
#endIf

It seems that something changed in recent Xojo as I used to be able to set the blending mode and drawing a picture using Xojo API would respect that, now it resets the blending mode :frowning:

So this code fragment includes the functions needed to convert a Xojo picture to a CGImage, draw that CGImage and then release it. Do not convert and release in the paint event of a production application, this is wasteful and will degrade performance.

Thanks. I need to be Xplat so I’m probably just going to subtract out the values from the RGB surfaces.

1 Like

I spent some time this evening trying to figure this out.

I tried using hashes - still not giving me the results I wanted. Sam pointed in the right direction. I compared each pixel between two images.

It’s surprising how much variation a color on an image can have. Black is not necessarily all black. There is variation - as @Tim_Parnell pointed out, you have compression. So I’ve loosened the tolerance a little bit. This code here works well. I assume that the images are equal until I find a pixel that doesn’t match (I’m looking for sufficiently large variations…) and then I stop looking.

Tonight I took a database from one of my customers that had about 30,000 images and ran this. I took th database from 460 MB to 9 MB! I took about 90 minutes to convert everything but now we have a database a fraction of the size…

Public Function CompareImages(p1 as Picture, p2 as Picture) as Boolean
  Dim FoundImage As Boolean
  
  // First compare width and height - if they are not the same then they are different images
  If (p1.Width = p2.Width) And (p1.Height = p2.Height) Then
    
    // Now get the RGBSurfaces of each picture
    Dim rgb1 As RGBSurface = p1.RGBSurface
    Dim rgb2 As RGBSurface = p2.RGBSurface
    
    // Set maximum width and height values for looping
    Dim Xmax As Integer = p1.Width-1
    Dim Ymax As Integer = p1.Height-1
    
    // Assume the images are the same
    FoundImage = True
    
    // Scan each pixel and compare the colors.
    // If the color is the same, move to the next pixel.
    // If the color is different, they are not the same images - stop the process.
    // Allow for small variations in color w/o failing..
    
    For y As Integer = 0 To Ymax
      For x As Integer = 0 To Xmax
        
        Dim diff As Double = Abs(rgb1.Pixel(x,y).Value-rgb2.Pixel(x,y).Value)
        
        If diff > 0.3 Then // Looking for variatios larger than 0.3
          
          // Colors are not the same in that pixel - Set FoundImage to False and exit the loop
          FoundImage = False
          Exit For y
        End If
      Next
    Next
    
  End If
  
  
  Return FoundImage
End Function
2 Likes

Depending on the size of your images and how they are created, you can certainly speed up that loop by comparing every 2nd, 5th, 10th pixel:

For y As Integer = 0 To Ymax step 2 //or step 5, or step 10
      For x As Integer = 0 To Xmax step 2//or step 5, or step 10

On the downside, it can return false positives

Agreed.

Although the big long process only takes one time with users who have a lot of images loaded and after that, the method only runs when a new image is being added. So it should go pretty quick.

Your suggestion reminds me of the image compression techniques I was working on during an internship in Japan back in the late 80’s. We’d compare pixels around a particular pixel and decided if we wanted to eliminate that pixel from the file to be transferred across the network.

Hey all,

My code above has a very serious flaw in it. The Color.Value Property is not a “value” for color. It’s really the value of the brightness when using the HSV color format (Hue, Saturation, Value). Value is misleading. It’s really brightness.

I had two simple images - one was basically a red dot. Another was a yellow dot. The code I posted said they were identical. Obviously that’s a fail. So please don’t use that.

Here is a new version of the method. In my case I am looking for significant variations between the two images. It’s particularly hard to compare colors in the black spectrum because do you realize how many shades of black there are?? They are so subtle you can’t tell. In my case, I’m looking to see if there’s white where there should be blue, red where there should be black, etc.

  Public Function CompareImages(p1 as Picture, p2 as Picture) as Boolean
  Dim FoundImage As Boolean
  
  // First compare width and height - if they are not the same then they are different images
  If (p1.Width = p2.Width) And (p1.Height = p2.Height) Then
    
    // Now get the RGBSurfaces of each picture
    Dim rgb1 As RGBSurface = p1.RGBSurface
    Dim rgb2 As RGBSurface = p2.RGBSurface
    
    // Set maximum width and height values for looping
    Dim Xmax As Integer = p1.Width-1
    Dim Ymax As Integer = p1.Height-1
    
    // Assume the images are the same
    FoundImage = True
    
    // Scan each pixel and compare the colors.
    // If the color is the same, move to the next pixel.
    // If the color is different, they are not the same images - stop the process.
    // Allow for small variations in color w/o failing..
    
    For y As Integer = 0 To Ymax
      For x As Integer = 0 To Xmax
        
        Dim c1 As Color = rgb1.Pixel(x,y)
        Dim c2 As Color = rgb2.Pixel(x,y)
        
        Dim r1 As Integer = c1.red
        Dim r2 As Integer = c2.red
        Dim g1 As Integer = c1.green
        Dim g2 As Integer = c2.green
        Dim b1 As Integer = c1.blue
        Dim b2 As Integer = c2.blue
        
        // If the value of all of the color components is less than 80 then we pretty much have black.
        // We aren't going to try to analyze black.
        If (r1 < 80 And r2 < 80)  And (g1 < 80 And g2 < 80) And (b1 < 80 And b2 < 80) Then
          Continue
        Else
          // Now compare each color component.  If they vary by more than 50 than consider it a fail.
          If Abs(r1-r2) > 50 Then   // compare red
            FoundImage = False
            Exit For Y
          Elseif Abs(g1-g2) > 50 Then   // compare green
            FoundImage = False
            Exit For Y
          Elseif Abs(b1-b2) > 50 Then  // compare blue
            FoundImage = False
            Exit For Y
          End If
        End If
      Next
    Next
  End If

  Return FoundImage
End Function