Skip to content

Commit dfb61b4

Browse files
committed
Apply changes from ST-NNNN.
This PR applies the changes from [ST-NNNN](swiftlang/swift-evolution#2985). It merges `AttachableAsCGImage` and `AttachableAsIWICBitmapSource` into a single `AttachableAsImage` protocol and it adjusts the interfaces of `AttachableImageFormat` and `Attachment where AttachableValue: AttachableAsImage`.
1 parent 16682c9 commit dfb61b4

22 files changed

+361
-442
lines changed

Sources/Overlays/_Testing_AppKit/Attachments/NSImage+AttachableAsCGImage.swift renamed to Sources/Overlays/_Testing_AppKit/Attachments/NSImage+AttachableAsImage.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,21 @@ extension NSImageRep {
3636
/// @Metadata {
3737
/// @Available(Swift, introduced: 6.3)
3838
/// }
39-
extension NSImage: AttachableAsCGImage {
39+
extension NSImage: AttachableAsImage, AttachableAsCGImage {
4040
/// @Metadata {
4141
/// @Available(Swift, introduced: 6.3)
4242
/// }
43-
public var attachableCGImage: CGImage {
43+
package var attachableCGImage: CGImage {
4444
get throws {
45-
let ctm = AffineTransform(scale: _attachmentScaleFactor) as NSAffineTransform
45+
let ctm = AffineTransform(scale: attachmentScaleFactor) as NSAffineTransform
4646
guard let result = cgImage(forProposedRect: nil, context: nil, hints: [.ctm: ctm]) else {
4747
throw ImageAttachmentError.couldNotCreateCGImage
4848
}
4949
return result
5050
}
5151
}
5252

53-
public var _attachmentScaleFactor: CGFloat {
53+
package var attachmentScaleFactor: CGFloat {
5454
let maxRepWidth = representations.lazy
5555
.map { CGFloat($0.pixelsWide) / $0.size.width }
5656
.filter { $0 > 0.0 }

Sources/Overlays/_Testing_AppKit/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
if (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
1010
add_library(_Testing_AppKit
11-
Attachments/NSImage+AttachableAsCGImage.swift
11+
Attachments/NSImage+AttachableAsImage.swift
1212
ReexportTesting.swift)
1313

1414
target_link_libraries(_Testing_AppKit PUBLIC

Sources/Overlays/_Testing_CoreGraphics/Attachments/AttachableAsCGImage.swift

Lines changed: 47 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,35 +9,18 @@
99
//
1010

1111
#if SWT_TARGET_OS_APPLE && canImport(CoreGraphics)
12-
public import CoreGraphics
12+
package import CoreGraphics
1313
private import ImageIO
14+
private import UniformTypeIdentifiers
1415

1516
/// A protocol describing images that can be converted to instances of
16-
/// [`Attachment`](https://developer.apple.com/documentation/testing/attachment).
17+
/// [`Attachment`](https://developer.apple.com/documentation/testing/attachment)
18+
/// and which can be represented as instances of [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage).
1719
///
18-
/// Instances of types conforming to this protocol do not themselves conform to
19-
/// [`Attachable`](https://developer.apple.com/documentation/testing/attachable).
20-
/// Instead, the testing library provides additional initializers on [`Attachment`](https://developer.apple.com/documentation/testing/attachment)
21-
/// that take instances of such types and handle converting them to image data when needed.
22-
///
23-
/// You can attach instances of the following system-provided image types to a
24-
/// test:
25-
///
26-
/// | Platform | Supported Types |
27-
/// |-|-|
28-
/// | macOS | [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage), [`CIImage`](https://developer.apple.com/documentation/coreimage/ciimage), [`NSImage`](https://developer.apple.com/documentation/appkit/nsimage) |
29-
/// | iOS, watchOS, tvOS, and visionOS | [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage), [`CIImage`](https://developer.apple.com/documentation/coreimage/ciimage), [`UIImage`](https://developer.apple.com/documentation/uikit/uiimage) |
30-
/// | Windows | [`HBITMAP`](https://learn.microsoft.com/en-us/windows/win32/gdi/bitmaps), [`HICON`](https://learn.microsoft.com/en-us/windows/win32/menurc/icons), [`IWICBitmapSource`](https://learn.microsoft.com/en-us/windows/win32/api/wincodec/nn-wincodec-iwicbitmapsource) (including its subclasses declared by Windows Imaging Component) |
31-
///
32-
/// You do not generally need to add your own conformances to this protocol. If
33-
/// you have an image in another format that needs to be attached to a test,
34-
/// first convert it to an instance of one of the types above.
35-
///
36-
/// @Metadata {
37-
/// @Available(Swift, introduced: 6.3)
38-
/// }
20+
/// This protocol is not part of the public interface of the testing library. It
21+
/// encapsulates Apple-specific logic for image attachments.
3922
@available(_uttypesAPI, *)
40-
public protocol AttachableAsCGImage: _AttachableAsImage, SendableMetatype {
23+
package protocol AttachableAsCGImage: AttachableAsImage, SendableMetatype {
4124
/// An instance of `CGImage` representing this image.
4225
///
4326
/// - Throws: Any error that prevents the creation of an image.
@@ -53,38 +36,64 @@ public protocol AttachableAsCGImage: _AttachableAsImage, SendableMetatype {
5336
/// `CGImagePropertyOrientation`. The default value of this property is
5437
/// `.up`.
5538
///
56-
/// This property is not part of the public interface of the testing
57-
/// library. It may be removed in a future update.
58-
var _attachmentOrientation: UInt32 { get }
39+
/// This property is not part of the public interface of the testing library.
40+
/// It may be removed in a future update.
41+
var attachmentOrientation: UInt32 { get }
5942

6043
/// The scale factor of the image.
6144
///
6245
/// The value of this property is typically greater than `1.0` when an image
6346
/// originates from a Retina Display screenshot or similar. The default value
6447
/// of this property is `1.0`.
6548
///
66-
/// This property is not part of the public interface of the testing
67-
/// library. It may be removed in a future update.
68-
var _attachmentScaleFactor: CGFloat { get }
49+
/// This property is not part of the public interface of the testing library.
50+
/// It may be removed in a future update.
51+
var attachmentScaleFactor: CGFloat { get }
6952
}
7053

7154
@available(_uttypesAPI, *)
7255
extension AttachableAsCGImage {
73-
public var _attachmentOrientation: UInt32 {
56+
package var attachmentOrientation: UInt32 {
7457
CGImagePropertyOrientation.up.rawValue
7558
}
7659

77-
public var _attachmentScaleFactor: CGFloat {
60+
package var attachmentScaleFactor: CGFloat {
7861
1.0
7962
}
8063

81-
public func _deinitializeAttachableValue() {}
82-
}
64+
public func withUnsafeBytes<R>(as imageFormat: AttachableImageFormat, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
65+
let data = NSMutableData()
8366

84-
@available(_uttypesAPI, *)
85-
extension AttachableAsCGImage where Self: Sendable {
86-
public func _copyAttachableValue() -> Self {
87-
self
67+
// Convert the image to a CGImage.
68+
let attachableCGImage = try attachableCGImage
69+
70+
// Create the image destination.
71+
guard let dest = CGImageDestinationCreateWithData(data as CFMutableData, imageFormat.contentType.identifier as CFString, 1, nil) else {
72+
throw ImageAttachmentError.couldNotCreateImageDestination
73+
}
74+
75+
// Configure the properties of the image conversion operation.
76+
let orientation = attachmentOrientation
77+
let scaleFactor = attachmentScaleFactor
78+
let properties: [CFString: Any] = [
79+
kCGImageDestinationLossyCompressionQuality: CGFloat(imageFormat.encodingQuality),
80+
kCGImagePropertyOrientation: orientation,
81+
kCGImagePropertyDPIWidth: 72.0 * scaleFactor,
82+
kCGImagePropertyDPIHeight: 72.0 * scaleFactor,
83+
]
84+
85+
// Perform the image conversion.
86+
CGImageDestinationAddImage(dest, attachableCGImage, properties as CFDictionary)
87+
guard CGImageDestinationFinalize(dest) else {
88+
throw ImageAttachmentError.couldNotConvertImage
89+
}
90+
91+
// Pass the bits of the image out to the body. Note that we have an
92+
// NSMutableData here so we have to use slightly different API than we would
93+
// with an instance of Data.
94+
return try withExtendedLifetime(data) {
95+
try body(UnsafeRawBufferPointer(start: data.bytes, count: data.length))
96+
}
8897
}
8998
}
9099
#endif

Sources/Overlays/_Testing_CoreGraphics/Attachments/AttachableImageFormat+UTType.swift

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -15,52 +15,6 @@ public import UniformTypeIdentifiers
1515

1616
@available(_uttypesAPI, *)
1717
extension AttachableImageFormat {
18-
/// Get the content type to use when encoding the image, substituting a
19-
/// concrete type for `UTType.image` in particular.
20-
///
21-
/// - Parameters:
22-
/// - imageFormat: The image format to use, or `nil` if the developer did
23-
/// not specify one.
24-
/// - preferredName: The preferred name of the image for which a type is
25-
/// needed.
26-
///
27-
/// - Returns: An instance of `UTType` referring to a concrete image type.
28-
///
29-
/// This function is not part of the public interface of the testing library.
30-
static func computeContentType(for imageFormat: Self?, withPreferredName preferredName: String) -> UTType {
31-
guard let imageFormat else {
32-
// The developer didn't specify a type. Substitute the generic `.image`
33-
// and solve for that instead.
34-
return computeContentType(for: Self(.image, encodingQuality: 1.0), withPreferredName: preferredName)
35-
}
36-
37-
switch imageFormat.kind {
38-
case .png:
39-
return .png
40-
case .jpeg:
41-
return .jpeg
42-
case let .systemValue(contentType):
43-
let contentType = contentType as! UTType
44-
if contentType != .image {
45-
// The developer explicitly specified a type.
46-
return contentType
47-
}
48-
49-
// The developer didn't specify a concrete type, so try to derive one from
50-
// the preferred name's path extension.
51-
let pathExtension = (preferredName as NSString).pathExtension
52-
if !pathExtension.isEmpty,
53-
let contentType = UTType(filenameExtension: pathExtension, conformingTo: .image),
54-
contentType.isDeclared {
55-
return contentType
56-
}
57-
58-
// We couldn't derive a concrete type from the path extension, so pick
59-
// between PNG and JPEG based on the encoding quality.
60-
return imageFormat.encodingQuality < 1.0 ? .jpeg : .png
61-
}
62-
}
63-
6418
/// The content type corresponding to this image format.
6519
///
6620
/// For example, if this image format equals ``png``, the value of this

Sources/Overlays/_Testing_CoreGraphics/Attachments/CGImage+AttachableAsCGImage.swift renamed to Sources/Overlays/_Testing_CoreGraphics/Attachments/CGImage+AttachableAsImage.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ public import CoreGraphics
1414
/// @Metadata {
1515
/// @Available(Swift, introduced: 6.3)
1616
/// }
17-
extension CGImage: AttachableAsCGImage {
17+
extension CGImage: AttachableAsImage, AttachableAsCGImage {
1818
/// @Metadata {
1919
/// @Available(Swift, introduced: 6.3)
2020
/// }
21-
public var attachableCGImage: CGImage {
21+
package var attachableCGImage: CGImage {
2222
self
2323
}
2424
}

Sources/Overlays/_Testing_CoreGraphics/Attachments/_AttachableImageWrapper+AttachableWrapper.swift

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
public import Testing
1313
private import CoreGraphics
1414

15-
private import ImageIO
1615
private import UniformTypeIdentifiers
1716

1817
/// ## Why can't images directly conform to Attachable?
@@ -39,46 +38,47 @@ private import UniformTypeIdentifiers
3938
/// useful.)
4039

4140
@available(_uttypesAPI, *)
42-
extension _AttachableImageWrapper: Attachable, AttachableWrapper where Image: AttachableAsCGImage {
43-
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<_AttachableImageWrapper>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
44-
let data = NSMutableData()
45-
46-
// Convert the image to a CGImage.
47-
let attachableCGImage = try wrappedValue.attachableCGImage
48-
49-
// Create the image destination.
50-
let contentType = AttachableImageFormat.computeContentType(for: imageFormat, withPreferredName: attachment.preferredName)
51-
guard let dest = CGImageDestinationCreateWithData(data as CFMutableData, contentType.identifier as CFString, 1, nil) else {
52-
throw ImageAttachmentError.couldNotCreateImageDestination
41+
extension _AttachableImageWrapper: Attachable, AttachableWrapper where Image: AttachableAsImage {
42+
/// Get the image format to use when encoding an image, substituting a
43+
/// concrete type for `UTType.image` in particular.
44+
///
45+
/// - Parameters:
46+
/// - preferredName: The preferred name of the image for which a type is
47+
/// needed.
48+
///
49+
/// - Returns: An instance of ``AttachableImageFormat`` referring to a
50+
/// concrete image type.
51+
///
52+
/// This function is not part of the public interface of the testing library.
53+
private func _imageFormat(forPreferredName preferredName: String) -> AttachableImageFormat {
54+
if let imageFormat, case let contentType = imageFormat.contentType, contentType != .image {
55+
// The developer explicitly specified a type.
56+
return imageFormat
5357
}
5458

55-
// Configure the properties of the image conversion operation.
56-
let orientation = wrappedValue._attachmentOrientation
57-
let scaleFactor = wrappedValue._attachmentScaleFactor
58-
let properties: [CFString: Any] = [
59-
kCGImageDestinationLossyCompressionQuality: CGFloat(imageFormat?.encodingQuality ?? 1.0),
60-
kCGImagePropertyOrientation: orientation,
61-
kCGImagePropertyDPIWidth: 72.0 * scaleFactor,
62-
kCGImagePropertyDPIHeight: 72.0 * scaleFactor,
63-
]
64-
65-
// Perform the image conversion.
66-
CGImageDestinationAddImage(dest, attachableCGImage, properties as CFDictionary)
67-
guard CGImageDestinationFinalize(dest) else {
68-
throw ImageAttachmentError.couldNotConvertImage
59+
// The developer didn't specify a concrete type, so try to derive one from
60+
// the preferred name's path extension.
61+
let pathExtension = (preferredName as NSString).pathExtension
62+
if !pathExtension.isEmpty,
63+
let contentType = UTType(filenameExtension: pathExtension, conformingTo: .image),
64+
contentType.isDeclared {
65+
return AttachableImageFormat(contentType)
6966
}
7067

71-
// Pass the bits of the image out to the body. Note that we have an
72-
// NSMutableData here so we have to use slightly different API than we would
73-
// with an instance of Data.
74-
return try withExtendedLifetime(data) {
75-
try body(UnsafeRawBufferPointer(start: data.bytes, count: data.length))
76-
}
68+
// We couldn't derive a concrete type from the path extension, so pick
69+
// between PNG and JPEG based on the encoding quality.
70+
let encodingQuality = imageFormat?.encodingQuality ?? 1.0
71+
return encodingQuality < 1.0 ? .jpeg : .png
72+
}
73+
74+
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<_AttachableImageWrapper>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
75+
let imageFormat = _imageFormat(forPreferredName: attachment.preferredName)
76+
return try wrappedValue.withUnsafeBytes(as: imageFormat, body)
7777
}
7878

7979
public borrowing func preferredName(for attachment: borrowing Attachment<_AttachableImageWrapper>, basedOn suggestedName: String) -> String {
80-
let contentType = AttachableImageFormat.computeContentType(for: imageFormat, withPreferredName: suggestedName)
81-
return (suggestedName as NSString).appendingPathExtension(for: contentType)
80+
let imageFormat = _imageFormat(forPreferredName: suggestedName)
81+
return (suggestedName as NSString).appendingPathExtension(for: imageFormat.contentType)
8282
}
8383
}
8484
#endif

Sources/Overlays/_Testing_CoreGraphics/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88

99
if(APPLE)
1010
add_library(_Testing_CoreGraphics
11-
Attachments/_AttachableImageWrapper+AttachableWrapper.swift
1211
Attachments/AttachableAsCGImage.swift
12+
Attachments/_AttachableImageWrapper+AttachableWrapper.swift
1313
Attachments/AttachableImageFormat+UTType.swift
14-
Attachments/CGImage+AttachableAsCGImage.swift
14+
Attachments/CGImage+AttachableAsImage.swift
1515
ReexportTesting.swift)
1616

1717
target_link_libraries(_Testing_CoreGraphics PUBLIC

Sources/Overlays/_Testing_CoreImage/Attachments/CIImage+AttachableAsCGImage.swift renamed to Sources/Overlays/_Testing_CoreImage/Attachments/CIImage+AttachableAsImage.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ public import _Testing_CoreGraphics
1515
/// @Metadata {
1616
/// @Available(Swift, introduced: 6.3)
1717
/// }
18-
extension CIImage: AttachableAsCGImage {
18+
extension CIImage: AttachableAsImage, AttachableAsCGImage {
1919
/// @Metadata {
2020
/// @Available(Swift, introduced: 6.3)
2121
/// }
22-
public var attachableCGImage: CGImage {
22+
package var attachableCGImage: CGImage {
2323
get throws {
2424
guard let result = CIContext().createCGImage(self, from: extent) else {
2525
throw ImageAttachmentError.couldNotCreateCGImage

Sources/Overlays/_Testing_CoreImage/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
if(APPLE)
1010
add_library(_Testing_CoreImage
11-
Attachments/CIImage+AttachableAsCGImage.swift
11+
Attachments/CIImage+AttachableAsImage.swift
1212
ReexportTesting.swift)
1313

1414
target_link_libraries(_Testing_CoreImage PUBLIC

Sources/Overlays/_Testing_UIKit/Attachments/UIImage+AttachableAsCGImage.swift renamed to Sources/Overlays/_Testing_UIKit/Attachments/UIImage+AttachableAsImage.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ private import UIKitCore_Private
2020
/// @Metadata {
2121
/// @Available(Swift, introduced: 6.3)
2222
/// }
23-
extension UIImage: AttachableAsCGImage {
23+
extension UIImage: AttachableAsImage, AttachableAsCGImage {
2424
/// @Metadata {
2525
/// @Available(Swift, introduced: 6.3)
2626
/// }
27-
public var attachableCGImage: CGImage {
27+
package var attachableCGImage: CGImage {
2828
get throws {
2929
#if canImport(UIKitCore_Private)
3030
// _UIImageGetCGImageRepresentation() is an internal UIKit function that
@@ -49,7 +49,7 @@ extension UIImage: AttachableAsCGImage {
4949
}
5050
}
5151

52-
public var _attachmentOrientation: UInt32 {
52+
package var attachmentOrientation: UInt32 {
5353
let result: CGImagePropertyOrientation = switch imageOrientation {
5454
case .up: .up
5555
case .down: .down
@@ -64,7 +64,7 @@ extension UIImage: AttachableAsCGImage {
6464
return result.rawValue
6565
}
6666

67-
public var _attachmentScaleFactor: CGFloat {
67+
package var attachmentScaleFactor: CGFloat {
6868
scale
6969
}
7070
}

0 commit comments

Comments
 (0)