From aa9aacce4c282110c70eae3d7d45a4c232d64297 Mon Sep 17 00:00:00 2001 From: Eric Jensen Date: Fri, 21 Oct 2022 20:07:54 -0400 Subject: [PATCH] Normalize color spaces before comparing images Use the sRGB converted snapshot when doing the perceptual comparison This reduces the chances of failures when comparing snapshots in different color spaces --- .../Snapshotting/NSImage.swift | 6 +- .../Snapshotting/UIImage.swift | 9 ++- .../SnapshotTestingTests.swift | 57 +++++++++++------- .../testImageColorSpaceConversion.p3.png | Bin 0 -> 1616 bytes .../testImageColorSpaceConversion.srgb.png | Bin 0 -> 1349 bytes 5 files changed, 45 insertions(+), 27 deletions(-) create mode 100644 Tests/SnapshotTestingTests/__Snapshots__/SnapshotTestingTests/testImageColorSpaceConversion.p3.png create mode 100644 Tests/SnapshotTestingTests/__Snapshots__/SnapshotTestingTests/testImageColorSpaceConversion.srgb.png diff --git a/Sources/SnapshotTesting/Snapshotting/NSImage.swift b/Sources/SnapshotTesting/Snapshotting/NSImage.swift index 620556e6c..e77ae6a52 100644 --- a/Sources/SnapshotTesting/Snapshotting/NSImage.swift +++ b/Sources/SnapshotTesting/Snapshotting/NSImage.swift @@ -94,8 +94,8 @@ private func compare(_ old: NSImage, _ new: NSImage, precision: Float, perceptua } if perceptualPrecision < 1, #available(macOS 10.13, *) { return perceptuallyCompare( - CIImage(cgImage: oldCgImage), - CIImage(cgImage: newCgImage), + CIImage(cgImage: oldContext.makeImage() ?? oldCgImage), + CIImage(cgImage: newerContext.makeImage() ?? newerCgImage), pixelPrecision: precision, perceptualPrecision: perceptualPrecision ) @@ -119,7 +119,7 @@ private func compare(_ old: NSImage, _ new: NSImage, precision: Float, perceptua private func context(for cgImage: CGImage) -> CGContext? { guard - let space = cgImage.colorSpace, + let space = CGColorSpace(name: CGColorSpace.sRGB) ?? cgImage.colorSpace, let context = CGContext( data: nil, width: cgImage.width, diff --git a/Sources/SnapshotTesting/Snapshotting/UIImage.swift b/Sources/SnapshotTesting/Snapshotting/UIImage.swift index e6ac25440..6421aa90d 100644 --- a/Sources/SnapshotTesting/Snapshotting/UIImage.swift +++ b/Sources/SnapshotTesting/Snapshotting/UIImage.swift @@ -94,7 +94,10 @@ private func compare(_ old: UIImage, _ new: UIImage, precision: Float, perceptua let pixelCount = oldCgImage.width * oldCgImage.height let byteCount = imageContextBytesPerPixel * pixelCount var oldBytes = [UInt8](repeating: 0, count: byteCount) - guard let oldData = context(for: oldCgImage, data: &oldBytes)?.data else { + guard + let oldContext = context(for: oldCgImage, data: &oldBytes), + let oldData = oldContext.data + else { return "Reference image's data could not be loaded." } if let newContext = context(for: newCgImage), let newData = newContext.data { @@ -115,8 +118,8 @@ private func compare(_ old: UIImage, _ new: UIImage, precision: Float, perceptua } if perceptualPrecision < 1, #available(iOS 11.0, tvOS 11.0, *) { return perceptuallyCompare( - CIImage(cgImage: oldCgImage), - CIImage(cgImage: newCgImage), + CIImage(cgImage: oldContext.makeImage() ?? oldCgImage), + CIImage(cgImage: newerContext.makeImage() ?? newerCgImage), pixelPrecision: precision, perceptualPrecision: perceptualPrecision ) diff --git a/Tests/SnapshotTestingTests/SnapshotTestingTests.swift b/Tests/SnapshotTestingTests/SnapshotTestingTests.swift index 570f24240..b5e8e1340 100644 --- a/Tests/SnapshotTestingTests/SnapshotTestingTests.swift +++ b/Tests/SnapshotTestingTests/SnapshotTestingTests.swift @@ -20,6 +20,10 @@ import XCTest @testable import SnapshotTesting final class SnapshotTestingTests: XCTestCase { + private let fixturesURL = URL(fileURLWithPath: #file, isDirectory: false) + .deletingLastPathComponent() + .appendingPathComponent("__Fixtures__", isDirectory: true) + override func setUp() { super.setUp() diffTool = "ksdiff" @@ -291,9 +295,9 @@ final class SnapshotTestingTests: XCTestCase { func testImagePrecision() throws { #if os(iOS) || os(tvOS) || os(macOS) - let imageURL = URL(fileURLWithPath: String(#file), isDirectory: false) - .deletingLastPathComponent() - .appendingPathComponent("__Fixtures__/testImagePrecision.reference.png") + let imageURL = fixturesURL + .appendingPathComponent(sanitizePathComponent(#function), isDirectory: false) + .appendingPathExtension("reference.png") #if os(iOS) || os(tvOS) let image = try XCTUnwrap(UIImage(contentsOfFile: imageURL.path)) #elseif os(macOS) @@ -301,9 +305,30 @@ final class SnapshotTestingTests: XCTestCase { #endif assertSnapshot(matching: image, as: .image(precision: 0.995), named: "exact") - if #available(iOS 11.0, tvOS 11.0, macOS 10.13, *) { - assertSnapshot(matching: image, as: .image(perceptualPrecision: 0.98), named: "perceptual") - } + assertSnapshot(matching: image, as: .image(perceptualPrecision: 0.98), named: "perceptual") + #endif + } + + func testImageColorSpaceConversion() throws { + #if os(iOS) || os(tvOS) || os(macOS) + let fileName = URL(fileURLWithPath: #file, isDirectory: false).deletingPathExtension().lastPathComponent + let baseImageURL = URL(fileURLWithPath: #file, isDirectory: false) + .deletingLastPathComponent() + .appendingPathComponent("__Snapshots__", isDirectory: true) + .appendingPathComponent(fileName, isDirectory: true) + .appendingPathComponent(sanitizePathComponent(#function), isDirectory: false) + let p3ImageURL = baseImageURL.appendingPathExtension("p3.png") + let sRGBImageURL = baseImageURL.appendingPathExtension("srgb.png") + #if os(iOS) || os(tvOS) + let p3Image = try XCTUnwrap(UIImage(contentsOfFile: p3ImageURL.path)) + let sRGBImage = try XCTUnwrap(UIImage(contentsOfFile: sRGBImageURL.path)) + #elseif os(macOS) + let p3Image = try XCTUnwrap(NSImage(byReferencing: p3ImageURL)) + let sRGBImage = try XCTUnwrap(NSImage(byReferencing: sRGBImageURL)) + #endif + + assertSnapshot(matching: p3Image, as: .image(perceptualPrecision: 0.99), named: "srgb") + assertSnapshot(matching: sRGBImage, as: .image(perceptualPrecision: 0.99), named: "p3") #endif } @@ -319,9 +344,7 @@ final class SnapshotTestingTests: XCTestCase { // sphereNode.position = SCNVector3Zero // scene.rootNode.addChildNode(sphereNode) // -// sphereGeometry.firstMaterial?.diffuse.contents = URL(fileURLWithPath: String(#file), isDirectory: false) -// .deletingLastPathComponent() -// .appendingPathComponent("__Fixtures__/earth.png") +// sphereGeometry.firstMaterial?.diffuse.contents = fixturesURL.appendingPathComponent("earth.png", isDirectory: false) // // let cameraNode = SCNNode() // cameraNode.camera = SCNCamera() @@ -1029,9 +1052,7 @@ final class SnapshotTestingTests: XCTestCase { func testWebView() throws { #if os(iOS) || os(macOS) - let fixtureUrl = URL(fileURLWithPath: String(#file), isDirectory: false) - .deletingLastPathComponent() - .appendingPathComponent("__Fixtures__/pointfree.html") + let fixtureUrl = fixturesURL.appendingPathComponent("pointfree.html", isDirectory: false) let html = try String(contentsOf: fixtureUrl) let webView = WKWebView() webView.loadHTMLString(html, baseURL: nil) @@ -1080,9 +1101,7 @@ final class SnapshotTestingTests: XCTestCase { let label = UILabel() label.text = "Hello, Blob!" - let fixtureUrl = URL(fileURLWithPath: String(#file), isDirectory: false) - .deletingLastPathComponent() - .appendingPathComponent("__Fixtures__/pointfree.html") + let fixtureUrl = fixturesURL.appendingPathComponent("pointfree.html", isDirectory: false) let html = try String(contentsOf: fixtureUrl) let webView = WKWebView() webView.loadHTMLString(html, baseURL: nil) @@ -1112,9 +1131,7 @@ final class SnapshotTestingTests: XCTestCase { let webView = WKWebView() webView.navigationDelegate = manipulatingWKWebViewNavigationDelegate - let fixtureUrl = URL(fileURLWithPath: String(#file), isDirectory: false) - .deletingLastPathComponent() - .appendingPathComponent("__Fixtures__/pointfree.html") + let fixtureUrl = fixturesURL.appendingPathComponent("pointfree.html", isDirectory: false) let html = try String(contentsOf: fixtureUrl) webView.loadHTMLString(html, baseURL: nil) if !ProcessInfo.processInfo.environment.keys.contains("GITHUB_WORKFLOW") { @@ -1159,9 +1176,7 @@ final class SnapshotTestingTests: XCTestCase { let webView = WKWebView() webView.navigationDelegate = cancellingWKWebViewNavigationDelegate - let fixtureUrl = URL(fileURLWithPath: String(#file), isDirectory: false) - .deletingLastPathComponent() - .appendingPathComponent("__Fixtures__/pointfree.html") + let fixtureUrl = fixturesURL.appendingPathComponent("pointfree.html", isDirectory: false) let html = try String(contentsOf: fixtureUrl) webView.loadHTMLString(html, baseURL: nil) if !ProcessInfo.processInfo.environment.keys.contains("GITHUB_WORKFLOW") { diff --git a/Tests/SnapshotTestingTests/__Snapshots__/SnapshotTestingTests/testImageColorSpaceConversion.p3.png b/Tests/SnapshotTestingTests/__Snapshots__/SnapshotTestingTests/testImageColorSpaceConversion.p3.png new file mode 100644 index 0000000000000000000000000000000000000000..7313a1cb7beaef33317dcb413bec4158b9617c0c GIT binary patch literal 1616 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9Sn@U@Xmab`HpPc6ZLt$u9~nNK8(3 z$t*6&NvsSoW?;~mSUO>^_u&AMqyDRdE?9N&$;udYI$ycORaTgxE#hhr!n!(mRvl+< zjf!Sap>Vk75BZ3Lkp&w%LJA5C1YFn!@6NH(+>udnZ+gfrrF+Mp{yffy2bbzGx zHztjj(n}Cz)4NjFZf(ACZ~oQr=|^Q=1)fP1R+H!o2yA3weOUkJa-V&lZc%tb zfL=e7&M6m9LE%kDK3`avC}dQ~;rMF8j^fvK{A+)o%*}l-IwQB>oc^s_PRtf_7f!x< z`&I>ugcL*GD<9Jk_g4$QJu5o!o6}$hQ`>uu&56$rDVA5)^Sr2j_$Qg?gDg|o)X(dc z&%Kpuo!_Yamt9;lYfmtn<7r?#>`IOBO!M_+&;rK}8>0{dGmymygba*Q46I-_149_2 zG@KpBr~y^O1XN?l03;bifjF%_lYs@Qz6MBxm^vT=Mh9e8q~_!%mgE;H1Qg{f80#76 z83N^*C$NCc5CO86EntG1!LfiD!3N4P7#SEDSs5Bw85t@Vnpqi{SQ(fzXxJWJ50v68 z@Q5sCV9-+rVaAH3_GLhGXJm#%ltlRYSS9D@>LsS+C#C9D=?Nk^)#sNw%$0gl~X?bAC~(f*H`o21<5Z3JMA~MJZ`kK`w4k z?LeNbQbtKhft9{~d3m{Bxv^e;QM$gNrKP35fswwEkuFe$ZgFK^Nn(X=Ua>O75STeG zsl~}fnFS@8`FRQ;a}$&DOG|8(lt3220mPlD6`2T|@`|C}0(wv%B%^PrXP}QwTS;ab z4s9SAoZ5h41osw-Wfh495KZ8~fVj~HW`cb5SbL1T3b2vkXK(k{WEf^NT|A^K42FM=mQZ}s3wdKJNcz?*m%L`0mo`Pzmw;k`FI^ZU)ro0<1GzjSdtJ{1Rm z@!VpzfM<-Y*ck4gCq6yFBf6T=GXU?O9{6jF;x}JeEa(7tPXKH@1o(rhjUNE(62Q-8 z0P+%G(th>vs)~jkTFTXQ9ZsPfgE8(PL{Q@J58M&M?3>G_LoMfx)0 zK#>%M$P!UxUY;e&43XyG{;wDR(6rxPEV}^0G+Uhi=Im{N$c|Yoc_sZasaOrcs9Gyj z@EbPs1gcL^Y*5eO{YJg%5J;#W8Kq zw3HfYnuwrI06syNgzNL;Y$mUGQEDpZp@l#pr= z9fT?G6((c|5n1s`fx1?GolVU3rs-*lDh^W*vPylLmSS}XNMY?Dqdb%m)($eRI3~q0 zXQ{CY1QEiz4qg@6q=zQI{j35lzAG(`P3pc;Ex!G2=Quvw$y_#5JaV*BeM^6T+1Mw2 ee;&CsJ~0ZL8E*6S+ShINv~%Z|vhU6M literal 0 HcmV?d00001