How to compare two UIImage objects
IosIos Problem Overview
I am developing one application.In that i am using the imageviews
.SO before changeing the UIImageview
image I need to take that image in UIimage
obejct and compare with another UIImage
object for finding both are sam or not. So please tell me how to do that one.
Ios Solutions
Solution 1 - Ios
One way is to convert them to image data first, and then compare that.
- (BOOL)image:(UIImage *)image1 isEqualTo:(UIImage *)image2
{
NSData *data1 = UIImagePNGRepresentation(image1);
NSData *data2 = UIImagePNGRepresentation(image2);
return [data1 isEqual:data2];
}
Solution 2 - Ios
A Swift implementation of @Simon's answer:
func image(image1: UIImage, isEqualTo image2: UIImage) -> Bool {
let data1: NSData = UIImagePNGRepresentation(image1)!
let data2: NSData = UIImagePNGRepresentation(image2)!
return data1.isEqual(data2)
}
Or by extending UIImage based on @nhgrif's suggestion:
import UIKit
extension UIImage {
func isEqualToImage(image: UIImage) -> Bool {
let data1: NSData = UIImagePNGRepresentation(self)!
let data2: NSData = UIImagePNGRepresentation(image)!
return data1.isEqual(data2)
}
}
Solution 3 - Ios
Updated Mark Tickner's solution to Swift 4
import UIKit
extension UIImage {
func isEqualToImage(_ image: UIImage) -> Bool {
let data1 = self.pngData()
let data2 = image.pngData()
return data1 == data2
}
}
The two variables are probably overkill, but they might help explain to someone new to this. Could shorten to:
import UIKit
extension UIImage {
func isEqualToImage(_ image: UIImage) -> Bool {
return self.pngData() == image.pngData()
}
}
Solution 4 - Ios
when both using [UIImage imageNamed:]
, we can use isEqual:
, otherwise we could compare the data.
Solution 5 - Ios
My preferred (Swift) solution
import UIKit
func ==(lhs: UIImage, rhs: UIImage) -> Bool
{
guard let data1 = UIImagePNGRepresentation(lhs),
data2 = UIImagePNGRepresentation(rhs)
else { return false }
return data1.isEqual(data2)
}
Solution 6 - Ios
The right answer depends on "What kind of comparison you want to do?".
- The easiest way is just to compare data.
- If you want to know whether image were created from one local file – you can use -isEqual: (but there is an dangerous way, because I'm not sure, what happens if image cache clear for some reason).
- The hard way is provide per-pixel comparison (surely, system will spend more time on it). I can't provide the code from our company's library because of legal reason :(
But you can check good example on facebook's ios-snapshot-test-case project here: link right to the needed file. You can use performance tests to measure process time.
For the Great Justice I'll copy code from there below:
- (BOOL)fb_compareWithImage:(UIImage *)image tolerance:(CGFloat)tolerance
{
NSAssert(CGSizeEqualToSize(self.size, image.size), @"Images must be same size.");
CGSize referenceImageSize = CGSizeMake(CGImageGetWidth(self.CGImage), CGImageGetHeight(self.CGImage));
CGSize imageSize = CGSizeMake(CGImageGetWidth(image.CGImage), CGImageGetHeight(image.CGImage));
// The images have the equal size, so we could use the smallest amount of bytes because of byte padding
size_t minBytesPerRow = MIN(CGImageGetBytesPerRow(self.CGImage), CGImageGetBytesPerRow(image.CGImage));
size_t referenceImageSizeBytes = referenceImageSize.height * minBytesPerRow;
void *referenceImagePixels = calloc(1, referenceImageSizeBytes);
void *imagePixels = calloc(1, referenceImageSizeBytes);
if (!referenceImagePixels || !imagePixels) {
free(referenceImagePixels);
free(imagePixels);
return NO;
}
CGContextRef referenceImageContext = CGBitmapContextCreate(referenceImagePixels,
referenceImageSize.width,
referenceImageSize.height,
CGImageGetBitsPerComponent(self.CGImage),
minBytesPerRow,
CGImageGetColorSpace(self.CGImage),
(CGBitmapInfo)kCGImageAlphaPremultipliedLast
);
CGContextRef imageContext = CGBitmapContextCreate(imagePixels,
imageSize.width,
imageSize.height,
CGImageGetBitsPerComponent(image.CGImage),
minBytesPerRow,
CGImageGetColorSpace(image.CGImage),
(CGBitmapInfo)kCGImageAlphaPremultipliedLast
);
if (!referenceImageContext || !imageContext) {
CGContextRelease(referenceImageContext);
CGContextRelease(imageContext);
free(referenceImagePixels);
free(imagePixels);
return NO;
}
CGContextDrawImage(referenceImageContext, CGRectMake(0, 0, referenceImageSize.width, referenceImageSize.height), self.CGImage);
CGContextDrawImage(imageContext, CGRectMake(0, 0, imageSize.width, imageSize.height), image.CGImage);
CGContextRelease(referenceImageContext);
CGContextRelease(imageContext);
BOOL imageEqual = YES;
// Do a fast compare if we can
if (tolerance == 0) {
imageEqual = (memcmp(referenceImagePixels, imagePixels, referenceImageSizeBytes) == 0);
} else {
// Go through each pixel in turn and see if it is different
const NSInteger pixelCount = referenceImageSize.width * referenceImageSize.height;
FBComparePixel *p1 = referenceImagePixels;
FBComparePixel *p2 = imagePixels;
NSInteger numDiffPixels = 0;
for (int n = 0; n < pixelCount; ++n) {
// If this pixel is different, increment the pixel diff count and see
// if we have hit our limit.
if (p1->raw != p2->raw) {
numDiffPixels ++;
CGFloat percent = (CGFloat)numDiffPixels / pixelCount;
if (percent > tolerance) {
imageEqual = NO;
break;
}
}
p1++;
p2++;
}
}
free(referenceImagePixels);
free(imagePixels);
return imageEqual;
}
Solution 7 - Ios
Converting the images to JPG / PNG, or relying on accessibility identifiers is either an expensive operation, or fragile & prone to failure.
Here, I follow the suggestion provided by Apple at the following link:
> The isEqual(:) method is the only reliable way to determine whether > two images contain the same image data. The image objects you create > may be different from each other, even when you initialize them with > the same cached image data. The only way to determine their equality > is to use the isEqual(:) method, which compares the actual image > data. Listing 1 illustrates the correct and incorrect ways to compare > images.
To simplify things, I create the following extension for doing comparisons, so that I can avoid the issue of converting the first image:
import UIKit
extension UIImage {
func isEqual(to image: UIImage) -> Bool {
return isEqual(image)
}
}
With this, I can now setup an example to compare it on a pair of images:
let imageA = UIImage(named: "a")!
let imageB = UIImage(named: "b")!
let imageC = UIImage(named: "a")!
print(imageA.isEqual(to: imageA)) // true
print(imageA.isEqual(to: imageC)) // true
print(imageA.isEqual(to: imageB)) // false
Solution 8 - Ios
I did some changes to Mark's answer and used Data and elementsEqual instead NSData and isEqual.
extension UIImage {
func isEqual(to image: UIImage) -> Bool {
guard let data1: Data = UIImagePNGRepresentation(self),
let data2: Data = UIImagePNGRepresentation(image) else {
return false
}
return data1.elementsEqual(data2)
}
}
Solution 9 - Ios
Swift 4.x version of Facebook's comparison algorithm:
/// Value in range 0...100 %
typealias Percentage = Float
// See: https://github.com/facebookarchive/ios-snapshot-test-case/blob/master/FBSnapshotTestCase/Categories/UIImage%2BCompare.m
private func compare(tolerance: Percentage, expected: Data, observed: Data) throws -> Bool {
guard let expectedUIImage = UIImage(data: expected), let observedUIImage = UIImage(data: observed) else {
throw Error.unableToGetUIImageFromData
}
guard let expectedCGImage = expectedUIImage.cgImage, let observedCGImage = observedUIImage.cgImage else {
throw Error.unableToGetCGImageFromData
}
guard let expectedColorSpace = expectedCGImage.colorSpace, let observedColorSpace = observedCGImage.colorSpace else {
throw Error.unableToGetColorSpaceFromCGImage
}
if expectedCGImage.width != observedCGImage.width || expectedCGImage.height != observedCGImage.height {
throw Error.imagesHasDifferentSizes
}
let imageSize = CGSize(width: expectedCGImage.width, height: expectedCGImage.height)
let numberOfPixels = Int(imageSize.width * imageSize.height)
// Checking that our `UInt32` buffer has same number of bytes as image has.
let bytesPerRow = min(expectedCGImage.bytesPerRow, observedCGImage.bytesPerRow)
assert(MemoryLayout<UInt32>.stride == bytesPerRow / Int(imageSize.width))
let expectedPixels = UnsafeMutablePointer<UInt32>.allocate(capacity: numberOfPixels)
let observedPixels = UnsafeMutablePointer<UInt32>.allocate(capacity: numberOfPixels)
let expectedPixelsRaw = UnsafeMutableRawPointer(expectedPixels)
let observedPixelsRaw = UnsafeMutableRawPointer(observedPixels)
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
guard let expectedContext = CGContext(data: expectedPixelsRaw, width: Int(imageSize.width), height: Int(imageSize.height),
bitsPerComponent: expectedCGImage.bitsPerComponent, bytesPerRow: bytesPerRow,
space: expectedColorSpace, bitmapInfo: bitmapInfo.rawValue) else {
expectedPixels.deallocate()
observedPixels.deallocate()
throw Error.unableToInitializeContext
}
guard let observedContext = CGContext(data: observedPixelsRaw, width: Int(imageSize.width), height: Int(imageSize.height),
bitsPerComponent: observedCGImage.bitsPerComponent, bytesPerRow: bytesPerRow,
space: observedColorSpace, bitmapInfo: bitmapInfo.rawValue) else {
expectedPixels.deallocate()
observedPixels.deallocate()
throw Error.unableToInitializeContext
}
expectedContext.draw(expectedCGImage, in: CGRect(origin: .zero, size: imageSize))
observedContext.draw(observedCGImage, in: CGRect(origin: .zero, size: imageSize))
let expectedBuffer = UnsafeBufferPointer(start: expectedPixels, count: numberOfPixels)
let observedBuffer = UnsafeBufferPointer(start: observedPixels, count: numberOfPixels)
var isEqual = true
if tolerance == 0 {
isEqual = expectedBuffer.elementsEqual(observedBuffer)
} else {
// Go through each pixel in turn and see if it is different
var numDiffPixels = 0
for pixel in 0 ..< numberOfPixels where expectedBuffer[pixel] != observedBuffer[pixel] {
// If this pixel is different, increment the pixel diff count and see if we have hit our limit.
numDiffPixels += 1
let percentage = 100 * Float(numDiffPixels) / Float(numberOfPixels)
if percentage > tolerance {
isEqual = false
break
}
}
}
expectedPixels.deallocate()
observedPixels.deallocate()
return isEqual
}
Solution 10 - Ios
> Swift 3
There is two ways. Like:-
-
Use isEqual() function.
self.image?.isEqual(UIImage(named: "add-image"))
-
Use accessibilityIdentifier
Set the accessibilityIdentifier as image Name
myImageView.image?.accessibilityIdentifier = "add-image"
Then Use the following code.
extension UIImageView
{
func getFileName() -> String? {
// First set accessibilityIdentifier of image before calling.
let imgName = self.image?.accessibilityIdentifier
return imgName
}
}
Finally, The calling way of method to identify
myImageView.getFileName()
Solution 11 - Ios
Swift 5.x
You can just use
let image: UIImage!
let anotherImage: UIImage!
image == anotherImage
Solution 12 - Ios
Compare the image size initially
For a less expensive method, compare the image size initially. Even if there is a small change inside an image, the size will be different.
NSData *data1 = UIImagePNGRepresentation(image1);
NSData *data2 = UIImagePNGRepresentation(image2);
if(data1.length == data2.length) {
// if required, compare the data to confirm it
if(data1 isEqual:data2) {
// images are exactly same
} else {
// even though size is same images are different
}
} else {
// images are different.
}
Successfully tested in comparing images from same source (same dimension, format etc).
Solution 13 - Ios
This method works great:
func isEqualImages(image1: UIImage, image2: UIImage) -> Bool {
let data1: Data? = UIImagePNGRepresentation(image1)
let data2: Data? = UIImagePNGRepresentation(image2)
return data1 == data2
}
Solution 14 - Ios
Short-hand and Swifty solution
extension UIImage: Equatable {
static func ==(lhs: UIImage, rhs: UIImage) -> Bool {
return lhs.pngData() == rhs.pngData()
}
}
Solution 15 - Ios
I needed to detect differences within the video feed frames and threshold that difference in order to decide to do something within my code. You can use a similar pixel comparison to look at the pngData of the UIImage.
see my answer here
Solution 16 - Ios
i not sure about comparing the UIImage data as it would be prettry expensive. What you could do is subclass Uimage and add tag property your self and then compare tag before changing the image. assign
Solution 17 - Ios
if one image name is known to you,it will help you....
CGImageRef cgImage = [imageView.image CGImage];
if (cgImage == [UIImage imageNamed:@"imagename.png"].CGImage) {
// True Statement
}
Solution 18 - Ios
Well you can also the 'tag' property to identify the objects at latter stages of your program. Just set the integer value and you are good to go
Solution 19 - Ios
We need to use isEqualToData method in Objective-C. The Apple document states that
Two data objects are equal if they hold the same number of bytes, and if the bytes at the same position in the objects are the same.
- (BOOL)image:(UIImage *)image1 isEqualTo:(UIImage *)image2
{
NSData *data1 = UIImagePNGRepresentation(image1);
NSData *data2 = UIImagePNGRepresentation(image2);
return [data1 isEqualToData:data2];
}