@@ -4,16 +4,24 @@ import XCTest
4
4
5
5
extension Diffing where Value == UIImage {
6
6
/// 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 )
8
8
9
9
/// A pixel-diffing strategy for UIImage that allows customizing how precise the matching must be.
10
10
///
11
11
/// - 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.
12
13
/// - 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
+
14
22
return Diffing (
15
23
toData: { $0. pngData ( ) ?? emptyImage ( ) . pngData ( ) ! } ,
16
- fromData: { UIImage ( data: $0, scale: UIScreen . main . scale ) ! }
24
+ fromData: { UIImage ( data: $0, scale: imageScale ) ! }
17
25
) { old, new in
18
26
guard !compare( old, new, precision: precision) else { return nil }
19
27
let difference = SnapshotTesting . diff ( old, new)
@@ -32,8 +40,8 @@ extension Diffing where Value == UIImage {
32
40
)
33
41
}
34
42
}
35
-
36
-
43
+
44
+
37
45
/// Used when the image size has no width or no height to generated the default empty image
38
46
private static func emptyImage( ) -> UIImage {
39
47
let label = UILabel ( frame: CGRect ( x: 0 , y: 0 , width: 400 , height: 80 ) )
@@ -48,20 +56,26 @@ extension Diffing where Value == UIImage {
48
56
extension Snapshotting where Value == UIImage , Format == UIImage {
49
57
/// A snapshot strategy for comparing images based on pixel equality.
50
58
public static var image : Snapshotting {
51
- return . image( precision: 1 )
59
+ return . image( precision: 1 , scale : nil )
52
60
}
53
61
54
62
/// A snapshot strategy for comparing images based on pixel equality.
55
63
///
56
64
/// - 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 {
58
67
return . init(
59
68
pathExtension: " png " ,
60
- diffing: . image( precision: precision)
69
+ diffing: . image( precision: precision, scale : scale )
61
70
)
62
71
}
63
72
}
64
73
74
+ // remap snapshot & reference to same colorspace
75
+ let imageContextColorSpace = CGColorSpace ( name: CGColorSpace . sRGB)
76
+ let imageContextBitsPerComponent = 8
77
+ let imageContextBytesPerPixel = 4
78
+
65
79
private func compare( _ old: UIImage , _ new: UIImage , precision: Float ) -> Bool {
66
80
guard let oldCgImage = old. cgImage else { return false }
67
81
guard let newCgImage = new. cgImage else { return false }
@@ -71,23 +85,18 @@ private func compare(_ old: UIImage, _ new: UIImage, precision: Float) -> Bool {
71
85
guard oldCgImage. height != 0 else { return false }
72
86
guard newCgImage. height != 0 else { return false }
73
87
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
80
88
89
+ let byteCount = imageContextBytesPerPixel * oldCgImage. width * oldCgImage. height
81
90
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 }
83
92
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 {
85
94
if memcmp ( oldData, newData, byteCount) == 0 { return true }
86
95
}
87
96
let newer = UIImage ( data: new. pngData ( ) !) !
88
97
guard let newerCgImage = newer. cgImage else { return false }
89
98
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 }
91
100
guard let newerData = newerContext. data else { return false }
92
101
if memcmp ( oldData, newerData, byteCount) == 0 { return true }
93
102
if precision >= 1 { return false }
@@ -100,16 +109,17 @@ private func compare(_ old: UIImage, _ new: UIImage, precision: Float) -> Bool {
100
109
return true
101
110
}
102
111
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
104
114
guard
105
- let space = cgImage . colorSpace ,
115
+ let colorSpace = imageContextColorSpace ,
106
116
let context = CGContext (
107
117
data: data,
108
118
width: cgImage. width,
109
119
height: cgImage. height,
110
- bitsPerComponent: cgImage . bitsPerComponent ,
120
+ bitsPerComponent: imageContextBitsPerComponent ,
111
121
bytesPerRow: bytesPerRow,
112
- space: space ,
122
+ space: colorSpace ,
113
123
bitmapInfo: CGImageAlphaInfo . premultipliedLast. rawValue
114
124
)
115
125
else { return nil }
0 commit comments