diff --git a/Sources/Overlays/_Testing_CoreGraphics/Attachments/AttachableImageFormat+UTType.swift b/Sources/Overlays/_Testing_CoreGraphics/Attachments/AttachableImageFormat+UTType.swift index 575e357cd..173265807 100644 --- a/Sources/Overlays/_Testing_CoreGraphics/Attachments/AttachableImageFormat+UTType.swift +++ b/Sources/Overlays/_Testing_CoreGraphics/Attachments/AttachableImageFormat+UTType.swift @@ -97,4 +97,38 @@ extension AttachableImageFormat { self.init(kind: .systemValue(contentType), encodingQuality: encodingQuality) } } + +@available(_uttypesAPI, *) +@_spi(Experimental) // STOP: not part of ST-0014 +extension AttachableImageFormat { + /// Construct an instance of this type with the given path extension and + /// encoding quality. + /// + /// - Parameters: + /// - pathExtension: A path extension corresponding to the image format to + /// use when encoding images. + /// - encodingQuality: The encoding quality to use when encoding images. For + /// the lowest supported quality, pass `0.0`. For the highest supported + /// quality, pass `1.0`. + /// + /// If the target image format does not support variable-quality encoding, + /// the value of the `encodingQuality` argument is ignored. + /// + /// If `pathExtension` does not correspond to a recognized image format, this + /// initializer returns `nil`: + /// + /// - On Apple platforms, the content type corresponding to `pathExtension` + /// must conform to [`UTType.image`](https://developer.apple.com/documentation/uniformtypeidentifiers/uttype-swift.struct/image). + /// - On Windows, there must be a corresponding subclass of [`IWICBitmapEncoder`](https://learn.microsoft.com/en-us/windows/win32/api/wincodec/nn-wincodec-iwicbitmapencoder) + /// registered with Windows Imaging Component. + public init?(pathExtension: String, encodingQuality: Float = 1.0) { + let pathExtension = pathExtension.drop { $0 == "." } + + guard let contentType = UTType(filenameExtension: String(pathExtension), conformingTo: .image) else { + return nil + } + + self.init(contentType, encodingQuality: encodingQuality) + } +} #endif diff --git a/Sources/Overlays/_Testing_WinSDK/Attachments/AttachableAsIWICBitmapSource.swift b/Sources/Overlays/_Testing_WinSDK/Attachments/AttachableAsIWICBitmapSource.swift index b3c37d27f..cf23df63a 100644 --- a/Sources/Overlays/_Testing_WinSDK/Attachments/AttachableAsIWICBitmapSource.swift +++ b/Sources/Overlays/_Testing_WinSDK/Attachments/AttachableAsIWICBitmapSource.swift @@ -113,6 +113,14 @@ public protocol _AttachableByAddressAsIWICBitmapSource { /// first convert it to an instance of one of the types above. @_spi(Experimental) public protocol AttachableAsIWICBitmapSource { + /// Create a WIC bitmap source representing an instance of this type. + /// + /// - Returns: A pointer to a new WIC bitmap source representing this image. + /// The caller is responsible for releasing this image when done with it. + /// + /// - Throws: Any error that prevented the creation of the WIC bitmap source. + func copyAttachableIWICBitmapSource() throws -> UnsafeMutablePointer + /// Create a WIC bitmap representing an instance of this type. /// /// - Parameters: @@ -124,9 +132,16 @@ public protocol AttachableAsIWICBitmapSource { /// /// - Throws: Any error that prevented the creation of the WIC bitmap. /// + /// The default implementation of this function ignores `factory` and calls + /// ``copyAttachableIWICBitmapSource()``. If your implementation of + /// ``copyAttachableIWICBitmapSource()`` needs to create a WIC imaging factory + /// in order to return a result, it is more efficient to implement this + /// function too so that the testing library can pass the WIC imaging factory + /// it creates. + /// /// This function is not part of the public interface of the testing library. /// It may be removed in a future update. - borrowing func _copyAttachableIWICBitmapSource( + func _copyAttachableIWICBitmapSource( using factory: UnsafeMutablePointer ) throws -> UnsafeMutablePointer @@ -164,6 +179,14 @@ public protocol AttachableAsIWICBitmapSource { func _deinitializeAttachableValue() } +extension AttachableAsIWICBitmapSource { + public func _copyAttachableIWICBitmapSource( + using factory: UnsafeMutablePointer + ) throws -> UnsafeMutablePointer { + try copyAttachableIWICBitmapSource() + } +} + extension AttachableAsIWICBitmapSource where Self: Sendable { public func _copyAttachableValue() -> Self { self diff --git a/Sources/Overlays/_Testing_WinSDK/Attachments/AttachableImageFormat+CLSID.swift b/Sources/Overlays/_Testing_WinSDK/Attachments/AttachableImageFormat+CLSID.swift index bf7e88366..009015e13 100644 --- a/Sources/Overlays/_Testing_WinSDK/Attachments/AttachableImageFormat+CLSID.swift +++ b/Sources/Overlays/_Testing_WinSDK/Attachments/AttachableImageFormat+CLSID.swift @@ -277,19 +277,21 @@ extension AttachableImageFormat { /// If the target image format does not support variable-quality encoding, /// the value of the `encodingQuality` argument is ignored. /// - /// If `pathExtension` does not correspond to an image format that WIC can use - /// to encode images, this initializer returns `nil`. For a list of image - /// encoders supported by WIC, see the documentation for the [IWICBitmapEncoder](https://learn.microsoft.com/en-us/windows/win32/api/wincodec/nn-wincodec-iwicbitmapencoder) - /// class. + /// If `pathExtension` does not correspond to a recognized image format, this + /// initializer returns `nil`: + /// + /// - On Apple platforms, the content type corresponding to `pathExtension` + /// must conform to [`UTType.image`](https://developer.apple.com/documentation/uniformtypeidentifiers/uttype-swift.struct/image). + /// - On Windows, there must be a corresponding subclass of [`IWICBitmapEncoder`](https://learn.microsoft.com/en-us/windows/win32/api/wincodec/nn-wincodec-iwicbitmapencoder) + /// registered with Windows Imaging Component. public init?(pathExtension: String, encodingQuality: Float = 1.0) { let pathExtension = pathExtension.drop { $0 == "." } - let clsid = Self._computeCLSID(forPathExtension: String(pathExtension)) - if let clsid { - self.init(clsid, encodingQuality: encodingQuality) - } else { + guard let clsid = Self._computeCLSID(forPathExtension: String(pathExtension)) else { return nil } + + self.init(clsid, encodingQuality: encodingQuality) } } #endif diff --git a/Sources/Overlays/_Testing_WinSDK/Attachments/Attachment+AttachableAsIWICBitmapSource.swift b/Sources/Overlays/_Testing_WinSDK/Attachments/Attachment+AttachableAsIWICBitmapSource.swift index 5597ab1f4..ec0bb016b 100644 --- a/Sources/Overlays/_Testing_WinSDK/Attachments/Attachment+AttachableAsIWICBitmapSource.swift +++ b/Sources/Overlays/_Testing_WinSDK/Attachments/Attachment+AttachableAsIWICBitmapSource.swift @@ -12,7 +12,7 @@ @_spi(Experimental) public import Testing @_spi(Experimental) -extension Attachment where AttachableValue: ~Copyable { +extension Attachment { /// Initialize an instance of this type that encloses the given image. /// /// - Parameters: @@ -42,7 +42,7 @@ extension Attachment where AttachableValue: ~Copyable { /// correspond to an image format the operating system knows how to write, the /// testing library selects an appropriate image format for you. public init( - _ image: borrowing T, + _ image: T, named preferredName: String? = nil, as imageFormat: AttachableImageFormat? = nil, sourceLocation: SourceLocation = #_sourceLocation @@ -82,7 +82,7 @@ extension Attachment where AttachableValue: ~Copyable { /// correspond to an image format the operating system knows how to write, the /// testing library selects an appropriate image format for you. public static func record( - _ image: borrowing T, + _ image: T, named preferredName: String? = nil, as imageFormat: AttachableImageFormat? = nil, sourceLocation: SourceLocation = #_sourceLocation diff --git a/Sources/Overlays/_Testing_WinSDK/Attachments/UnsafeMutablePointer+AttachableAsIWICBitmapSource.swift b/Sources/Overlays/_Testing_WinSDK/Attachments/UnsafeMutablePointer+AttachableAsIWICBitmapSource.swift index 0bfa354f4..6d34a6e90 100644 --- a/Sources/Overlays/_Testing_WinSDK/Attachments/UnsafeMutablePointer+AttachableAsIWICBitmapSource.swift +++ b/Sources/Overlays/_Testing_WinSDK/Attachments/UnsafeMutablePointer+AttachableAsIWICBitmapSource.swift @@ -15,6 +15,14 @@ public import WinSDK @_spi(Experimental) extension UnsafeMutablePointer: AttachableAsIWICBitmapSource where Pointee: _AttachableByAddressAsIWICBitmapSource { + public func copyAttachableIWICBitmapSource() throws -> UnsafeMutablePointer { + let factory = try IWICImagingFactory.create() + defer { + _ = factory.pointee.lpVtbl.pointee.Release(factory) + } + return try _copyAttachableIWICBitmapSource(using: factory) + } + public func _copyAttachableIWICBitmapSource(using factory: UnsafeMutablePointer) throws -> UnsafeMutablePointer { try Pointee._copyAttachableIWICBitmapSource(from: self, using: factory) } diff --git a/Sources/Testing/Attachments/AttachableImageFormat.swift b/Sources/Testing/Attachments/AttachableImageFormat.swift index bf5df4f7f..4798e42b1 100644 --- a/Sources/Testing/Attachments/AttachableImageFormat.swift +++ b/Sources/Testing/Attachments/AttachableImageFormat.swift @@ -23,6 +23,8 @@ /// - On Apple platforms, you can use [`CGImageDestinationCopyTypeIdentifiers()`](https://developer.apple.com/documentation/imageio/cgimagedestinationcopytypeidentifiers()) /// from the [Image I/O framework](https://developer.apple.com/documentation/imageio) /// to determine which formats are supported. +/// - On Windows, you can use [`IWICImagingFactory.CreateComponentEnumerator()`](https://learn.microsoft.com/en-us/windows/win32/api/wincodec/nf-wincodec-iwicimagingfactory-createcomponentenumerator) +/// to enumerate the available image encoders. @_spi(Experimental) @available(_uttypesAPI, *) public struct AttachableImageFormat: Sendable { diff --git a/Tests/TestingTests/AttachmentTests.swift b/Tests/TestingTests/AttachmentTests.swift index e26dbe595..84db8fe38 100644 --- a/Tests/TestingTests/AttachmentTests.swift +++ b/Tests/TestingTests/AttachmentTests.swift @@ -836,6 +836,14 @@ extension AttachmentTests { #expect(jpgjpegFilename == "example.jpg") } #endif + +#if (canImport(CoreGraphics) && canImport(_Testing_CoreGraphics)) || (canImport(WinSDK) && canImport(_Testing_WinSDK)) + @available(_uttypesAPI, *) + @Test func imageFormatFromPathExtension() { + let format = AttachableImageFormat(pathExtension: "png") + #expect(format != nil) + } +#endif } }