aaron-official commited on
Commit
d5aa3ec
Β·
1 Parent(s): 10f711f

Add application file

Browse files
Files changed (1) hide show
  1. app.py +540 -0
app.py ADDED
@@ -0,0 +1,540 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from config.settings import (
3
+ OPENAI_API_KEY, ANTHROPIC_API_KEY, REMOVE_BG_API_KEY, DEEPSEEK_API_KEY,
4
+ GRADIO_SERVER_NAME, GRADIO_SERVER_PORT
5
+ )
6
+ from config.logging_config import setup_logging
7
+ from src.image_processing import ImageProcessor
8
+ from src.ai_image_generator import AIImageGenerator
9
+ from src.background_removal import BackgroundRemover
10
+ from src.image_analysis import ImageAnalysis
11
+ from src.custom_filters import apply_custom_filter
12
+ from src.resize_crop import resize_crop_image
13
+ import logging
14
+ import io
15
+ import zipfile
16
+ import tempfile
17
+ from PIL import Image
18
+
19
+ setup_logging()
20
+ logger = logging.getLogger(__name__)
21
+
22
+ import gradio as gr
23
+
24
+ SUPPORTED_FORMATS = [
25
+ "JPEG", "JPG", "PNG", "BMP", "TIFF", "TIF", "WEBP", "GIF",
26
+ "ICO", "EPS", "PDF", "PSD", "SVG", "HEIC", "AVIF", "JXL"
27
+ ]
28
+ ENHANCEMENT_OPTIONS = [
29
+ "AI Super Resolution", "Noise Reduction", "Color Enhancement", "Brightness/Contrast", "Sharpening", "HDR Effect", "Vintage Filter", "Black & White", "Sepia", "Vignette", "Blur Background"
30
+ ]
31
+
32
+ processor = ImageProcessor()
33
+ ai_generator = AIImageGenerator(openai_key=OPENAI_API_KEY, anthropic_key=ANTHROPIC_API_KEY)
34
+ bg_remover = BackgroundRemover()
35
+
36
+ # Restore AI_MODELS and BG_REMOVAL_SERVICES constants
37
+ AI_MODELS = {
38
+ "OpenAI DALL-E 3": "dall-e-3",
39
+ "OpenAI DALL-E 2": "dall-e-2",
40
+ "Anthropic Claude (via API)": "claude-3-5-sonnet-20241022",
41
+ "DeepSeek": "deepseek-chat"
42
+ }
43
+ BG_REMOVAL_SERVICES = {
44
+ "Remove.bg": "removebg",
45
+ "Removal.ai": "removelai",
46
+ "Local rembg": "local",
47
+ "Clipdrop": "clipdrop"
48
+ }
49
+
50
+ # Custom CSS for better styling
51
+ custom_css = """
52
+ .gradio-container {
53
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
54
+ background: #f4f6fb;
55
+ color: #222 !important;
56
+ }
57
+ .tab-nav {
58
+ background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
59
+ }
60
+ .tab-nav button {
61
+ background: transparent !important;
62
+ border: none !important;
63
+ color: #fff !important;
64
+ font-weight: 600 !important;
65
+ font-size: 1.1rem !important;
66
+ text-shadow: 0 1px 2px rgba(0,0,0,0.15);
67
+ }
68
+ .tab-nav button.selected {
69
+ background: rgba(255,255,255,0.2) !important;
70
+ border-radius: 8px !important;
71
+ }
72
+ .upload-container {
73
+ border: 2px dashed #667eea;
74
+ border-radius: 12px;
75
+ padding: 20px;
76
+ text-align: center;
77
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
78
+ color: #222 !important;
79
+ font-size: 1.05rem;
80
+ transition: all 0.3s ease;
81
+ }
82
+ .upload-container:hover {
83
+ border-color: #764ba2;
84
+ transform: translateY(-2px);
85
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.15);
86
+ }
87
+ .feature-card {
88
+ background: #fff;
89
+ border-radius: 12px;
90
+ padding: 20px;
91
+ box-shadow: 0 4px 15px rgba(0,0,0,0.1);
92
+ margin: 10px 0;
93
+ border-left: 4px solid #667eea;
94
+ color: #222 !important;
95
+ }
96
+ .success-message {
97
+ color: #22c55e;
98
+ font-weight: 600;
99
+ font-size: 1.05rem;
100
+ }
101
+ .error-message {
102
+ color: #ef4444;
103
+ font-weight: 600;
104
+ font-size: 1.05rem;
105
+ }
106
+ .gr-textbox label, .gr-dropdown label, .gr-slider label, .gr-number label, .gr-checkbox label {
107
+ color: #222 !important;
108
+ font-weight: 600;
109
+ font-size: 1.05rem;
110
+ }
111
+ .gr-textbox input, .gr-dropdown select, .gr-slider input, .gr-number input, .gr-checkbox input {
112
+ color: #222 !important;
113
+ background: #f8fafc !important;
114
+ border-radius: 6px !important;
115
+ border: 1px solid #c3cfe2 !important;
116
+ font-size: 1.05rem;
117
+ }
118
+ .gr-button {
119
+ font-size: 1.08rem !important;
120
+ font-weight: 600 !important;
121
+ }
122
+ """
123
+
124
+ # Android icon sizes (density: size in px)
125
+ ANDROID_ICON_SIZES = {
126
+ "mdpi": 48,
127
+ "hdpi": 72,
128
+ "xhdpi": 96,
129
+ "xxhdpi": 144,
130
+ "xxxhdpi": 192,
131
+ }
132
+
133
+ def make_android_icons_zip(image: Image.Image) -> str:
134
+ import tempfile
135
+ with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp:
136
+ with zipfile.ZipFile(tmp, mode="w") as zf:
137
+ for density, size in ANDROID_ICON_SIZES.items():
138
+ icon = image.resize((size, size), Image.LANCZOS)
139
+ icon_bytes = io.BytesIO()
140
+ icon.save(icon_bytes, format="PNG")
141
+ icon_bytes.seek(0)
142
+ zf.writestr(f"ic_launcher_{density}.png", icon_bytes.read())
143
+ return tmp.name # Return the file path for gr.File
144
+
145
+ # Main Gradio Interface
146
+ with gr.Blocks(css=custom_css, title="🎨 Advanced Image Processing Suite", theme=gr.themes.Soft()) as demo:
147
+
148
+ gr.HTML("""
149
+ <div style="text-align: center; padding: 20px; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 12px; margin-bottom: 20px;">
150
+ <h1 style="margin: 0; font-size: 2.5rem; font-weight: 700;">🎨 Advanced Image Processing Suite</h1>
151
+ <p style="margin: 10px 0 0 0; font-size: 1.1rem; opacity: 0.9;">Professional-grade image processing, AI generation, and enhancement tools</p>
152
+ </div>
153
+ """)
154
+
155
+ with gr.Tabs() as tabs:
156
+ # Tab 1: Format
157
+ with gr.Tab("πŸ–ΌοΈ Format", elem_classes="feature-card"):
158
+ gr.Markdown("### Convert images between multiple formats with advanced options")
159
+
160
+ with gr.Row():
161
+ with gr.Column(scale=1):
162
+ conv_input = gr.Image(label="πŸ“Ž Upload Image", type="filepath", elem_classes="upload-container")
163
+ conv_format = gr.Dropdown(
164
+ choices=SUPPORTED_FORMATS,
165
+ value="PNG",
166
+ label="🎯 Target Format"
167
+ )
168
+ conv_quality = gr.Slider(
169
+ minimum=10, maximum=100, value=95,
170
+ label="πŸŽ›οΈ Quality (for JPEG/WebP)", step=5
171
+ )
172
+ conv_btn = gr.Button("πŸš€ Convert Image", variant="primary")
173
+
174
+ with gr.Column(scale=1):
175
+ conv_output = gr.File(label="πŸ“₯ Download Converted Image")
176
+ conv_status = gr.Textbox(label="πŸ“Š Status", interactive=False)
177
+
178
+ conv_btn.click(
179
+ fn=lambda img, fmt, qual: processor.convert_image(img, fmt, qual) if img else (None, "❌ Please upload an image"),
180
+ inputs=[conv_input, conv_format, conv_quality],
181
+ outputs=[conv_output, conv_status]
182
+ )
183
+
184
+ # Tab 2: AI Gen
185
+ with gr.Tab("πŸ€– AI Gen", elem_classes="feature-card"):
186
+ gr.Markdown("### Generate stunning images using state-of-the-art AI models")
187
+
188
+ with gr.Row():
189
+ with gr.Column():
190
+ gr.Markdown("#### πŸ”‘ API Configuration")
191
+ openai_key = gr.Textbox(
192
+ label="OpenAI API Key",
193
+ type="password",
194
+ placeholder="sk-..."
195
+ )
196
+ anthropic_key = gr.Textbox(
197
+ label="Anthropic API Key",
198
+ type="password",
199
+ placeholder="sk-ant-..."
200
+ )
201
+ deepseek_key = gr.Textbox(
202
+ label="DeepSeek API Key",
203
+ type="password",
204
+ placeholder="sk-..."
205
+ )
206
+
207
+ save_keys_btn = gr.Button("πŸ’Ύ Save API Keys", size="sm")
208
+ key_status = gr.Textbox(label="πŸ” Key Status", interactive=False)
209
+
210
+ with gr.Row():
211
+ with gr.Column():
212
+ ai_prompt = gr.Textbox(
213
+ label="🎨 Image Prompt",
214
+ placeholder="A serene landscape with mountains and a lake at sunset...",
215
+ lines=3
216
+ )
217
+ ai_model = gr.Dropdown(
218
+ choices=list(AI_MODELS.keys()),
219
+ value="OpenAI DALL-E 3",
220
+ label="🧠 AI Model"
221
+ )
222
+ ai_size = gr.Dropdown(
223
+ choices=["256x256", "512x512", "1024x1024", "1792x1024", "1024x1792"],
224
+ value="1024x1024",
225
+ label="πŸ“ Image Size"
226
+ )
227
+
228
+ generate_btn = gr.Button("✨ Generate Image", variant="primary")
229
+
230
+ with gr.Column():
231
+ ai_output = gr.Image(label="πŸ–ΌοΈ Generated Image")
232
+ ai_status = gr.Textbox(label="πŸ“Š Generation Status", interactive=False)
233
+
234
+ # API key saving (refactored)
235
+ def save_api_keys(openai, anthropic, deepseek):
236
+ ai_generator.openai_key = openai
237
+ ai_generator.anthropic_key = anthropic
238
+ # DeepSeek can be added to ai_generator if implemented
239
+ saved_keys = []
240
+ if openai:
241
+ saved_keys.append("OpenAI")
242
+ if anthropic:
243
+ saved_keys.append("Anthropic")
244
+ if deepseek:
245
+ saved_keys.append("DeepSeek")
246
+ if saved_keys:
247
+ return f"βœ… Saved keys for: {', '.join(saved_keys)}"
248
+ return "❌ No keys provided"
249
+
250
+ save_keys_btn.click(
251
+ fn=save_api_keys,
252
+ inputs=[openai_key, anthropic_key, deepseek_key],
253
+ outputs=key_status
254
+ )
255
+
256
+ # Image generation (fixed: pass API keys directly)
257
+ def generate_image(prompt, model, size, openai, anthropic):
258
+ if not prompt:
259
+ return None, "❌ Please enter a prompt"
260
+ if "OpenAI" in model:
261
+ model_name = AI_MODELS[model]
262
+ temp_gen = AIImageGenerator(openai_key=openai, anthropic_key=anthropic)
263
+ return temp_gen.generate_image_openai(prompt, model_name, size)
264
+ elif "Anthropic" in model:
265
+ temp_gen = AIImageGenerator(openai_key=openai, anthropic_key=anthropic)
266
+ return temp_gen.generate_image_anthropic(prompt)
267
+ else:
268
+ return None, f"❌ {model} not yet implemented"
269
+
270
+ generate_btn.click(
271
+ fn=generate_image,
272
+ inputs=[ai_prompt, ai_model, ai_size, openai_key, anthropic_key],
273
+ outputs=[ai_output, ai_status]
274
+ )
275
+
276
+ # Tab 3: Enhance
277
+ with gr.Tab("✨ Enhance", elem_classes="feature-card"):
278
+ gr.Markdown("### Enhance your images with professional-grade filters and AI")
279
+
280
+ with gr.Row():
281
+ with gr.Column():
282
+ enhance_input = gr.Image(label="πŸ“Ž Upload Image to Enhance", type="filepath")
283
+ enhance_type = gr.Dropdown(
284
+ choices=ENHANCEMENT_OPTIONS,
285
+ value="Color Enhancement",
286
+ label="🎭 Enhancement Type"
287
+ )
288
+ enhance_intensity = gr.Slider(
289
+ minimum=0.1, maximum=2.0, value=1.0, step=0.1,
290
+ label="πŸŽ›οΈ Enhancement Intensity"
291
+ )
292
+ enhance_btn = gr.Button("πŸš€ Enhance Image", variant="primary")
293
+
294
+ with gr.Column():
295
+ enhance_output = gr.Image(label="✨ Enhanced Image")
296
+ enhance_status = gr.Textbox(label="πŸ“Š Enhancement Status", interactive=False)
297
+
298
+ enhance_btn.click(
299
+ fn=lambda img, enh_type, intensity: processor.enhance_image(img, enh_type, intensity) if img else (None, "❌ Please upload an image"),
300
+ inputs=[enhance_input, enhance_type, enhance_intensity],
301
+ outputs=[enhance_output, enhance_status]
302
+ )
303
+
304
+ # Tab 4: BG Remove
305
+ with gr.Tab("🎭 BG Remove", elem_classes="feature-card"):
306
+ gr.Markdown("### Remove backgrounds instantly with AI-powered tools")
307
+
308
+ with gr.Row():
309
+ with gr.Column():
310
+ gr.Markdown("#### πŸ”‘ API Keys for Premium Services")
311
+ removebg_key = gr.Textbox(
312
+ label="Remove.bg API Key",
313
+ type="password",
314
+ placeholder="Your Remove.bg API key"
315
+ )
316
+ save_bg_key_btn = gr.Button("πŸ’Ύ Save Remove.bg Key", size="sm")
317
+ bg_key_status = gr.Textbox(label="πŸ” API Status", interactive=False)
318
+
319
+ with gr.Row():
320
+ with gr.Column():
321
+ bg_input = gr.Image(label="πŸ“Ž Upload Image", type="filepath")
322
+ bg_service = gr.Dropdown(
323
+ choices=list(BG_REMOVAL_SERVICES.keys()),
324
+ value="Local rembg",
325
+ label="πŸ› οΈ Background Removal Service"
326
+ )
327
+ bg_btn = gr.Button("🎭 Remove Background", variant="primary")
328
+
329
+ with gr.Column():
330
+ bg_output = gr.Image(label="πŸ–ΌοΈ Result (Transparent Background)")
331
+ bg_status = gr.Textbox(label="πŸ“Š Processing Status", interactive=False)
332
+
333
+ # Save background removal API key (refactored)
334
+ def save_bg_api_key(key):
335
+ bg_remover.removebg_key = key
336
+ if key:
337
+ return "βœ… Remove.bg API key saved"
338
+ return "❌ No key provided"
339
+ save_bg_key_btn.click(
340
+ fn=save_bg_api_key,
341
+ inputs=removebg_key,
342
+ outputs=bg_key_status
343
+ )
344
+ # Background removal (fixed: pass Remove.bg key directly)
345
+ def remove_bg(img, service, removebg):
346
+ if not img:
347
+ return None, "❌ Please upload an image"
348
+ service_key = BG_REMOVAL_SERVICES.get(service, "local")
349
+ if service_key == "local":
350
+ return bg_remover.remove_local(img)
351
+ elif service_key == "removebg":
352
+ temp_remover = BackgroundRemover(removebg_key=removebg)
353
+ return temp_remover.remove_with_removebg(img)
354
+ else:
355
+ return None, f"❌ {service} not implemented"
356
+ bg_btn.click(
357
+ fn=remove_bg,
358
+ inputs=[bg_input, bg_service, removebg_key],
359
+ outputs=[bg_output, bg_status]
360
+ )
361
+
362
+ # Tab 5: Batch
363
+ with gr.Tab("πŸ“¦ Batch", elem_classes="feature-card"):
364
+ gr.Markdown("### Process multiple images simultaneously")
365
+
366
+ with gr.Row():
367
+ with gr.Column():
368
+ batch_files = gr.Files(label="πŸ“Ž Upload Multiple Images", file_types=["image"])
369
+ batch_operation = gr.Dropdown(
370
+ choices=["Format Conversion", "Enhancement", "Background Removal"],
371
+ value="Format Conversion",
372
+ label="πŸ”§ Batch Operation"
373
+ )
374
+ batch_btn = gr.Button("πŸš€ Process Batch", variant="primary")
375
+
376
+ with gr.Column():
377
+ batch_output = gr.Files(label="πŸ“₯ Download Processed Images")
378
+ batch_status = gr.Textbox(label="πŸ“Š Batch Status", interactive=False, lines=5)
379
+
380
+ # Batch processing (refactored)
381
+ def process_batch(files, operation):
382
+ if not files:
383
+ return None, "❌ Please upload images for batch processing"
384
+ results = []
385
+ status_messages = []
386
+ for i, file in enumerate(files):
387
+ try:
388
+ if operation == "Format Conversion":
389
+ result, msg = processor.convert_image(file.name, "PNG")
390
+ elif operation == "Enhancement":
391
+ result, msg = processor.enhance_image(file.name, "Color Enhancement")
392
+ elif operation == "Background Removal":
393
+ result, msg = bg_remover.remove_local(file.name)
394
+ if result:
395
+ results.append(result)
396
+ status_messages.append(f"File {i+1}: {msg}")
397
+ except Exception as e:
398
+ status_messages.append(f"File {i+1}: ❌ Error - {str(e)}")
399
+ return results if results else None, "\n".join(status_messages)
400
+ batch_btn.click(
401
+ fn=process_batch,
402
+ inputs=[batch_files, batch_operation],
403
+ outputs=[batch_output, batch_status]
404
+ )
405
+
406
+ # Tab 6: Tools
407
+ with gr.Tab("πŸ”¬ Tools", elem_classes="feature-card"):
408
+ gr.Markdown("### Professional image analysis and specialized processing")
409
+
410
+ with gr.Tabs():
411
+ with gr.Tab("πŸ” Image Analysis"):
412
+ with gr.Row():
413
+ with gr.Column():
414
+ analysis_input = gr.Image(label="πŸ“Ž Upload Image for Analysis", type="filepath")
415
+ analysis_btn = gr.Button("πŸ”¬ Analyze Image", variant="primary")
416
+
417
+ with gr.Column():
418
+ analysis_output = gr.JSON(label="πŸ“Š Image Analysis Results")
419
+
420
+ # Advanced Tools: Image Analysis (refactored)
421
+ def analyze_image(img_path):
422
+ return ImageAnalysis.analyze_image(img_path)
423
+ analysis_btn.click(
424
+ fn=analyze_image,
425
+ inputs=analysis_input,
426
+ outputs=analysis_output
427
+ )
428
+
429
+ with gr.Tab("🎨 Custom Filters"):
430
+ with gr.Row():
431
+ with gr.Column():
432
+ filter_input = gr.Image(label="πŸ“Ž Upload Image", type="filepath")
433
+
434
+ # Custom filter controls
435
+ brightness = gr.Slider(-100, 100, 0, label="β˜€οΈ Brightness")
436
+ contrast = gr.Slider(-100, 100, 0, label="πŸŒ“ Contrast")
437
+ saturation = gr.Slider(-100, 100, 0, label="🎨 Saturation")
438
+ hue_shift = gr.Slider(-180, 180, 0, label="🌈 Hue Shift")
439
+
440
+ apply_filter_btn = gr.Button("🎭 Apply Custom Filter", variant="primary")
441
+
442
+ with gr.Column():
443
+ filter_output = gr.Image(label="πŸ–ΌοΈ Filtered Image")
444
+ filter_status = gr.Textbox(label="πŸ“Š Filter Status", interactive=False)
445
+
446
+ # Advanced Tools: Custom Filters (refactored)
447
+ apply_filter_btn.click(
448
+ fn=apply_custom_filter,
449
+ inputs=[filter_input, brightness, contrast, saturation, hue_shift],
450
+ outputs=[filter_output, filter_status]
451
+ )
452
+
453
+ with gr.Tab("πŸ“ Resize & Crop"):
454
+ with gr.Row():
455
+ with gr.Column():
456
+ resize_input = gr.Image(label="πŸ“Ž Upload Image", type="filepath")
457
+
458
+ resize_mode = gr.Radio(
459
+ choices=["Resize", "Crop", "Smart Crop", "Canvas Resize"],
460
+ value="Resize",
461
+ label="πŸ”§ Operation Mode"
462
+ )
463
+
464
+ new_width = gr.Number(label="πŸ“ Width", value=800)
465
+ new_height = gr.Number(label="πŸ“ Height", value=600)
466
+
467
+ maintain_ratio = gr.Checkbox(label="πŸ”’ Maintain Aspect Ratio", value=True)
468
+ resize_quality = gr.Dropdown(
469
+ choices=["NEAREST", "LANCZOS", "BILINEAR", "BICUBIC"],
470
+ value="LANCZOS",
471
+ label="🎯 Resize Quality"
472
+ )
473
+
474
+ resize_btn = gr.Button("πŸ“ Process Image", variant="primary")
475
+
476
+ with gr.Column():
477
+ resize_output = gr.Image(label="πŸ–ΌοΈ Processed Image")
478
+ resize_status = gr.Textbox(label="πŸ“Š Processing Status", interactive=False)
479
+
480
+ # Advanced Tools: Resize & Crop (refactored)
481
+ resize_btn.click(
482
+ fn=resize_crop_image,
483
+ inputs=[resize_input, resize_mode, new_width, new_height, maintain_ratio, resize_quality],
484
+ outputs=[resize_output, resize_status]
485
+ )
486
+
487
+ # Tab 7: Android Icons
488
+ with gr.Tab("πŸ“± Android Icons", elem_classes="feature-card"):
489
+ gr.Markdown("### Generate all Android app icon sizes and download as a ZIP")
490
+ android_icon_input = gr.Image(type="pil", label="Android Icon Source")
491
+ android_icon_btn = gr.Button("Generate Android Icons")
492
+ android_icon_zip = gr.File(label="Download Icons ZIP")
493
+
494
+ def handle_android_icon(image):
495
+ if image is None:
496
+ return None
497
+ return make_android_icons_zip(image)
498
+
499
+ android_icon_btn.click(
500
+ fn=handle_android_icon,
501
+ inputs=android_icon_input,
502
+ outputs=android_icon_zip
503
+ )
504
+
505
+ # Footer with additional information
506
+ gr.HTML("""
507
+ <div style="margin-top: 40px; padding: 20px; background: #f8fafc; border-radius: 12px; text-align: center;">
508
+ <h3 style="color: #374151; margin-bottom: 15px;">πŸš€ Professional Image Processing Suite</h3>
509
+ <div style="display: flex; justify-content: center; gap: 30px; flex-wrap: wrap;">
510
+ <div>
511
+ <strong>πŸ“‹ Supported Formats:</strong><br>
512
+ JPEG, PNG, BMP, TIFF, WEBP, GIF, ICO, EPS, PDF, PSD, SVG, HEIC, AVIF, JXL
513
+ </div>
514
+ <div>
515
+ <strong>πŸ€– AI Services:</strong><br>
516
+ OpenAI DALL-E 2/3, Anthropic Claude, DeepSeek, Remove.bg, Clipdrop
517
+ </div>
518
+ <div>
519
+ <strong>✨ Features:</strong><br>
520
+ Format Conversion, AI Generation, Enhancement, Background Removal, Batch Processing
521
+ </div>
522
+ </div>
523
+ <p style="margin-top: 15px; color: #6b7280; font-size: 0.9rem;">
524
+ β€’ Made with ❀️ & Gradio β€” by Aaron πŸš€ β€’
525
+ </p>
526
+ </div>
527
+ """)
528
+
529
+
530
+ # Launch configuration
531
+ if __name__ == "__main__":
532
+ demo.launch(
533
+ server_name="localhost",
534
+ server_port=7860,
535
+ share=True,
536
+ favicon_path=None,
537
+ pwa=True,
538
+ ssl_verify=False,
539
+ show_api=False
540
+ )