-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from alexey1312/feature/addMacOSPlatform
add mac os platform
- Loading branch information
Showing
7 changed files
with
242 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
#if os(macOS) | ||
import AVFoundation | ||
import Cocoa | ||
|
||
extension NSImage { | ||
func heicData(compressionQuality: CompressionQuality = .lossless) -> Data? { | ||
let data = NSMutableData() | ||
|
||
guard let imageDestination = CGImageDestinationCreateWithData( | ||
data, AVFileType.heic as CFString, 1, nil | ||
) | ||
else { return nil } | ||
|
||
guard let cgImage = cgImage(forProposedRect: nil, | ||
context: nil, | ||
hints: nil) | ||
else { return nil } | ||
|
||
let options: NSDictionary = [ | ||
kCGImageDestinationLossyCompressionQuality: compressionQuality.value | ||
] | ||
|
||
CGImageDestinationAddImage(imageDestination, cgImage, options) | ||
|
||
guard CGImageDestinationFinalize(imageDestination) else { return nil } | ||
|
||
return data as Data | ||
} | ||
} | ||
#endif | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
#if os(macOS) | ||
import Cocoa | ||
import XCTest | ||
@testable import SnapshotTesting | ||
|
||
public extension Diffing where Value == NSImage { | ||
/// A pixel-diffing strategy for NSImage's which requires a 100% match. | ||
static let imageHEIC = Diffing.imageHEIC(precision: 1, compressionQuality: .lossless) | ||
|
||
/// A pixel-diffing strategy for NSImage that allows customizing how precise the matching must be. | ||
/// | ||
/// - Parameter precision: A value between 0 and 1, where 1 means the images must match 100% of their pixels. | ||
/// - Returns: A new diffing strategy. | ||
static func imageHEIC(precision: Float, compressionQuality: CompressionQuality = .lossless) -> Diffing { | ||
return .init( | ||
toData: { NSImageHEICRepresentation($0, compressionQuality: compressionQuality)! }, | ||
fromData: { NSImage(data: $0)! } | ||
) { old, new in | ||
guard !compare(old, new, precision: precision, compressionQuality: compressionQuality) | ||
else { return nil } | ||
let difference = diffNSImage(old, new) | ||
let message = new.size == old.size | ||
? "Newly-taken snapshot does not match reference." | ||
: "Newly-taken snapshot@\(new.size) does not match reference@\(old.size)." | ||
return ( | ||
message, | ||
[XCTAttachment(image: old), XCTAttachment(image: new), XCTAttachment(image: difference)] | ||
) | ||
} | ||
} | ||
} | ||
|
||
public extension Snapshotting where Value == NSImage, Format == NSImage { | ||
/// A snapshot strategy for comparing images based on pixel equality. | ||
static var imageHEIC: Snapshotting { | ||
return .imageHEIC(precision: 1) | ||
} | ||
|
||
/// A snapshot strategy for comparing images based on pixel equality. | ||
/// | ||
/// - Parameter precision: The percentage of pixels that must match. | ||
static func imageHEIC(precision: Float) -> Snapshotting { | ||
return .init(pathExtension: "heic", diffing: .imageHEIC(precision: precision)) | ||
} | ||
} | ||
|
||
private func NSImageHEICRepresentation(_ image: NSImage, compressionQuality: CompressionQuality) -> Data? { | ||
return image.heicData(compressionQuality: compressionQuality) | ||
} | ||
|
||
private func compare( | ||
_ old: NSImage, | ||
_ new: NSImage, | ||
precision: Float, | ||
compressionQuality: CompressionQuality | ||
) -> Bool { | ||
guard let oldCgImage = old.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return false } | ||
guard let newCgImage = new.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return false } | ||
guard oldCgImage.width != 0 else { return false } | ||
guard newCgImage.width != 0 else { return false } | ||
guard oldCgImage.width == newCgImage.width else { return false } | ||
guard oldCgImage.height != 0 else { return false } | ||
guard newCgImage.height != 0 else { return false } | ||
guard oldCgImage.height == newCgImage.height else { return false } | ||
guard let oldContext = context(for: oldCgImage) else { return false } | ||
guard let newContext = context(for: newCgImage) else { return false } | ||
guard let oldData = oldContext.data else { return false } | ||
guard let newData = newContext.data else { return false } | ||
let byteCount = oldContext.height * oldContext.bytesPerRow | ||
if memcmp(oldData, newData, byteCount) == 0 { return true } | ||
let newer = NSImage(data: NSImageHEICRepresentation(new, compressionQuality: compressionQuality)!)! | ||
guard let newerCgImage = newer.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return false } | ||
guard let newerContext = context(for: newerCgImage) else { return false } | ||
guard let newerData = newerContext.data else { return false } | ||
if memcmp(oldData, newerData, byteCount) == 0 { return true } | ||
if precision >= 1 { return false } | ||
let oldRep = NSBitmapImageRep(cgImage: oldCgImage) | ||
let newRep = NSBitmapImageRep(cgImage: newerCgImage) | ||
var differentPixelCount = 0 | ||
let pixelCount = oldRep.pixelsWide * oldRep.pixelsHigh | ||
let threshold = (1 - precision) * Float(pixelCount) | ||
let p1: UnsafeMutablePointer<UInt8> = oldRep.bitmapData! | ||
let p2: UnsafeMutablePointer<UInt8> = newRep.bitmapData! | ||
for offset in 0 ..< pixelCount * 4 { | ||
if p1[offset] != p2[offset] { | ||
differentPixelCount += 1 | ||
} | ||
if Float(differentPixelCount) > threshold { return false } | ||
} | ||
return true | ||
} | ||
|
||
private func context(for cgImage: CGImage) -> CGContext? { | ||
guard | ||
let space = cgImage.colorSpace, | ||
let context = CGContext( | ||
data: nil, | ||
width: cgImage.width, | ||
height: cgImage.height, | ||
bitsPerComponent: cgImage.bitsPerComponent, | ||
bytesPerRow: cgImage.bytesPerRow, | ||
space: space, | ||
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue | ||
) | ||
else { return nil } | ||
|
||
context.draw(cgImage, in: CGRect(x: 0, y: 0, width: cgImage.width, height: cgImage.height)) | ||
return context | ||
} | ||
|
||
private func diffNSImage(_ old: NSImage, _ new: NSImage) -> NSImage { | ||
let oldCiImage = CIImage(cgImage: old.cgImage(forProposedRect: nil, context: nil, hints: nil)!) | ||
let newCiImage = CIImage(cgImage: new.cgImage(forProposedRect: nil, context: nil, hints: nil)!) | ||
let differenceFilter = CIFilter(name: "CIDifferenceBlendMode")! | ||
differenceFilter.setValue(oldCiImage, forKey: kCIInputImageKey) | ||
differenceFilter.setValue(newCiImage, forKey: kCIInputBackgroundImageKey) | ||
let maxSize = CGSize( | ||
width: max(old.size.width, new.size.width), | ||
height: max(old.size.height, new.size.height) | ||
) | ||
let rep = NSCIImageRep(ciImage: differenceFilter.outputImage!) | ||
let difference = NSImage(size: maxSize) | ||
difference.addRepresentation(rep) | ||
return difference | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
#if os(macOS) | ||
import Cocoa | ||
@testable import SnapshotTesting | ||
|
||
public extension Snapshotting where Value == NSView, Format == NSImage { | ||
/// A snapshot strategy for comparing views based on pixel equality. | ||
static var imageHEIC: Snapshotting { | ||
return .imageHEIC() | ||
} | ||
|
||
/// A snapshot strategy for comparing views based on pixel equality. | ||
/// | ||
/// - Parameters: | ||
/// - precision: The percentage of pixels that must match. | ||
/// - size: A view size override. | ||
static func imageHEIC(precision: Float = 1, size: CGSize? = nil) -> Snapshotting { | ||
return SimplySnapshotting.imageHEIC(precision: precision).asyncPullback { view in | ||
let initialSize = view.frame.size | ||
if let size = size { view.frame.size = size } | ||
guard view.frame.width > 0, view.frame.height > 0 else { | ||
fatalError("View not renderable to image at size \(view.frame.size)") | ||
} | ||
return view.snapshot ?? Async { callback in | ||
addImagesForRenderedViews(view).sequence().run { views in | ||
let bitmapRep = view.bitmapImageRepForCachingDisplay(in: view.bounds)! | ||
view.cacheDisplay(in: view.bounds, to: bitmapRep) | ||
let image = NSImage(size: view.bounds.size) | ||
image.addRepresentation(bitmapRep) | ||
callback(image) | ||
views.forEach { $0.removeFromSuperview() } | ||
view.frame.size = initialSize | ||
} | ||
} | ||
} | ||
} | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
#if os(macOS) | ||
import Cocoa | ||
@testable import SnapshotTesting | ||
|
||
public extension Snapshotting where Value == NSViewController, Format == NSImage { | ||
/// A snapshot strategy for comparing view controller views based on pixel equality. | ||
static var imageHEIC: Snapshotting { | ||
return .imageHEIC() | ||
} | ||
|
||
/// A snapshot strategy for comparing view controller views based on pixel equality. | ||
/// | ||
/// - Parameters: | ||
/// - precision: The percentage of pixels that must match. | ||
/// - size: A view size override. | ||
static func imageHEIC(precision: Float = 1, size: CGSize? = nil) -> Snapshotting { | ||
return Snapshotting<NSView, NSImage>.imageHEIC(precision: precision, size: size).pullback { $0.view } | ||
} | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file added
BIN
+5.48 KB
...s/SnapshotTestingHEICTests/__Snapshots__/SnapshotTestingHEICTests/test_HEIC_NSView.1.heic
Binary file not shown.