1
+ // Copyright 2025 Google LLC
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ import CoreGraphics
16
+ import Foundation
17
+ import ImageIO
18
+
19
+ /// A reference to a mask for inpainting.
20
+ @available ( iOS 15 . 0 , macOS 12 . 0 , macCatalyst 15 . 0 , tvOS 15 . 0 , watchOS 8 . 0 , * )
21
+ public struct ImagenMaskReference : ImagenReferenceImage , Encodable {
22
+ /// The mask data.
23
+ public let data : Data
24
+
25
+ public init ( data: Data ) {
26
+ self . data = data
27
+ }
28
+
29
+ enum CodingKeys : String , CodingKey {
30
+ case data = " bytesBase64Encoded "
31
+ }
32
+
33
+ public func encode( to encoder: Encoder ) throws {
34
+ var container = encoder. container ( keyedBy: CodingKeys . self)
35
+ try container. encode ( data. base64EncodedString ( ) , forKey: . data)
36
+ }
37
+
38
+ static func generateMaskAndPadForOutpainting( image: ImagenInlineImage ,
39
+ newDimensions: Dimensions ,
40
+ newPosition: ImagenImagePlacement ) throws
41
+ -> [ ImagenReferenceImage ] {
42
+ guard let cgImage = CGImage . fromData ( image. data) else {
43
+ throw NSError ( domain: " com.google.firebase.ai " , code: 0 , userInfo: [ NSLocalizedDescriptionKey: " Could not create image from data. " ] )
44
+ }
45
+
46
+ let originalWidth = cgImage. width
47
+ let originalHeight = cgImage. height
48
+
49
+ guard newDimensions. width >= originalWidth, newDimensions. height >= originalHeight else {
50
+ throw NSError ( domain: " com.google.firebase.ai " , code: 0 , userInfo: [ NSLocalizedDescriptionKey: " New dimensions must be larger than the original image. " ] )
51
+ }
52
+
53
+ let offsetX : Int
54
+ let offsetY : Int
55
+
56
+ switch newPosition {
57
+ case . topLeft:
58
+ offsetX = 0
59
+ offsetY = 0
60
+ case . topCenter:
61
+ offsetX = ( newDimensions. width - originalWidth) / 2
62
+ offsetY = 0
63
+ case . topRight:
64
+ offsetX = newDimensions. width - originalWidth
65
+ offsetY = 0
66
+ case . middleLeft:
67
+ offsetX = 0
68
+ offsetY = ( newDimensions. height - originalHeight) / 2
69
+ case . center:
70
+ offsetX = ( newDimensions. width - originalWidth) / 2
71
+ offsetY = ( newDimensions. height - originalHeight) / 2
72
+ case . middleRight:
73
+ offsetX = newDimensions. width - originalWidth
74
+ offsetY = ( newDimensions. height - originalHeight) / 2
75
+ case . bottomLeft:
76
+ offsetX = 0
77
+ offsetY = newDimensions. height - originalHeight
78
+ case . bottomCenter:
79
+ offsetX = ( newDimensions. width - originalWidth) / 2
80
+ offsetY = newDimensions. height - originalHeight
81
+ case . bottomRight:
82
+ offsetX = newDimensions. width - originalWidth
83
+ offsetY = newDimensions. height - originalHeight
84
+ case let . custom( x, y) :
85
+ offsetX = x
86
+ offsetY = y
87
+ }
88
+
89
+ let colorSpace = CGColorSpaceCreateDeviceRGB ( )
90
+ let bitmapInfo = CGImageAlphaInfo . premultipliedLast. rawValue
91
+
92
+ // Create padded image
93
+ guard let paddedContext = CGContext ( data: nil , width: newDimensions. width, height: newDimensions. height, bitsPerComponent: 8 , bytesPerRow: 0 , space: colorSpace, bitmapInfo: bitmapInfo) else {
94
+ throw NSError ( domain: " com.google.firebase.ai " , code: 0 , userInfo: [ NSLocalizedDescriptionKey: " Could not create padded image context. " ] )
95
+ }
96
+ paddedContext. draw ( cgImage, in: CGRect ( x: offsetX, y: offsetY, width: originalWidth, height: originalHeight) )
97
+ guard let paddedCGImage = paddedContext. makeImage ( ) , let paddedImageData = paddedCGImage. toData ( ) else {
98
+ throw NSError ( domain: " com.google.firebase.ai " , code: 0 , userInfo: [ NSLocalizedDescriptionKey: " Could not get padded image data. " ] )
99
+ }
100
+
101
+ // Create mask
102
+ guard let maskContext = CGContext ( data: nil , width: newDimensions. width, height: newDimensions. height, bitsPerComponent: 8 , bytesPerRow: 0 , space: CGColorSpaceCreateDeviceGray ( ) , bitmapInfo: CGImageAlphaInfo . none. rawValue) else {
103
+ throw NSError ( domain: " com.google.firebase.ai " , code: 0 , userInfo: [ NSLocalizedDescriptionKey: " Could not create mask context. " ] )
104
+ }
105
+ maskContext. setFillColor ( gray: 1.0 , alpha: 1.0 )
106
+ maskContext. fill ( CGRect ( x: 0 , y: 0 , width: newDimensions. width, height: newDimensions. height) )
107
+ maskContext. setFillColor ( gray: 0.0 , alpha: 1.0 )
108
+ maskContext. fill ( CGRect ( x: offsetX, y: offsetY, width: originalWidth, height: originalHeight) )
109
+ guard let maskCGImage = maskContext. makeImage ( ) , let maskData = maskCGImage. toData ( ) else {
110
+ throw NSError ( domain: " com.google.firebase.ai " , code: 0 , userInfo: [ NSLocalizedDescriptionKey: " Could not get mask data. " ] )
111
+ }
112
+
113
+ return [ ImagenRawImage ( data: paddedImageData) , ImagenMaskReference ( data: maskData) ]
114
+ }
115
+ }
116
+
117
+ extension CGImage {
118
+ static func fromData( _ data: Data ) -> CGImage ? {
119
+ guard let provider = CGDataProvider ( data: data as CFData ) else { return nil }
120
+ return CGImage ( pngDataProviderSource: provider, decode: nil , shouldInterpolate: true , intent: . defaultIntent)
121
+ }
122
+
123
+ func toData( ) -> Data ? {
124
+ guard let mutableData = CFDataCreateMutable ( nil , 0 ) ,
125
+ let destination = CGImageDestinationCreateWithData ( mutableData, " public.png " as CFString , 1 , nil ) else { return nil }
126
+ CGImageDestinationAddImage ( destination, self , nil )
127
+ guard CGImageDestinationFinalize ( destination) else { return nil }
128
+ return mutableData as Data
129
+ }
130
+ }
0 commit comments