Skip to content

Commit adbcf31

Browse files
committed
Apply fix from pointfreeco#446
Credit to @dflems
1 parent 32ebb83 commit adbcf31

File tree

1 file changed

+31
-21
lines changed

1 file changed

+31
-21
lines changed

Sources/SnapshotTesting/Snapshotting/UIImage.swift

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,24 @@ import XCTest
44

55
extension Diffing where Value == UIImage {
66
/// A pixel-diffing strategy for UIImage's which requires a 100% match.
7-
public static let image = Diffing.image(precision: 1)
7+
public static let image = Diffing.image(precision: 1, scale: nil)
88

99
/// A pixel-diffing strategy for UIImage that allows customizing how precise the matching must be.
1010
///
1111
/// - Parameter precision: A value between 0 and 1, where 1 means the images must match 100% of their pixels.
12+
/// - Parameter scale: Scale to use when loading the reference image from disk. If `nil` or the `UITraitCollection`s default value of `0.0`, the screens scale is used.
1213
/// - Returns: A new diffing strategy.
13-
public static func image(precision: Float) -> Diffing {
14+
public static func image(precision: Float, scale: CGFloat?) -> Diffing {
15+
let imageScale: CGFloat
16+
if let scale = scale, scale != 0.0 {
17+
imageScale = scale
18+
} else {
19+
imageScale = UIScreen.main.scale
20+
}
21+
1422
return Diffing(
1523
toData: { $0.pngData() ?? emptyImage().pngData()! },
16-
fromData: { UIImage(data: $0, scale: UIScreen.main.scale)! }
24+
fromData: { UIImage(data: $0, scale: imageScale)! }
1725
) { old, new in
1826
guard !compare(old, new, precision: precision) else { return nil }
1927
let difference = SnapshotTesting.diff(old, new)
@@ -32,8 +40,8 @@ extension Diffing where Value == UIImage {
3240
)
3341
}
3442
}
35-
36-
43+
44+
3745
/// Used when the image size has no width or no height to generated the default empty image
3846
private static func emptyImage() -> UIImage {
3947
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 400, height: 80))
@@ -48,20 +56,26 @@ extension Diffing where Value == UIImage {
4856
extension Snapshotting where Value == UIImage, Format == UIImage {
4957
/// A snapshot strategy for comparing images based on pixel equality.
5058
public static var image: Snapshotting {
51-
return .image(precision: 1)
59+
return .image(precision: 1, scale: nil)
5260
}
5361

5462
/// A snapshot strategy for comparing images based on pixel equality.
5563
///
5664
/// - Parameter precision: The percentage of pixels that must match.
57-
public static func image(precision: Float) -> Snapshotting {
65+
/// - Parameter scale: The scale of the reference image stored on disk.
66+
public static func image(precision: Float, scale: CGFloat?) -> Snapshotting {
5867
return .init(
5968
pathExtension: "png",
60-
diffing: .image(precision: precision)
69+
diffing: .image(precision: precision, scale: scale)
6170
)
6271
}
6372
}
6473

74+
// remap snapshot & reference to same colorspace
75+
let imageContextColorSpace = CGColorSpace(name: CGColorSpace.sRGB)
76+
let imageContextBitsPerComponent = 8
77+
let imageContextBytesPerPixel = 4
78+
6579
private func compare(_ old: UIImage, _ new: UIImage, precision: Float) -> Bool {
6680
guard let oldCgImage = old.cgImage else { return false }
6781
guard let newCgImage = new.cgImage else { return false }
@@ -71,23 +85,18 @@ private func compare(_ old: UIImage, _ new: UIImage, precision: Float) -> Bool {
7185
guard oldCgImage.height != 0 else { return false }
7286
guard newCgImage.height != 0 else { return false }
7387
guard oldCgImage.height == newCgImage.height else { return false }
74-
// Values between images may differ due to padding to multiple of 64 bytes per row,
75-
// because of that a freshly taken view snapshot may differ from one stored as PNG.
76-
// At this point we're sure that size of both images is the same, so we can go with minimal `bytesPerRow` value
77-
// and use it to create contexts.
78-
let minBytesPerRow = min(oldCgImage.bytesPerRow, newCgImage.bytesPerRow)
79-
let byteCount = minBytesPerRow * oldCgImage.height
8088

89+
let byteCount = imageContextBytesPerPixel * oldCgImage.width * oldCgImage.height
8190
var oldBytes = [UInt8](repeating: 0, count: byteCount)
82-
guard let oldContext = context(for: oldCgImage, bytesPerRow: minBytesPerRow, data: &oldBytes) else { return false }
91+
guard let oldContext = context(for: oldCgImage, data: &oldBytes) else { return false }
8392
guard let oldData = oldContext.data else { return false }
84-
if let newContext = context(for: newCgImage, bytesPerRow: minBytesPerRow), let newData = newContext.data {
93+
if let newContext = context(for: newCgImage), let newData = newContext.data {
8594
if memcmp(oldData, newData, byteCount) == 0 { return true }
8695
}
8796
let newer = UIImage(data: new.pngData()!)!
8897
guard let newerCgImage = newer.cgImage else { return false }
8998
var newerBytes = [UInt8](repeating: 0, count: byteCount)
90-
guard let newerContext = context(for: newerCgImage, bytesPerRow: minBytesPerRow, data: &newerBytes) else { return false }
99+
guard let newerContext = context(for: newerCgImage, data: &newerBytes) else { return false }
91100
guard let newerData = newerContext.data else { return false }
92101
if memcmp(oldData, newerData, byteCount) == 0 { return true }
93102
if precision >= 1 { return false }
@@ -100,16 +109,17 @@ private func compare(_ old: UIImage, _ new: UIImage, precision: Float) -> Bool {
100109
return true
101110
}
102111

103-
private func context(for cgImage: CGImage, bytesPerRow: Int, data: UnsafeMutableRawPointer? = nil) -> CGContext? {
112+
private func context(for cgImage: CGImage, data: UnsafeMutableRawPointer? = nil) -> CGContext? {
113+
let bytesPerRow = cgImage.width * imageContextBytesPerPixel
104114
guard
105-
let space = cgImage.colorSpace,
115+
let colorSpace = imageContextColorSpace,
106116
let context = CGContext(
107117
data: data,
108118
width: cgImage.width,
109119
height: cgImage.height,
110-
bitsPerComponent: cgImage.bitsPerComponent,
120+
bitsPerComponent: imageContextBitsPerComponent,
111121
bytesPerRow: bytesPerRow,
112-
space: space,
122+
space: colorSpace,
113123
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
114124
)
115125
else { return nil }

0 commit comments

Comments
 (0)