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?
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.
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.
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.
#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
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.
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
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.
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