| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110 |
- // Copyright 2024 Google LLC
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- import UniformTypeIdentifiers
- #if canImport(UIKit)
- import UIKit // For UIImage extensions.
- #elseif canImport(AppKit)
- import AppKit // For NSImage extensions.
- #endif
- private let imageCompressionQuality: CGFloat = 0.8
- /// An enum describing failures that can occur when converting image types to model content data.
- /// For some image types like `CIImage`, creating valid model content requires creating a JPEG
- /// representation of the image that may not yet exist, which may be computationally expensive.
- enum ImageConversionError: Error {
- /// The image (the receiver of the call `toModelContentParts()`) was invalid.
- case invalidUnderlyingImage
- /// A valid image destination could not be allocated.
- case couldNotAllocateDestination
- /// JPEG image data conversion failed.
- case couldNotConvertToJPEG
- }
- #if canImport(UIKit)
- /// Enables images to be representable as ``PartsRepresentable``.
- @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
- extension UIImage: PartsRepresentable {
- public var partsValue: [any Part] {
- guard let data = jpegData(compressionQuality: imageCompressionQuality) else {
- return [ErrorPart(ImageConversionError.couldNotConvertToJPEG)]
- }
- return [InlineDataPart(data: data, mimeType: "image/jpeg")]
- }
- }
- #elseif canImport(AppKit)
- /// Enables images to be representable as ``PartsRepresentable``.
- @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
- extension NSImage: PartsRepresentable {
- public var partsValue: [any Part] {
- guard let cgImage = cgImage(forProposedRect: nil, context: nil, hints: nil) else {
- return [ErrorPart(ImageConversionError.invalidUnderlyingImage)]
- }
- let bmp = NSBitmapImageRep(cgImage: cgImage)
- guard let data = bmp.representation(using: .jpeg, properties: [.compressionFactor: 0.8])
- else {
- return [ErrorPart(ImageConversionError.couldNotConvertToJPEG)]
- }
- return [InlineDataPart(data: data, mimeType: "image/jpeg")]
- }
- }
- #endif
- #if !os(watchOS) // This code does not build on watchOS.
- /// Enables `CGImages` to be representable as model content.
- @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
- extension CGImage: PartsRepresentable {
- public var partsValue: [any Part] {
- let output = NSMutableData()
- guard let imageDestination = CGImageDestinationCreateWithData(
- output, UTType.jpeg.identifier as CFString, 1, nil
- ) else {
- return [ErrorPart(ImageConversionError.couldNotAllocateDestination)]
- }
- CGImageDestinationAddImage(imageDestination, self, nil)
- CGImageDestinationSetProperties(imageDestination, [
- kCGImageDestinationLossyCompressionQuality: imageCompressionQuality,
- ] as CFDictionary)
- if CGImageDestinationFinalize(imageDestination) {
- return [InlineDataPart(data: output as Data, mimeType: "image/jpeg")]
- }
- return [ErrorPart(ImageConversionError.couldNotConvertToJPEG)]
- }
- }
- #endif // !os(watchOS)
- #if canImport(CoreImage)
- /// Enables `CIImages` to be representable as model content.
- @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
- extension CIImage: PartsRepresentable {
- public var partsValue: [any Part] {
- let context = CIContext()
- let jpegData = (colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB))
- .flatMap {
- // The docs specify kCGImageDestinationLossyCompressionQuality as a supported option, but
- // Swift's type system does not allow this.
- // [kCGImageDestinationLossyCompressionQuality: imageCompressionQuality]
- context.jpegRepresentation(of: self, colorSpace: $0, options: [:])
- }
- if let jpegData = jpegData {
- return [InlineDataPart(data: jpegData, mimeType: "image/jpeg")]
- }
- return [ErrorPart(ImageConversionError.couldNotConvertToJPEG)]
- }
- }
- #endif // canImport(CoreImage)
|