Files
firebase-ios-sdk/FirebaseAI/Sources/PartsRepresentable+Image.swift
T

112 lines
4.3 KiB
Swift

// 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``.
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``.
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.
extension CGImage: PartsRepresentable {
public var partsValue: [any Part] {
let output = NSMutableData()
let jpegIdentifier = if #available(macOS 11.0, *) {
UTType.jpeg.identifier
} else {
"public.jpeg"
}
guard let imageDestination = CGImageDestinationCreateWithData(
output, jpegIdentifier 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.
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)