我正在开发一个应用程序。在其中我使用了 imageviews
。在更改 UIImageview
图像之前,我需要将该图像放入 UIimage
对象中,并与另一个 UIImage
对象进行比较,以查找它们是否相同。请告诉我如何实现。
我正在开发一个应用程序。在其中我使用了 imageviews
。在更改 UIImageview
图像之前,我需要将该图像放入 UIimage
对象中,并与另一个 UIImage
对象进行比较,以查找它们是否相同。请告诉我如何实现。
一种方法是先将它们转换为图像数据,然后进行比较。
- (BOOL)image:(UIImage *)image1 isEqualTo:(UIImage *)image2
{
NSData *data1 = UIImagePNGRepresentation(image1);
NSData *data2 = UIImagePNGRepresentation(image2);
return [data1 isEqual:data2];
}
isEqualToData:
还是 isEqual:
?这是一个合理的问题——我不确定 isEqual:
在处理 NSData 对象时是否会失败,但是使用专门为该类设计的条件测试似乎是合乎逻辑的选择,对吗? - toblerpwn一个Swift实现,来自@Simon的答案:
func image(image1: UIImage, isEqualTo image2: UIImage) -> Bool {
let data1: NSData = UIImagePNGRepresentation(image1)!
let data2: NSData = UIImagePNGRepresentation(image2)!
return data1.isEqual(data2)
}
或者根据 @nhgrif 的建议扩展 UIImage:
import UIKit
extension UIImage {
func isEqualToImage(image: UIImage) -> Bool {
let data1: NSData = UIImagePNGRepresentation(self)!
let data2: NSData = UIImagePNGRepresentation(image)!
return data1.isEqual(data2)
}
}
false
,而不是强制解包和崩溃,如果任何一个为 nil
。guard let data1 = UIImagePNGRepresentation(self),data2 = UIImagePNGRepresentation(image) else { return false } return data1 == data2
- nhgrif更新了 Mark Tickner 的解决方案以适应 Swift 4
import UIKit
extension UIImage {
func isEqualToImage(_ image: UIImage) -> Bool {
let data1 = self.pngData()
let data2 = image.pngData()
return data1 == data2
}
}
这两个变量可能有些多余,但它们可能有助于向新手解释。可以缩短为:
import UIKit
extension UIImage {
func isEqualToImage(_ image: UIImage) -> Bool {
return self.pngData() == image.pngData()
}
}
当使用[UIImage imageNamed:]
时,我们可以使用isEqual:
来比较,否则我们可以比较数据。
isEqual:
的默认实现是从 NSObject
继承而来的,它是一个简单的引用比较。然而,一些对象会覆盖这个默认实现。例如,我知道其中一个是 NSString
,它的 isEqual:
在功能上等同于 isEqualToString:
,两者都是值比较,而不是引用比较。至于 UIImage
是否重写了继承的方法 isEqual
,我无法评论。 - nhgrif但是,您可以在facebook的ios-snapshot-test-case项目中检查好的示例:链接指向所需文件。您可以使用性能测试来测量处理时间。
出于伟大的正义,我将在下面复制那里的代码:
- (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;
}
我倾向于使用(Swift)的解决方案
import UIKit
func ==(lhs: UIImage, rhs: UIImage) -> Bool
{
guard let data1 = UIImagePNGRepresentation(lhs),
data2 = UIImagePNGRepresentation(rhs)
else { return false }
return data1.isEqual(data2)
}
将图像转换为JPG / PNG,或依赖可访问性标识符要么是昂贵的操作,要么容易出现故障和失败。
在这里,我遵循苹果提供的建议,位于以下链接:
isEqual(:)方法是唯一可靠的方法来确定两个图像是否包含相同的图像数据。您创建的图像对象可能彼此不同,即使您使用相同的缓存图像数据进行初始化也是如此。唯一确定它们的等式是使用 isEqual(:) 方法,它比较实际的图像数据。程序清单1说明了比较图像的正确和错误方式。
为了简化事情,我创建了以下扩展程序,用于执行比较,以便避免转换第一个图像的问题:
import UIKit
extension UIImage {
func isEqual(to image: UIImage) -> Bool {
return isEqual(image)
}
}
有了这个,我现在可以设置一个示例来比较一对图像:
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
isEqual
方法并不能正确比较两个 UIImage 实例的数据,即使这两个实例包含着相同的数据,也会返回 false。将 UIImage 实例转换成 PNG 图像,并比较它们的原始数据(就像 Simon 的回答中所做的那样),是我发现的唯一正确比较图片的方法。 - BinaryNateFacebook的比较算法的Swift 4.x版本:
/// 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
}
我对Mark的回答进行了一些修改,并使用了Data和elementsEqual而不是NSData和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)
}
}
Swift 3
有两种方法。如下:
1)使用isEqual()函数。
self.image?.isEqual(UIImage(named: "add-image"))
2) 使用 accessibilityIdentifier
将 accessibilityIdentifier 设置为图片名称。
myImageView.image?.accessibilityIdentifier = "add-image"
extension UIImageView
{
func getFileName() -> String? {
// First set accessibilityIdentifier of image before calling.
let imgName = self.image?.accessibilityIdentifier
return imgName
}
}
最后,方法调用的识别方式
myImageView.getFileName()