Spaces:
Sleeping
Sleeping
File size: 7,644 Bytes
7441163 2890c7d 7441163 9ee7032 8aa68a4 7441163 9ee7032 7441163 e55ab8a 7441163 e55ab8a 7441163 4cf5765 e55ab8a 7441163 4cf5765 7441163 8aa68a4 4cf5765 9ee7032 e55ab8a 7441163 e55ab8a 7441163 e55ab8a 7441163 e55ab8a cbdc574 4cf5765 e55ab8a 426cbf1 46b8f96 7441163 e02d183 46b8f96 e02d183 56cc452 4cf5765 e02d183 8aa68a4 e02d183 56cc452 e02d183 56cc452 e02d183 56cc452 e02d183 56cc452 8aa68a4 7441163 cbdc574 e02d183 7441163 56cc452 e55ab8a 56cc452 e55ab8a 4cf5765 7441163 4cf5765 7441163 4cf5765 7441163 8aa68a4 e02d183 56cc452 e02d183 56cc452 8aa68a4 56cc452 7441163 56cc452 e55ab8a 7441163 e55ab8a 7441163 e55ab8a 7441163 e55ab8a 7441163 e55ab8a 7441163 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | import os
import tempfile
from PIL import Image
import gradio as gr
from google import genai
from google.genai import types
# Helpers
def save_binary_file(file_name, data):
with open(file_name, "wb") as f:
f.write(data)
def generate_edit(prompt, pil_image, api_key, model="gemini-2.0-flash-exp"):
client = genai.Client(api_key=(api_key.strip() if api_key and api_key.strip() != "" else os.environ.get("GEMINI_API_KEY")))
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_img:
image_path = tmp_img.name
pil_image.save(image_path)
files = [client.files.upload(file=image_path)]
contents = [
types.Content(
role="user",
parts=[
types.Part.from_uri(file_uri=files[0].uri, mime_type=files[0].mime_type),
types.Part.from_text(text=prompt),
],
),
]
generate_content_config = types.GenerateContentConfig(
temperature=1,
top_p=0.95,
top_k=40,
max_output_tokens=8192,
response_modalities=["image", "text"],
response_mime_type="text/plain",
)
text_response = ""
image_out_path = None
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_out:
out_path = tmp_out.name
for chunk in client.models.generate_content_stream(
model=model,
contents=contents,
config=generate_content_config,
):
if not chunk.candidates or not chunk.candidates[0].content or not chunk.candidates[0].content.parts:
continue
candidate = chunk.candidates[0].content.parts[0]
if candidate.inline_data:
save_binary_file(out_path, candidate.inline_data.data)
image_out_path = out_path
break
else:
text_response += chunk.text + "\n"
del files
return image_out_path, text_response
def process_image_and_prompt(pil_image, prompt, api_key):
try:
image_path, text_out = generate_edit(prompt, pil_image, api_key)
if image_path:
img = Image.open(image_path)
if img.mode == "RGBA":
img = img.convert("RGB")
return img # Return only the image on success
else:
# If no image generated, raise an error for Gradio popup
raise gr.Error(f"โ ๏ธ Image generation failed: {text_out.strip() if text_out.strip() else 'No specific error message.'}")
except Exception as e:
# Catch any other exceptions and re-raise as Gradio error
raise gr.Error(f"โ Generation failed: {str(e)}")
def reset_inputs(api_key_value=None):
# Reset all inputs, keeping API key unchanged
return None, "", api_key_value or ""
# Styles with gradient background for body/app container
css_style = """
:root { --bg: #0f111a; --panel: #1b1e28; --text: #e9eefc; --muted: #9fb3c8; --accent: #6a8efd; }
body, .app-container {
/* Gradient background instead of solid */
background: linear-gradient(135deg, #2a3a67, #1b254b);
color: var(--text);
}
.header-block { width: 100%; display: flex; justify-content: center; padding: 8px 0; }
.header-gradient { width: 100%; padding: 20px 0; border-radius: 12px; background: linear-gradient(90deg, #6a8efd, #44abc7); text-align: center; }
.header-title { margin: 0; font-size: 2.6rem; font-weight: 900; color: #fff; text-shadow: 0 2px 8px rgba(0,0,0,.25); }
.header-subtitle { margin-top: 6px; font-size: 1.05rem; color: #e8f0ff; }
.main { display: flex; gap: 20px; align-items: stretch; padding: 0 12px; }
.sidebar { width: 320px; background: #1a1e2a; padding: 14px; border-radius: 12px; min-height: 360px; box-shadow: 0 2px 10px rgb(0 0 0 / 0.25); }
.sidebar h2 { color: #8ab4ff; font-size: 1rem; margin: 6px 0; }
.sidebar ul { margin: 0; padding-left: 18px; color: #d6e3ff; line-height: 1.9; }
.sidebar a { color: #97b7ff; text-decoration: none; }
.sidebar a:hover { text-decoration: underline; }
.main-panel { flex: 1; display: flex; flex-direction: column; gap: 12px; }
.section-header { font-weight: 800; font-size: 1.04rem; color: #cbd5e1; margin: 6px 0; }
.layout-two-col {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
}
@media (max-width: 1100px) {
.layout-two-col { grid-template-columns: 1fr; }
}
#output-viewport { display: flex; justify-content: center; align-items: center; min-height: 260px; }
#output-image { display: flex; justify-content: center; align-items: center; }
#output-image img { max-width: 100%; max-height: 420px; object-fit: contain; border-radius: 12px; background: #23252b; }
"""
# Layout
with gr.Blocks(css=css_style) as app:
gr.HTML("""
<div class='header-block'>
<div class='header-gradient'>
<h1 class='header-title'>๐ผ๏ธ Image Editor <span style="font-size:1.1em;">(Powered by Gemini)</span> ๐ฎ</h1>
<div class='header-subtitle'>Step-by-step prompts for image editing</div>
</div>
</div>
""")
with gr.Row():
with gr.Column(scale=3, elem_classes="sidebar"):
gr.Markdown(
"""
<h2>๐ How to Use</h2>
<ul>
<li>Step-by-step prompts guide the editing process.</li>
<li>Upload a PNG image, enter a prompt, then generate.</li>
<li>Keep your Gemini API key secure.</li>
</ul>
<hr>
<h2>๐ API Key</h2>
<div>Get your key here: <a href="https://aistudio.google.com/apikey" target="_blank">Get your Google API key</a></div>
"""
)
with gr.Column(scale=9, elem_classes="main-panel"):
# Step 1 & Step 3 side-by-side
with gr.Row():
with gr.Column():
gr.Markdown("<div class='section-header'>Step 1: Upload Image</div>")
image_input = gr.Image(type="pil", label=None, image_mode="RGBA")
with gr.Column():
gr.Markdown("<div class='section-header'>Step 3: Image Output</div>")
output_image = gr.Image(label=None, show_label=False, type="pil")
# Step 2: Prompt + API
gr.Markdown("<div class='section-header'>Step 2: Enter Editing Prompt</div>")
prompt_input = gr.Textbox(label="Edit Prompt", placeholder="Describe how to edit the image", lines=2)
api_key_input = gr.Textbox(label="Gemini API Key (required)", placeholder="Enter your Gemini API key here", type="password")
with gr.Row():
submit_btn = gr.Button("Generate Edit", elem_classes="gradient-button")
reset_btn = gr.Button("Reset Inputs")
# Note: Status bar elements removed as requested. Errors will now show as Gradio popups.
def on_submit(pil_img, prompt, key):
if not key or key.strip() == "":
raise gr.Error("Gemini API Key is required!")
# process_image_and_prompt now raises gr.Error directly for failures
return process_image_and_prompt(pil_img, prompt, key)
submit_btn.click(
fn=on_submit,
inputs=[image_input, prompt_input, api_key_input],
outputs=[output_image] # Only output the image
)
reset_btn.click(
fn=reset_inputs,
inputs=[api_key_input],
outputs=[image_input, prompt_input, api_key_input] # Remove status_bar from outputs
)
app.launch()
|