Skip to content

Commit 5ea43ab

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 c1be7ba commit 5ea43ab

27 files changed

+657
-534
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: 49 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,42 +9,21 @@
99
//
1010

1111
#if SWT_TARGET_OS_APPLE && canImport(CoreGraphics)
12-
public import CoreGraphics
13-
private import ImageIO
12+
package import CoreGraphics
13+
package 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.
44-
///
45-
/// @Metadata {
46-
/// @Available(Swift, introduced: 6.3)
47-
/// }
4827
var attachableCGImage: CGImage { get throws }
4928

5029
/// The orientation of the image.
@@ -53,38 +32,64 @@ public protocol AttachableAsCGImage: _AttachableAsImage, SendableMetatype {
5332
/// `CGImagePropertyOrientation`. The default value of this property is
5433
/// `.up`.
5534
///
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 }
35+
/// This property is not part of the public interface of the testing library.
36+
/// It may be removed in a future update.
37+
var attachmentOrientation: CGImagePropertyOrientation { get }
5938

6039
/// The scale factor of the image.
6140
///
6241
/// The value of this property is typically greater than `1.0` when an image
6342
/// originates from a Retina Display screenshot or similar. The default value
6443
/// of this property is `1.0`.
6544
///
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 }
45+
/// This property is not part of the public interface of the testing library.
46+
/// It may be removed in a future update.
47+
var attachmentScaleFactor: CGFloat { get }
6948
}
7049

7150
@available(_uttypesAPI, *)
7251
extension AttachableAsCGImage {
73-
public var _attachmentOrientation: UInt32 {
74-
CGImagePropertyOrientation.up.rawValue
52+
package var attachmentOrientation: CGImagePropertyOrientation {
53+
.up
7554
}
7655

77-
public var _attachmentScaleFactor: CGFloat {
56+
package var attachmentScaleFactor: CGFloat {
7857
1.0
7958
}
8059

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

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

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

Lines changed: 47 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -9,58 +9,10 @@
99
//
1010

1111
#if SWT_TARGET_OS_APPLE && canImport(CoreGraphics)
12-
public import Testing
13-
1412
public import UniformTypeIdentifiers
1513

1614
@available(_uttypesAPI, *)
1715
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-
6416
/// The content type corresponding to this image format.
6517
///
6618
/// For example, if this image format equals ``png``, the value of this
@@ -72,14 +24,7 @@ extension AttachableImageFormat {
7224
/// @Available(Swift, introduced: 6.3)
7325
/// }
7426
public var contentType: UTType {
75-
switch kind {
76-
case .png:
77-
return .png
78-
case .jpeg:
79-
return .jpeg
80-
case let .systemValue(contentType):
81-
return contentType as! UTType
82-
}
27+
kind.contentType
8328
}
8429

8530
/// Initialize an instance of this type with the given content type and
@@ -100,12 +45,19 @@ extension AttachableImageFormat {
10045
/// @Metadata {
10146
/// @Available(Swift, introduced: 6.3)
10247
/// }
103-
public init(_ contentType: UTType, encodingQuality: Float = 1.0) {
104-
precondition(
105-
contentType.conforms(to: .image),
106-
"An image cannot be attached as an instance of type '\(contentType.identifier)'. Use a type that conforms to 'public.image' instead."
107-
)
108-
self.init(kind: .systemValue(contentType), encodingQuality: encodingQuality)
48+
public init(contentType: UTType, encodingQuality: Float = 1.0) {
49+
switch contentType {
50+
case .png:
51+
self.init(kind: .png, encodingQuality: encodingQuality)
52+
case .jpeg:
53+
self.init(kind: .jpeg, encodingQuality: encodingQuality)
54+
default:
55+
precondition(
56+
contentType.conforms(to: .image),
57+
"An image cannot be attached as an instance of type '\(contentType.identifier)'. Use a type that conforms to 'public.image' instead."
58+
)
59+
self.init(kind: .systemValue(contentType), encodingQuality: encodingQuality)
60+
}
10961
}
11062

11163
/// Construct an instance of this type with the given path extension and
@@ -135,11 +87,42 @@ extension AttachableImageFormat {
13587
public init?(pathExtension: String, encodingQuality: Float = 1.0) {
13688
let pathExtension = pathExtension.drop { $0 == "." }
13789

138-
guard let contentType = UTType(filenameExtension: String(pathExtension), conformingTo: .image) else {
90+
guard let contentType = UTType(filenameExtension: String(pathExtension), conformingTo: .image),
91+
contentType.isDeclared else {
13992
return nil
14093
}
14194

142-
self.init(contentType, encodingQuality: encodingQuality)
95+
self.init(contentType: contentType, encodingQuality: encodingQuality)
96+
}
97+
}
98+
99+
// MARK: - CustomStringConvertible, CustomDebugStringConvertible
100+
101+
@available(_uttypesAPI, *)
102+
extension AttachableImageFormat.Kind: CustomStringConvertible, CustomDebugStringConvertible {
103+
/// The content type corresponding to this image format.
104+
fileprivate var contentType: UTType {
105+
switch self {
106+
case .png:
107+
return .png
108+
case .jpeg:
109+
return .jpeg
110+
case let .systemValue(contentType):
111+
return contentType as! UTType
112+
}
113+
}
114+
115+
package var description: String {
116+
let contentType = contentType
117+
return contentType.localizedDescription ?? contentType.identifier
118+
}
119+
120+
package var debugDescription: String {
121+
let contentType = contentType
122+
if let localizedDescription = contentType.localizedDescription {
123+
return "\(localizedDescription) (\(contentType.identifier))"
124+
}
125+
return contentType.identifier
143126
}
144127
}
145128
#endif

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
}

0 commit comments

Comments
 (0)