Skip to content

Commit 8cbb491

Browse files
authored
Reduce duplicated code between Darwin's and Windows' image attachments. (#1270)
This PR cleans up the implementations of image attachments on Darwin and Windows so that we have less code duplication between the two. `_AttachableImageWrapper` is partially lowered to the main library (excepting the platform-specific bits) and `ImageAttachmentError` is lowered in its entirety. There are some adjustments to the (internal/package) interface of `_AttachableImageWrapper` to accomodate it not being able to specify conformance to `AttachableAsCGImage` or `AttachableAsIWICBitmapSource`. Namely, whatever code initializes an instance of it is responsible for copying `image` and providing a deinitializer function as needed. ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated.
1 parent 317916f commit 8cbb491

16 files changed

+176
-218
lines changed

Sources/Overlays/_Testing_CoreGraphics/Attachments/AttachableAsCGImage.swift

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,21 @@ private import ImageIO
2020
/// initializers on ``Testing/Attachment`` that take instances of such types and
2121
/// handle converting them to image data when needed.
2222
///
23-
/// The following system-provided image types conform to this protocol and can
24-
/// be attached to a test:
23+
/// You can attach instances of the following system-provided image types to a
24+
/// test:
2525
///
26-
/// - [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage)
27-
/// - [`CIImage`](https://developer.apple.com/documentation/coreimage/ciimage)
28-
/// - [`NSImage`](https://developer.apple.com/documentation/appkit/nsimage)
29-
/// (macOS)
30-
/// - [`UIImage`](https://developer.apple.com/documentation/uikit/uiimage)
31-
/// (iOS, watchOS, tvOS, visionOS, and Mac Catalyst)
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) |
3231
///
3332
/// You do not generally need to add your own conformances to this protocol. If
3433
/// you have an image in another format that needs to be attached to a test,
3534
/// first convert it to an instance of one of the types above.
3635
@_spi(Experimental)
3736
@available(_uttypesAPI, *)
38-
public protocol AttachableAsCGImage {
37+
public protocol AttachableAsCGImage: SendableMetatype {
3938
/// An instance of `CGImage` representing this image.
4039
///
4140
/// - Throws: Any error that prevents the creation of an image.

Sources/Overlays/_Testing_CoreGraphics/Attachments/Attachment+AttachableAsCGImage.swift

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,14 @@ extension Attachment {
2626
/// This value is used when recording issues associated with the
2727
/// attachment.
2828
///
29-
/// The following system-provided image types conform to the
30-
/// ``AttachableAsCGImage`` protocol and can be attached to a test:
29+
/// You can attach instances of the following system-provided image types to a
30+
/// test:
3131
///
32-
/// - [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage)
33-
/// - [`CIImage`](https://developer.apple.com/documentation/coreimage/ciimage)
34-
/// - [`NSImage`](https://developer.apple.com/documentation/appkit/nsimage)
35-
/// (macOS)
36-
/// - [`UIImage`](https://developer.apple.com/documentation/uikit/uiimage)
37-
/// (iOS, watchOS, tvOS, visionOS, and Mac Catalyst)
32+
/// | Platform | Supported Types |
33+
/// |-|-|
34+
/// | 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) |
35+
/// | 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) |
36+
/// | 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) |
3837
///
3938
/// The testing library uses the image format specified by `imageFormat`. Pass
4039
/// `nil` to let the testing library decide which image format to use. If you
@@ -48,8 +47,12 @@ extension Attachment {
4847
named preferredName: String? = nil,
4948
as imageFormat: AttachableImageFormat? = nil,
5049
sourceLocation: SourceLocation = #_sourceLocation
51-
) where AttachableValue == _AttachableImageWrapper<T> {
52-
let imageWrapper = _AttachableImageWrapper(image: image, imageFormat: imageFormat)
50+
) where T: AttachableAsCGImage, AttachableValue == _AttachableImageWrapper<T> {
51+
let imageWrapper = _AttachableImageWrapper(
52+
image: image._copyAttachableValue(),
53+
imageFormat: imageFormat,
54+
deinitializingWith: { _ in }
55+
)
5356
self.init(imageWrapper, named: preferredName, sourceLocation: sourceLocation)
5457
}
5558

@@ -64,17 +67,14 @@ extension Attachment {
6467
/// - sourceLocation: The source location of the call to this function.
6568
///
6669
/// This function creates a new instance of ``Attachment`` wrapping `image`
67-
/// and immediately attaches it to the current test.
70+
/// and immediately attaches it to the current test. You can attach instances
71+
/// of the following system-provided image types to a test:
6872
///
69-
/// The following system-provided image types conform to the
70-
/// ``AttachableAsCGImage`` protocol and can be attached to a test:
71-
///
72-
/// - [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage)
73-
/// - [`CIImage`](https://developer.apple.com/documentation/coreimage/ciimage)
74-
/// - [`NSImage`](https://developer.apple.com/documentation/appkit/nsimage)
75-
/// (macOS)
76-
/// - [`UIImage`](https://developer.apple.com/documentation/uikit/uiimage)
77-
/// (iOS, watchOS, tvOS, visionOS, and Mac Catalyst)
73+
/// | Platform | Supported Types |
74+
/// |-|-|
75+
/// | 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) |
76+
/// | 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) |
77+
/// | 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) |
7878
///
7979
/// The testing library uses the image format specified by `imageFormat`. Pass
8080
/// `nil` to let the testing library decide which image format to use. If you
@@ -88,19 +88,20 @@ extension Attachment {
8888
named preferredName: String? = nil,
8989
as imageFormat: AttachableImageFormat? = nil,
9090
sourceLocation: SourceLocation = #_sourceLocation
91-
) where AttachableValue == _AttachableImageWrapper<T> {
91+
) where T: AttachableAsCGImage, AttachableValue == _AttachableImageWrapper<T> {
9292
let attachment = Self(image, named: preferredName, as: imageFormat, sourceLocation: sourceLocation)
9393
Self.record(attachment, sourceLocation: sourceLocation)
9494
}
9595
}
9696

97+
// MARK: -
98+
9799
@_spi(Experimental) // STOP: not part of ST-0014
98100
@available(_uttypesAPI, *)
99101
extension Attachment where AttachableValue: AttachableWrapper, AttachableValue.Wrapped: AttachableAsCGImage {
100102
/// The image format to use when encoding the represented image.
101-
@_disfavoredOverload
102-
public var imageFormat: AttachableImageFormat? {
103-
// FIXME: no way to express `where AttachableValue == _AttachableImageWrapper<???>` on a property
103+
@_disfavoredOverload public var imageFormat: AttachableImageFormat? {
104+
// FIXME: no way to express `where AttachableValue == _AttachableImageWrapper<???>` on a property (see rdar://47559973)
104105
(attachableValue as? _AttachableImageWrapper<AttachableValue.Wrapped>)?.imageFormat
105106
}
106107
}

Sources/Overlays/_Testing_CoreGraphics/Attachments/ImageAttachmentError.swift

Lines changed: 0 additions & 34 deletions
This file was deleted.

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

Lines changed: 5 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
private import CoreGraphics
1414

1515
private import ImageIO
16-
import UniformTypeIdentifiers
16+
private import UniformTypeIdentifiers
1717

1818
/// ## Why can't images directly conform to Attachable?
1919
///
@@ -38,53 +38,13 @@ import UniformTypeIdentifiers
3838
/// (And no, the language does not let us write `where T: Self` anywhere
3939
/// useful.)
4040

41-
/// A wrapper type for image types such as `CGImage` and `NSImage` that can be
42-
/// attached indirectly.
43-
///
44-
/// You do not need to use this type directly. Instead, initialize an instance
45-
/// of ``Attachment`` using an instance of an image type that conforms to
46-
/// ``AttachableAsCGImage``. The following system-provided image types conform
47-
/// to the ``AttachableAsCGImage`` protocol and can be attached to a test:
48-
///
49-
/// - [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage)
50-
/// - [`CIImage`](https://developer.apple.com/documentation/coreimage/ciimage)
51-
/// - [`NSImage`](https://developer.apple.com/documentation/appkit/nsimage)
52-
/// (macOS)
53-
/// - [`UIImage`](https://developer.apple.com/documentation/uikit/uiimage)
54-
/// (iOS, watchOS, tvOS, visionOS, and Mac Catalyst)
55-
@_spi(Experimental)
5641
@available(_uttypesAPI, *)
57-
public final class _AttachableImageWrapper<Image>: Sendable where Image: AttachableAsCGImage {
58-
/// The underlying image.
59-
///
60-
/// `CGImage` and `UIImage` are sendable, but `NSImage` is not. `NSImage`
61-
/// instances can be created from closures that are run at rendering time.
62-
/// The AppKit cross-import overlay is responsible for ensuring that any
63-
/// instances of this type it creates hold "safe" `NSImage` instances.
64-
nonisolated(unsafe) let image: Image
65-
66-
/// The image format to use when encoding the represented image.
67-
let imageFormat: AttachableImageFormat?
68-
69-
init(image: Image, imageFormat: AttachableImageFormat?) {
70-
self.image = image._copyAttachableValue()
71-
self.imageFormat = imageFormat
72-
}
73-
}
74-
75-
// MARK: -
76-
77-
@available(_uttypesAPI, *)
78-
extension _AttachableImageWrapper: AttachableWrapper {
79-
public var wrappedValue: Image {
80-
image
81-
}
82-
42+
extension _AttachableImageWrapper: Attachable, AttachableWrapper where Image: AttachableAsCGImage {
8343
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<_AttachableImageWrapper>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
8444
let data = NSMutableData()
8545

8646
// Convert the image to a CGImage.
87-
let attachableCGImage = try image.attachableCGImage
47+
let attachableCGImage = try wrappedValue.attachableCGImage
8848

8949
// Create the image destination.
9050
let contentType = AttachableImageFormat.computeContentType(for: imageFormat, withPreferredName: attachment.preferredName)
@@ -93,8 +53,8 @@ extension _AttachableImageWrapper: AttachableWrapper {
9353
}
9454

9555
// Configure the properties of the image conversion operation.
96-
let orientation = image._attachmentOrientation
97-
let scaleFactor = image._attachmentScaleFactor
56+
let orientation = wrappedValue._attachmentOrientation
57+
let scaleFactor = wrappedValue._attachmentScaleFactor
9858
let properties: [CFString: Any] = [
9959
kCGImageDestinationLossyCompressionQuality: CGFloat(imageFormat?.encodingQuality ?? 1.0),
10060
kCGImagePropertyOrientation: orientation,

Sources/Overlays/_Testing_WinSDK/Attachments/AttachableAsIWICBitmapSource.swift

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
//
1010

1111
#if os(Windows)
12-
@_spi(Experimental) import Testing
13-
12+
private import Testing
1413
public import WinSDK
1514

1615
/// A protocol describing images that can be converted to instances of
@@ -21,13 +20,14 @@ public import WinSDK
2120
/// initializers on ``Testing/Attachment`` that take instances of such types and
2221
/// handle converting them to image data when needed.
2322
///
24-
/// The following system-provided image types conform to this protocol and can
25-
/// be attached to a test:
23+
/// You can attach instances of the following system-provided image types to a
24+
/// test:
2625
///
27-
/// - [`HBITMAP`](https://learn.microsoft.com/en-us/windows/win32/gdi/bitmaps)
28-
/// - [`HICON`](https://learn.microsoft.com/en-us/windows/win32/menurc/icons)
29-
/// - [`IWICBitmapSource`](https://learn.microsoft.com/en-us/windows/win32/api/wincodec/nn-wincodec-iwicbitmapsource)
30-
/// (including its subclasses declared by Windows Imaging Component)
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) |
3131
///
3232
/// You do not generally need to add your own conformances to this protocol. If
3333
/// you have an image in another format that needs to be attached to a test,
@@ -93,26 +93,27 @@ public protocol _AttachableByAddressAsIWICBitmapSource {
9393
}
9494

9595
/// A protocol describing images that can be converted to instances of
96-
/// ``Testing/Attachment``.
96+
/// [`Attachment`](https://developer.apple.com/documentation/testing/attachment).
9797
///
9898
/// Instances of types conforming to this protocol do not themselves conform to
99-
/// ``Testing/Attachable``. Instead, the testing library provides additional
100-
/// initializers on ``Testing/Attachment`` that take instances of such types and
101-
/// handle converting them to image data when needed.
99+
/// [`Attachable`](https://developer.apple.com/documentation/testing/attachable).
100+
/// Instead, the testing library provides additional initializers on [`Attachment`](https://developer.apple.com/documentation/testing/attachment)
101+
/// that take instances of such types and handle converting them to image data when needed.
102102
///
103-
/// The following system-provided image types conform to this protocol and can
104-
/// be attached to a test:
103+
/// You can attach instances of the following system-provided image types to a
104+
/// test:
105105
///
106-
/// - [`HBITMAP`](https://learn.microsoft.com/en-us/windows/win32/gdi/bitmaps)
107-
/// - [`HICON`](https://learn.microsoft.com/en-us/windows/win32/menurc/icons)
108-
/// - [`IWICBitmapSource`](https://learn.microsoft.com/en-us/windows/win32/api/wincodec/nn-wincodec-iwicbitmapsource)
109-
/// (including its subclasses declared by Windows Imaging Component)
106+
/// | Platform | Supported Types |
107+
/// |-|-|
108+
/// | 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) |
109+
/// | 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) |
110+
/// | 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) |
110111
///
111112
/// You do not generally need to add your own conformances to this protocol. If
112113
/// you have an image in another format that needs to be attached to a test,
113114
/// first convert it to an instance of one of the types above.
114115
@_spi(Experimental)
115-
public protocol AttachableAsIWICBitmapSource {
116+
public protocol AttachableAsIWICBitmapSource: SendableMetatype {
116117
/// Create a WIC bitmap source representing an instance of this type.
117118
///
118119
/// - Returns: A pointer to a new WIC bitmap source representing this image.

Sources/Overlays/_Testing_WinSDK/Attachments/AttachableImageFormat+CLSID.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
//
1010

1111
#if os(Windows)
12-
@_spi(Experimental) import Testing
13-
12+
@_spi(Experimental) public import Testing
1413
public import WinSDK
1514

1615
extension AttachableImageFormat {
@@ -258,7 +257,7 @@ extension AttachableImageFormat {
258257
///
259258
/// If `clsid` does not represent an image encoder type supported by WIC, the
260259
/// result is undefined. For a list of image encoders supported by WIC, see
261-
/// the documentation for the [IWICBitmapEncoder](https://learn.microsoft.com/en-us/windows/win32/api/wincodec/nn-wincodec-iwicbitmapencoder)
260+
/// the documentation for the [`IWICBitmapEncoder`](https://learn.microsoft.com/en-us/windows/win32/api/wincodec/nn-wincodec-iwicbitmapencoder)
262261
/// class.
263262
public init(_ clsid: CLSID, encodingQuality: Float = 1.0) {
264263
self.init(kind: .systemValue(clsid), encodingQuality: encodingQuality)

0 commit comments

Comments
 (0)