Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
package import CoreGraphics
package import ImageIO
private import UniformTypeIdentifiers
#if canImport(UniformTypeIdentifiers_Private)
@_spi(Private) private import UniformTypeIdentifiers
#endif

/// A protocol describing images that can be converted to instances of
/// [`Attachment`](https://developer.apple.com/documentation/testing/attachment)
Expand Down Expand Up @@ -47,6 +50,20 @@ package protocol AttachableAsCGImage: AttachableAsImage {
var attachmentScaleFactor: CGFloat { get }
}

/// The set of type identifiers supported by Image I/O.
@available(_uttypesAPI, *)
private let _supportedTypeIdentifiers = Set(CGImageDestinationCopyTypeIdentifiers() as? [String] ?? [])

/// The set of content types supported by Image I/O.
@available(_uttypesAPI, *)
private let _supportedContentTypes = {
#if canImport(UniformTypeIdentifiers_Private)
Set(UTType._types(identifiers: _supportedTypeIdentifiers).values)
#else
Set(_supportedTypeIdentifiers.compactMap(UTType.init(_:)))
#endif
}()

@available(_uttypesAPI, *)
extension AttachableAsCGImage {
package var attachmentOrientation: CGImagePropertyOrientation {
Expand All @@ -63,8 +80,17 @@ extension AttachableAsCGImage {
// Convert the image to a CGImage.
let attachableCGImage = try attachableCGImage

// Determine the base content type to use.
var contentType = imageFormat.contentType
if !_supportedTypeIdentifiers.contains(contentType.identifier) {
guard let baseType = _supportedContentTypes.first(where: contentType.conforms(to:)) else {
throw ImageAttachmentError.unsupportedImageFormat(contentType.identifier)
}
contentType = baseType
}

// Create the image destination.
guard let dest = CGImageDestinationCreateWithData(data as CFMutableData, imageFormat.contentType.identifier as CFString, 1, nil) else {
guard let dest = CGImageDestinationCreateWithData(data as CFMutableData, contentType.identifier as CFString, 1, nil) else {
throw ImageAttachmentError.couldNotCreateImageDestination
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ package enum ImageAttachmentError: Error {

/// The image could not be converted.
case couldNotConvertImage

/// The specified content type is not supported by Image I/O.
case unsupportedImageFormat(_ typeIdentifier: String)
#elseif os(Windows)
/// A call to `QueryInterface()` failed.
case queryInterfaceFailed(Any.Type, CLong)
Expand Down Expand Up @@ -57,6 +60,8 @@ extension ImageAttachmentError: CustomStringConvertible {
"Could not create the Core Graphics image destination to encode this image."
case .couldNotConvertImage:
"Could not convert the image to the specified format."
case let .unsupportedImageFormat(typeIdentifier):
"Could not convert the image to the format '\(typeIdentifier)' because the system does not support it."
}
#elseif os(Windows)
switch self {
Expand Down
27 changes: 27 additions & 0 deletions Tests/TestingTests/AttachmentTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,33 @@ extension AttachmentTests {
}
}

@available(_uttypesAPI, *)
@Test func attachCGImageWithCustomUTType() throws {
let contentType = try #require(UTType(tag: "derived-from-jpeg", tagClass: .filenameExtension, conformingTo: .jpeg))
let format = AttachableImageFormat(contentType: contentType)
let image = try Self.cgImage.get()
let attachment = Attachment(image, named: "diamond", as: format)
#expect(attachment.attachableValue === image)
try attachment.attachableValue.withUnsafeBytes(for: attachment) { buffer in
#expect(buffer.count > 32)
}
if let ext = format.contentType.preferredFilenameExtension {
#expect(attachment.preferredName == ("diamond" as NSString).appendingPathExtension(ext))
}
}

@available(_uttypesAPI, *)
@Test func attachCGImageWithUnsupportedImageType() throws {
let contentType = try #require(UTType(tag: "unsupported-image-format", tagClass: .filenameExtension, conformingTo: .image))
let format = AttachableImageFormat(contentType: contentType)
let image = try Self.cgImage.get()
let attachment = Attachment(image, named: "diamond", as: format)
#expect(attachment.attachableValue === image)
#expect(throws: ImageAttachmentError.self) {
try attachment.attachableValue.withUnsafeBytes(for: attachment) { _ in }
}
}

#if !SWT_NO_EXIT_TESTS
@available(_uttypesAPI, *)
@Test func cannotAttachCGImageWithNonImageType() async {
Expand Down
Loading