Skip to content

Commit c7ceee4

Browse files
Add imagen generation example to VertexAI Sample (#14545)
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
1 parent c0501cb commit c7ceee4

File tree

5 files changed

+176
-1
lines changed

5 files changed

+176
-1
lines changed

FirebaseVertexAI/CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Unreleased
2-
- [feature] The Firebase Vertex AI SDK no longer requires `@preconcurrency` when imported in Swift 6.
2+
- [feature] The Vertex AI SDK no longer requires `@preconcurrency` when imported in Swift 6.
3+
- [feature] The Vertex AI Sample App now includes an image generation example.
34

45
# 11.9.0
56
- [feature] **Public Preview**: Added support for generating images using the
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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 SwiftUI
16+
17+
struct ImagenScreen: View {
18+
@StateObject var viewModel = ImagenViewModel()
19+
20+
enum FocusedField: Hashable {
21+
case message
22+
}
23+
24+
@FocusState
25+
var focusedField: FocusedField?
26+
27+
var body: some View {
28+
VStack {
29+
TextField("Enter a prompt to generate an image", text: $viewModel.userInput)
30+
.focused($focusedField, equals: .message)
31+
.textFieldStyle(.roundedBorder)
32+
.onSubmit {
33+
onGenerateTapped()
34+
}
35+
.padding()
36+
37+
Button("Generate") {
38+
onGenerateTapped()
39+
}
40+
.padding()
41+
if viewModel.inProgress {
42+
Text("Waiting for model response ...")
43+
}
44+
ForEach(viewModel.images, id: \.self) {
45+
Image(uiImage: $0)
46+
.resizable()
47+
.scaledToFill()
48+
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
49+
.aspectRatio(nil, contentMode: .fit)
50+
.clipped()
51+
}
52+
}
53+
.navigationTitle("Imagen sample")
54+
.onAppear {
55+
focusedField = .message
56+
}
57+
}
58+
59+
private func onGenerateTapped() {
60+
focusedField = nil
61+
62+
Task {
63+
await viewModel.generateImage(prompt: viewModel.userInput)
64+
}
65+
}
66+
}
67+
68+
#Preview {
69+
ImagenScreen()
70+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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 FirebaseVertexAI
16+
import Foundation
17+
import OSLog
18+
import SwiftUI
19+
20+
@MainActor
21+
class ImagenViewModel: ObservableObject {
22+
private var logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "generative-ai")
23+
24+
@Published
25+
var userInput: String = ""
26+
27+
@Published
28+
var images = [UIImage]()
29+
30+
@Published
31+
var errorMessage: String?
32+
33+
@Published
34+
var inProgress = false
35+
36+
private let model: ImagenModel
37+
38+
// 1. Initialize the Vertex AI service
39+
private let vertexAI = VertexAI.vertexAI()
40+
41+
init() {
42+
// 2. Configure Imagen settings
43+
let modelName = "imagen-3.0-generate-002"
44+
let safetySettings = ImagenSafetySettings(
45+
safetyFilterLevel: .blockLowAndAbove
46+
)
47+
var generationConfig = ImagenGenerationConfig()
48+
generationConfig.numberOfImages = 4
49+
generationConfig.aspectRatio = .landscape4x3
50+
51+
// 3. Initialize the Imagen model
52+
model = vertexAI.imagenModel(
53+
modelName: modelName,
54+
generationConfig: generationConfig,
55+
safetySettings: safetySettings
56+
)
57+
}
58+
59+
func generateImage(prompt: String) async {
60+
guard !inProgress else {
61+
print("Already generating images...")
62+
return
63+
}
64+
do {
65+
defer {
66+
inProgress = false
67+
}
68+
inProgress = true
69+
// 4. Call generateImages with the text prompt
70+
let response = try await model.generateImages(prompt: prompt)
71+
72+
// 5. Print the reason images were filtered out, if any.
73+
if let filteredReason = response.filteredReason {
74+
print("Image(s) Blocked: \(filteredReason)")
75+
}
76+
77+
// 6. Convert the image data to UIImage for display in the UI
78+
images = response.images.compactMap { UIImage(data: $0.data) }
79+
} catch {
80+
logger.error("Error generating images: \(error)")
81+
}
82+
}
83+
}

FirebaseVertexAI/Sample/VertexAISample.xcodeproj/project.pbxproj

+16
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
886F95E02B17D5010036F07A /* ConversationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E10F562B1112F600C08E95 /* ConversationViewModel.swift */; };
3232
886F95E12B17D5010036F07A /* ConversationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E10F542B1112CA00C08E95 /* ConversationScreen.swift */; };
3333
886F95E32B17D6630036F07A /* GenerativeAIUIComponents in Frameworks */ = {isa = PBXBuildFile; productRef = 886F95E22B17D6630036F07A /* GenerativeAIUIComponents */; };
34+
DEFECAA92D7B4CCD00EF9621 /* ImagenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFECAA72D7B4CCD00EF9621 /* ImagenViewModel.swift */; };
35+
DEFECAAA2D7B4CCD00EF9621 /* ImagenScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFECAA62D7B4CCD00EF9621 /* ImagenScreen.swift */; };
3436
/* End PBXBuildFile section */
3537

3638
/* Begin PBXFileReference section */
@@ -60,6 +62,8 @@
6062
88E10F582B11131900C08E95 /* ChatMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessage.swift; sourceTree = "<group>"; };
6163
88E10F5A2B11133E00C08E95 /* MessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageView.swift; sourceTree = "<group>"; };
6264
88E10F5C2B11135000C08E95 /* BouncingDots.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BouncingDots.swift; sourceTree = "<group>"; };
65+
DEFECAA62D7B4CCD00EF9621 /* ImagenScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagenScreen.swift; sourceTree = "<group>"; };
66+
DEFECAA72D7B4CCD00EF9621 /* ImagenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagenViewModel.swift; sourceTree = "<group>"; };
6367
/* End PBXFileReference section */
6468

6569
/* Begin PBXFrameworksBuildPhase section */
@@ -146,6 +150,7 @@
146150
8848C8262B0D04BC007B434F = {
147151
isa = PBXGroup;
148152
children = (
153+
DEFECAA82D7B4CCD00EF9621 /* ImagenScreen */,
149154
88B8A9352B0FCBA700424728 /* GenerativeAIUIComponents */,
150155
869200B22B879C4F00482873 /* GoogleService-Info.plist */,
151156
8848C8312B0D04BC007B434F /* VertexAISample */,
@@ -279,6 +284,15 @@
279284
path = Screens;
280285
sourceTree = "<group>";
281286
};
287+
DEFECAA82D7B4CCD00EF9621 /* ImagenScreen */ = {
288+
isa = PBXGroup;
289+
children = (
290+
DEFECAA62D7B4CCD00EF9621 /* ImagenScreen.swift */,
291+
DEFECAA72D7B4CCD00EF9621 /* ImagenViewModel.swift */,
292+
);
293+
path = ImagenScreen;
294+
sourceTree = "<group>";
295+
};
282296
/* End PBXGroup section */
283297

284298
/* Begin PBXNativeTarget section */
@@ -374,6 +388,8 @@
374388
886F95E02B17D5010036F07A /* ConversationViewModel.swift in Sources */,
375389
886F95DD2B17D5010036F07A /* MessageView.swift in Sources */,
376390
886F95DC2B17BAEF0036F07A /* PhotoReasoningScreen.swift in Sources */,
391+
DEFECAA92D7B4CCD00EF9621 /* ImagenViewModel.swift in Sources */,
392+
DEFECAAA2D7B4CCD00EF9621 /* ImagenScreen.swift in Sources */,
377393
886F95DB2B17BAEF0036F07A /* PhotoReasoningViewModel.swift in Sources */,
378394
886F95E12B17D5010036F07A /* ConversationScreen.swift in Sources */,
379395
88263BF02B239C09008AB09B /* ErrorView.swift in Sources */,

FirebaseVertexAI/Sample/VertexAISample/ContentView.swift

+5
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ struct ContentView: View {
4545
} label: {
4646
Label("Function Calling", systemImage: "function")
4747
}
48+
NavigationLink {
49+
ImagenScreen()
50+
} label: {
51+
Label("Imagen", systemImage: "camera.circle")
52+
}
4853
}
4954
.navigationTitle("Generative AI Samples")
5055
}

0 commit comments

Comments
 (0)