|  | import gradio as gr | 
					
						
						|  | from gradio_image_annotation import image_annotator | 
					
						
						|  | import fal_client | 
					
						
						|  | from PIL import Image | 
					
						
						|  | import io | 
					
						
						|  | import base64 | 
					
						
						|  | import numpy as np | 
					
						
						|  | import os | 
					
						
						|  |  | 
					
						
						|  | def process_images(annotated_image, second_image, user_api_key=None, progress=gr.Progress()): | 
					
						
						|  | """ | 
					
						
						|  | Process the annotated image and second image using fal API | 
					
						
						|  | """ | 
					
						
						|  |  | 
					
						
						|  | if annotated_image is None: | 
					
						
						|  | return None, "Please provide the first image and draw an annotation box" | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if second_image is None or (isinstance(second_image, np.ndarray) and second_image.size == 0): | 
					
						
						|  | return None, "Please provide the second image" | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if not annotated_image.get("boxes") or len(annotated_image["boxes"]) == 0: | 
					
						
						|  | return None, "Please draw an annotation box on the first image" | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | box = annotated_image["boxes"][0] | 
					
						
						|  | xmin = box.get("xmin") | 
					
						
						|  | ymin = box.get("ymin") | 
					
						
						|  | xmax = box.get("xmax") | 
					
						
						|  | ymax = box.get("ymax") | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | prompt = f"""add the <central object in the second image> in the first image only inside an imaginary box defined by pixels values "xmin": {xmin}, "ymin": {ymin}, "xmax": {xmax}, "ymax": {ymax}. Take care of shadows, lighting, style, and general concept of objects as per the first image.""" | 
					
						
						|  |  | 
					
						
						|  | progress(0.2, desc="Gradio is preparing your images...") | 
					
						
						|  |  | 
					
						
						|  | try: | 
					
						
						|  |  | 
					
						
						|  | original_key = os.environ.get("FAL_KEY", "") | 
					
						
						|  |  | 
					
						
						|  | if user_api_key and user_api_key.strip(): | 
					
						
						|  |  | 
					
						
						|  | os.environ["FAL_KEY"] = user_api_key.strip() | 
					
						
						|  | api_key_source = "user-provided" | 
					
						
						|  | elif original_key: | 
					
						
						|  |  | 
					
						
						|  | api_key_source = "environment" | 
					
						
						|  | else: | 
					
						
						|  |  | 
					
						
						|  | return None, "⚠️ No FAL API key found. Please either:\n1. Duplicate this app and set your FAL_KEY as a secret, or\n2. Enter your FAL API key in the field provided above." | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | first_img = annotated_image["image"] | 
					
						
						|  | if isinstance(first_img, np.ndarray): | 
					
						
						|  |  | 
					
						
						|  | first_img_pil = Image.fromarray(first_img.astype('uint8')) | 
					
						
						|  |  | 
					
						
						|  | img1_bytes = io.BytesIO() | 
					
						
						|  | first_img_pil.save(img1_bytes, format='PNG') | 
					
						
						|  | img1_bytes.seek(0) | 
					
						
						|  | uploaded_file1 = fal_client.upload(img1_bytes.getvalue(), "image/png") | 
					
						
						|  | elif isinstance(first_img, str): | 
					
						
						|  |  | 
					
						
						|  | uploaded_file1 = fal_client.upload_file(first_img) | 
					
						
						|  | else: | 
					
						
						|  |  | 
					
						
						|  | img1_bytes = io.BytesIO() | 
					
						
						|  | first_img.save(img1_bytes, format='PNG') | 
					
						
						|  | img1_bytes.seek(0) | 
					
						
						|  | uploaded_file1 = fal_client.upload(img1_bytes.getvalue(), "image/png") | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if isinstance(second_image, np.ndarray): | 
					
						
						|  | second_img_pil = Image.fromarray(second_image.astype('uint8')) | 
					
						
						|  | img2_bytes = io.BytesIO() | 
					
						
						|  | second_img_pil.save(img2_bytes, format='PNG') | 
					
						
						|  | img2_bytes.seek(0) | 
					
						
						|  | uploaded_file2 = fal_client.upload(img2_bytes.getvalue(), "image/png") | 
					
						
						|  | elif isinstance(second_image, str): | 
					
						
						|  | uploaded_file2 = fal_client.upload_file(second_image) | 
					
						
						|  | else: | 
					
						
						|  | img2_bytes = io.BytesIO() | 
					
						
						|  | second_image.save(img2_bytes, format='PNG') | 
					
						
						|  | img2_bytes.seek(0) | 
					
						
						|  | uploaded_file2 = fal_client.upload(img2_bytes.getvalue(), "image/png") | 
					
						
						|  |  | 
					
						
						|  | progress(0.4, desc="Processing with nano-banana...") | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | def on_queue_update(update): | 
					
						
						|  | if isinstance(update, fal_client.InProgress): | 
					
						
						|  |  | 
					
						
						|  | progress(0.6, desc="nano-banana is working on your image...") | 
					
						
						|  |  | 
					
						
						|  | if hasattr(update, 'logs') and update.logs: | 
					
						
						|  | for log in update.logs: | 
					
						
						|  | print(log.get("message", "")) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | result = fal_client.subscribe( | 
					
						
						|  | "fal-ai/nano-banana/edit", | 
					
						
						|  | arguments={ | 
					
						
						|  | "prompt": prompt, | 
					
						
						|  | "image_urls": [f"{uploaded_file1}", f"{uploaded_file2}"] | 
					
						
						|  | }, | 
					
						
						|  | with_logs=True, | 
					
						
						|  | on_queue_update=on_queue_update, | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | progress(0.95, desc="Finalizing...") | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if result and "images" in result and len(result["images"]) > 0: | 
					
						
						|  | output_url = result["images"][0]["url"] | 
					
						
						|  | description = result.get("description", "Image processed successfully!") | 
					
						
						|  | progress(1.0, desc="Complete!") | 
					
						
						|  | return output_url, description | 
					
						
						|  | else: | 
					
						
						|  | return None, "Failed to generate image. Please check your API key or try again." | 
					
						
						|  |  | 
					
						
						|  | except Exception as e: | 
					
						
						|  | error_message = str(e).lower() | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if "401" in error_message or "unauthorized" in error_message or "api key" in error_message: | 
					
						
						|  | return None, f"⚠️ API Authentication Error: Invalid or missing FAL API key.\n\nPlease either:\n1. Duplicate this app and set your FAL_KEY as a secret, or\n2. Enter your valid FAL API key in the field provided above.\n\nGet your API key at: https://fal.ai" | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | elif "429" in error_message or "rate limit" in error_message: | 
					
						
						|  | return None, "⚠️ Rate limit exceeded. Please wait a moment and try again, or use your own API key for higher limits." | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | elif "500" in error_message or "502" in error_message or "503" in error_message: | 
					
						
						|  | return None, f"⚠️ FAL API server error. The service might be temporarily unavailable.\n\nPlease either:\n1. Try again in a few moments, or\n2. Use your own API key by entering it in the field above.\n\nError details: {str(e)}" | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | else: | 
					
						
						|  | return None, f"⚠️ Error occurred: {str(e)}\n\nIf the error persists, please either:\n1. Duplicate this app and set your FAL_KEY as a secret, or\n2. Enter your FAL API key in the field provided above.\n\nGet your API key at: https://fal.ai" | 
					
						
						|  |  | 
					
						
						|  | finally: | 
					
						
						|  |  | 
					
						
						|  | if user_api_key and user_api_key.strip(): | 
					
						
						|  | if original_key: | 
					
						
						|  | os.environ["FAL_KEY"] = original_key | 
					
						
						|  | else: | 
					
						
						|  | os.environ.pop("FAL_KEY", None) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | with gr.Blocks(theme="ocean") as demo: | 
					
						
						|  | gr.HTML( | 
					
						
						|  | """ | 
					
						
						|  | <h1><center>🎨 Grounded Nano Banana App</center></h1> | 
					
						
						|  |  | 
					
						
						|  | <b>How to use:</b><br> | 
					
						
						|  | 1. Upload or capture the first image and draw a box where you want to place an object<br> | 
					
						
						|  | 2. Upload the second image containing the object you want to insert<br> | 
					
						
						|  | 3. Click "Generate Composite Image" and wait for the Gradio and Nano-Banana to blend the images<br> | 
					
						
						|  |  | 
					
						
						|  | The Gradio app will intelligently place the object from the second image into the boxed area of the first image, | 
					
						
						|  | taking care of lighting, shadows, and proper integration. | 
					
						
						|  | """ | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | with gr.Row(): | 
					
						
						|  | with gr.Column(): | 
					
						
						|  | with gr.Accordion("🔑 API Configuration (Optional)", open=False): | 
					
						
						|  | gr.Markdown( | 
					
						
						|  | """ | 
					
						
						|  | **Note:** If you're experiencing API errors or want to use your own FAL account: | 
					
						
						|  | - Enter your FAL API key below, or | 
					
						
						|  | - [Duplicate this Space](https://huggingface.co/spaces) and set FAL_KEY as a secret | 
					
						
						|  | - Get your API key at [fal.ai](https://fal.ai) | 
					
						
						|  | """ | 
					
						
						|  | ) | 
					
						
						|  | api_key_input = gr.Textbox( | 
					
						
						|  | label="FAL API Key", | 
					
						
						|  | placeholder="Enter your FAL key (optional)", | 
					
						
						|  | type="password", | 
					
						
						|  | interactive=True, | 
					
						
						|  | info="Your key will be used only for this session and won't be stored" | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | with gr.Row(): | 
					
						
						|  | with gr.Column(scale=1): | 
					
						
						|  | with gr.Row(): | 
					
						
						|  | with gr.Column(scale=1): | 
					
						
						|  | gr.Markdown("### Step 1: Annotate First Image") | 
					
						
						|  |  | 
					
						
						|  | from gradio_image_annotation import image_annotator | 
					
						
						|  |  | 
					
						
						|  | first_image = image_annotator( | 
					
						
						|  |  | 
					
						
						|  | label="Draw a box where you want to place the object", | 
					
						
						|  | image_type="pil", | 
					
						
						|  | single_box=True, | 
					
						
						|  | disable_edit_boxes=False, | 
					
						
						|  | show_download_button=False, | 
					
						
						|  | show_share_button=False, | 
					
						
						|  | box_thickness=3, | 
					
						
						|  | box_selected_thickness=4, | 
					
						
						|  |  | 
					
						
						|  | show_label=True, | 
					
						
						|  | image_mode="RGB", | 
					
						
						|  |  | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | with gr.Column(scale=1): | 
					
						
						|  | gr.Markdown("### Step 2: Upload Second Image") | 
					
						
						|  |  | 
					
						
						|  | second_image = gr.Image( | 
					
						
						|  | label="Image containing the object to insert", | 
					
						
						|  | type="numpy", | 
					
						
						|  | height=400, | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | generate_btn = gr.Button("🚀 Generate Composite Image", variant="primary", size="lg") | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | with gr.Column(): | 
					
						
						|  | output_image = gr.Image( | 
					
						
						|  | label="Generated Composite Image", | 
					
						
						|  | type="filepath", | 
					
						
						|  | height=500, | 
					
						
						|  | ) | 
					
						
						|  | status_text = gr.Textbox( | 
					
						
						|  | label="Status", | 
					
						
						|  | placeholder="Results will appear here...", | 
					
						
						|  | lines=3, | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | with gr.Accordion("ℹ️ Tips for Best Results", open=False): | 
					
						
						|  | gr.Markdown( | 
					
						
						|  | """ | 
					
						
						|  | - **Box Placement**: Draw the box exactly where you want the object to appear | 
					
						
						|  | - **Image Quality**: Use high-resolution images for better results | 
					
						
						|  | - **Object Selection**: The second image should clearly show the object you want to insert | 
					
						
						|  | - **Lighting**: Images with similar lighting conditions work best | 
					
						
						|  | - **Processing Time**: Generation typically takes 10-30 seconds | 
					
						
						|  | - **API Key**: If you encounter errors, try using your own FAL API key | 
					
						
						|  | """ | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | generate_btn.click( | 
					
						
						|  | fn=process_images, | 
					
						
						|  | inputs=[first_image, second_image, api_key_input], | 
					
						
						|  | outputs=[output_image, status_text], | 
					
						
						|  | show_progress=True, | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | if __name__ == "__main__": | 
					
						
						|  | demo.launch(debug=False) |