AhmadMustafa commited on
Commit
8bd49c7
Β·
1 Parent(s): d511f58

add: outpainting app

Browse files
Files changed (2) hide show
  1. app.py +220 -0
  2. requirements.txt +2 -0
app.py ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import os
3
+
4
+ import gradio as gr
5
+ from google import genai
6
+ from PIL import Image
7
+
8
+ # Initialize the Gemini client
9
+ client = genai.Client(api_key=os.environ.get("GEMINI_API_KEY"))
10
+
11
+
12
+ def add_padding(image, side, padding_size):
13
+ """Add black padding to specified side of image"""
14
+ width, height = image.size
15
+ if side == "left":
16
+ new_image = Image.new("RGB", (width + padding_size, height), color="black")
17
+ new_image.paste(image, (padding_size, 0))
18
+ elif side == "right":
19
+ new_image = Image.new("RGB", (width + padding_size, height), color="black")
20
+ new_image.paste(image, (0, 0))
21
+ elif side == "top":
22
+ new_image = Image.new("RGB", (width, height + padding_size), color="black")
23
+ new_image.paste(image, (0, padding_size))
24
+ elif side == "bottom":
25
+ new_image = Image.new("RGB", (width, height + padding_size), color="black")
26
+ new_image.paste(image, (0, 0))
27
+ return new_image
28
+
29
+
30
+ def preview_padding(image, side, padding_size):
31
+ """Add black padding to image and show preview"""
32
+ if image is None:
33
+ return None, None, "No image to pad", ""
34
+ if not isinstance(image, Image.Image):
35
+ image = Image.fromarray(image)
36
+
37
+ padded_image = add_padding(image, side, padding_size)
38
+ width, height = padded_image.size
39
+ info_text = (
40
+ f"Padded size: {width} x {height} pixels ({padding_size}px added to {side})"
41
+ )
42
+
43
+ direction_text = {
44
+ "left": "left side",
45
+ "right": "right side",
46
+ "top": "top",
47
+ "bottom": "bottom",
48
+ }
49
+ default_prompt = (
50
+ f"This image has a black region on the {direction_text[side]}. "
51
+ f"Please naturally extend and outpaint the image to fill in the black area on the {direction_text[side]}. "
52
+ f"Make it look seamless and consistent with the existing image content, matching the style, colors, and content naturally. "
53
+ f"Do not leave any black areas - completely fill in the {direction_text[side]} region."
54
+ )
55
+ return padded_image, side, info_text, default_prompt
56
+
57
+
58
+ def outpaint_with_nano_banana(padded_image, side, custom_prompt):
59
+ """Use Nano Banana to outpaint the padded image"""
60
+ if padded_image is None:
61
+ return None, "No padded image to outpaint"
62
+ if side is None:
63
+ return None, "No side information available"
64
+
65
+ if custom_prompt and custom_prompt.strip():
66
+ prompt = custom_prompt
67
+ else:
68
+ direction_text = {
69
+ "left": "left side",
70
+ "right": "right side",
71
+ "top": "top",
72
+ "bottom": "bottom",
73
+ }
74
+ prompt = (
75
+ f"This image has a black region on the {direction_text[side]}. "
76
+ f"Please naturally extend and outpaint the image to fill in the black area on the {direction_text[side]}."
77
+ )
78
+
79
+ try:
80
+ response = client.models.generate_content(
81
+ model="gemini-2.5-flash-image", contents=[padded_image, prompt]
82
+ )
83
+ for part in response.candidates[0].content.parts:
84
+ if getattr(part, "inline_data", None):
85
+ result_image = Image.open(io.BytesIO(part.inline_data.data))
86
+ w, h = result_image.size
87
+ return result_image, f"Output size: {w} x {h} pixels"
88
+ w, h = padded_image.size
89
+ return padded_image, f"Output size: {w} x {h} pixels (no result from API)"
90
+ except Exception as e:
91
+ return padded_image, f"Error: {str(e)}"
92
+
93
+
94
+ def get_image_info(image):
95
+ """Get image size information"""
96
+ if image is None:
97
+ return "No image uploaded"
98
+ if not isinstance(image, Image.Image):
99
+ image = Image.fromarray(image)
100
+ w, h = image.size
101
+ return f"Image size: {w} x {h} pixels"
102
+
103
+
104
+ # Create Gradio interface
105
+ with gr.Blocks(title="Nano Banana Outpainting") as demo:
106
+ gr.Markdown("# 🍌 Nano Banana Outpainting App")
107
+ gr.Markdown(
108
+ "Upload an image and click a direction to add black padding. Review the padding, then click 'Outpaint' to fill it in with AI."
109
+ )
110
+
111
+ # State variables
112
+ padded_state = gr.State(None)
113
+ side_state = gr.State(None)
114
+
115
+ # ─────────────────────────────
116
+ # ROW 1: The three image panes
117
+ # ─────────────────────────────
118
+ with gr.Row():
119
+ # LEFT: Upload + info
120
+ with gr.Column():
121
+ input_image = gr.Image(label="Upload Image", type="pil")
122
+ image_info = gr.Textbox(
123
+ label="Input Image Info", interactive=False, value="No image uploaded"
124
+ )
125
+
126
+ # MIDDLE: Padded preview + info
127
+ with gr.Column():
128
+ padded_preview = gr.Image(
129
+ label="Padded Image (with black pixels)", type="pil", interactive=False
130
+ )
131
+ padded_info = gr.Textbox(
132
+ label="Padded Image Info", interactive=False, value="No padding applied"
133
+ )
134
+
135
+ # RIGHT: Outpainted result + info
136
+ with gr.Column():
137
+ output_image = gr.Image(
138
+ label="Outpainted Result", type="pil", interactive=False
139
+ )
140
+ output_info = gr.Textbox(
141
+ label="Output Image Info", interactive=False, value="No output yet"
142
+ )
143
+
144
+ # ─────────────────────────────
145
+ # ROW 2: Step 1 and Step 2 side-by-side
146
+ # ─────────────────────────────
147
+ with gr.Row():
148
+ # STEP 1 PANEL (left)
149
+ with gr.Column(scale=1, min_width=360):
150
+ gr.Markdown("### Step 1: Choose direction to add black padding")
151
+ # Padding size belongs to Step 1
152
+ gr.Markdown("**Padding Size:**")
153
+ padding_slider = gr.Slider(
154
+ minimum=50,
155
+ maximum=500,
156
+ value=100,
157
+ step=10,
158
+ label="Black Padding Size (pixels)",
159
+ info="Adjust how many pixels to add",
160
+ )
161
+ with gr.Row():
162
+ btn_top = gr.Button("⬆️ Top", size="lg")
163
+ with gr.Row():
164
+ btn_left = gr.Button("⬅️ Left", size="lg")
165
+ btn_right = gr.Button("➑️ Right", size="lg")
166
+ with gr.Row():
167
+ btn_bottom = gr.Button("⬇️ Bottom", size="lg")
168
+
169
+ gr.Markdown("### Step 3: Outpaint the black region")
170
+ btn_outpaint = gr.Button(
171
+ "🎨 Outpaint with Nano Banana", size="lg", variant="primary"
172
+ )
173
+
174
+ # STEP 2 PANEL (right)
175
+ with gr.Column(scale=1, min_width=360):
176
+ gr.Markdown("### Step 2: Customize Prompt (Optional)")
177
+ prompt_textbox = gr.Textbox(
178
+ label="Outpainting Prompt",
179
+ placeholder="Click a direction button to see the default prompt, then edit if desired",
180
+ value="",
181
+ lines=6,
182
+ info="Edit this prompt to control what Nano Banana generates.",
183
+ show_label=True,
184
+ )
185
+
186
+ # Wire events
187
+ input_image.change(fn=get_image_info, inputs=input_image, outputs=image_info)
188
+
189
+ btn_top.click(
190
+ fn=lambda img, pad_size: preview_padding(img, "top", pad_size),
191
+ inputs=[input_image, padding_slider],
192
+ outputs=[padded_preview, side_state, padded_info, prompt_textbox],
193
+ ).then(fn=lambda img: img, inputs=padded_preview, outputs=padded_state)
194
+
195
+ btn_bottom.click(
196
+ fn=lambda img, pad_size: preview_padding(img, "bottom", pad_size),
197
+ inputs=[input_image, padding_slider],
198
+ outputs=[padded_preview, side_state, padded_info, prompt_textbox],
199
+ ).then(fn=lambda img: img, inputs=padded_preview, outputs=padded_state)
200
+
201
+ btn_left.click(
202
+ fn=lambda img, pad_size: preview_padding(img, "left", pad_size),
203
+ inputs=[input_image, padding_slider],
204
+ outputs=[padded_preview, side_state, padded_info, prompt_textbox],
205
+ ).then(fn=lambda img: img, inputs=padded_preview, outputs=padded_state)
206
+
207
+ btn_right.click(
208
+ fn=lambda img, pad_size: preview_padding(img, "right", pad_size),
209
+ inputs=[input_image, padding_slider],
210
+ outputs=[padded_preview, side_state, padded_info, prompt_textbox],
211
+ ).then(fn=lambda img: img, inputs=padded_preview, outputs=padded_state)
212
+
213
+ btn_outpaint.click(
214
+ fn=outpaint_with_nano_banana,
215
+ inputs=[padded_state, side_state, prompt_textbox],
216
+ outputs=[output_image, output_info],
217
+ )
218
+
219
+ if __name__ == "__main__":
220
+ demo.launch(debug=True)
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ pillow
2
+ google-genai