Reputation: 7524
I'm using ImageRenderer
to render a SwiftUI view to a UIImage
. I am able to successfully render a PNG from that using pngData()
. But if I call jpegData()
I get the following error:
writeImageAtIndex:867: *** CMPhotoCompressionSessionAddImage: err = kCMPhotoError_UnsupportedPixelFormat [-16995] (codec: 'jpeg')
I get the same error in the log if I send the UIImage
instance to UIActivityViewController
and choose Messages (and Messages does not show the image in its UI). If I choose Mail, it crashes the process with
-[MFMailComposeInternalViewController addAttachmentData:mimeType:fileName:] attachment must not be nil.
The problem seems to be something about the rendered image being incompatible with the JPEG encoder. However, if I share this image using AirDrop, it sends a jpeg file with perfectly-normal-seeming attributes.
I also tried rendering to a CGImage
and creating the UIImage
from that, and I get the same result.
Upvotes: 1
Views: 338
Reputation: 7524
This seems to be specific to running on an actual device—in Simulator I was not able to reproduce the issue.
The cause of the "unsupported pixel format" turned out to be a 24-bit color depth in the rendered image, and possibly also the presence of an alpha layer. The jpeg encoder is reported to require 8-bit color depth and no transparency (though I would have expected the UIImage
implementation to convert the color depth automatically).
The following converts the color depth to 8-bit:
extension View {
@MainActor func uiImageSnapshot() -> UIImage? {
let renderer = ImageRenderer(content: self)
// jpeg encoder requires that the image not have an alpha layer
renderer.isOpaque = true
guard let image = renderer.uiImage else { return nil }
// jpeg encoder requires an 8-bit color depth (including the encoder used in Mail, Messages, etc.)
return image.convertedToColorDepth(8)
}
}
extension UIImage {
func convertedToColorDepth(_ colorDepth: Int) -> UIImage? {
guard let cgImage else {
return nil
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue)
guard let context = CGContext(data: nil, width: cgImage.width, height: cgImage.height, bitsPerComponent: colorDepth, bytesPerRow: 0, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) else {
return nil
}
let rect = CGRect(x: 0, y: 0, width: cgImage.width, height: cgImage.height)
context.draw(cgImage, in: rect)
guard let newCGImage = context.makeImage() else {
return nil
}
return UIImage(cgImage: newCGImage)
}
}
Upvotes: 3