Update app.py
Browse files
app.py
CHANGED
|
@@ -134,7 +134,7 @@ class InfographicRequest(BaseModel):
|
|
| 134 |
style: str = "food_photography"
|
| 135 |
project_id: Optional[str] = None
|
| 136 |
callback_url: Optional[str] = None
|
| 137 |
-
background_url: Optional[str] = None
|
| 138 |
|
| 139 |
class JobStatusResponse(BaseModel):
|
| 140 |
job_id: str
|
|
@@ -182,239 +182,324 @@ current_pipe = None
|
|
| 182 |
model_lock = threading.Lock()
|
| 183 |
|
| 184 |
# =============================================
|
| 185 |
-
#
|
| 186 |
# =============================================
|
| 187 |
class InfographicLayoutGenerator:
|
| 188 |
def __init__(self):
|
| 189 |
-
#
|
| 190 |
-
self.title_color = (
|
| 191 |
-
self.text_color = (
|
| 192 |
-
self.accent_color = (
|
| 193 |
-
self.
|
| 194 |
-
|
| 195 |
-
def set_custom_background(self, background_image_path=None, background_image=None):
|
| 196 |
-
"""Set a custom background image"""
|
| 197 |
-
if background_image_path and os.path.exists(background_image_path):
|
| 198 |
-
try:
|
| 199 |
-
self.custom_background = Image.open(background_image_path).convert('RGB')
|
| 200 |
-
print(f"โ
Loaded custom background: {background_image_path}")
|
| 201 |
-
except Exception as e:
|
| 202 |
-
print(f"โ Failed to load background: {e}")
|
| 203 |
-
self.custom_background = None
|
| 204 |
-
elif background_image:
|
| 205 |
-
try:
|
| 206 |
-
if isinstance(background_image, Image.Image):
|
| 207 |
-
self.custom_background = background_image.convert('RGB')
|
| 208 |
-
else:
|
| 209 |
-
self.custom_background = Image.open(background_image).convert('RGB')
|
| 210 |
-
print(f"โ
Loaded custom background from image")
|
| 211 |
-
except Exception as e:
|
| 212 |
-
print(f"โ Failed to load background: {e}")
|
| 213 |
-
self.custom_background = None
|
| 214 |
-
|
| 215 |
def get_font(self, size, bold=False):
|
| 216 |
-
"""
|
|
|
|
| 217 |
font_paths = [
|
| 218 |
-
# Windows
|
| 219 |
-
"C:/Windows/Fonts/msyh.ttc",
|
| 220 |
-
"C:/Windows/Fonts/
|
| 221 |
-
"C:/Windows/Fonts/
|
| 222 |
-
#
|
|
|
|
|
|
|
|
|
|
| 223 |
"/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
|
|
|
|
| 224 |
"/usr/share/fonts/truetype/arphic/uming.ttc",
|
| 225 |
-
|
|
|
|
|
|
|
|
|
|
| 226 |
"/System/Library/Fonts/PingFang.ttc",
|
|
|
|
|
|
|
| 227 |
# Android fonts
|
| 228 |
"/system/fonts/NotoSansCJK-Regular.ttc",
|
|
|
|
| 229 |
# Fallback
|
| 230 |
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
| 231 |
"arial.ttf",
|
| 232 |
]
|
| 233 |
|
| 234 |
if bold:
|
| 235 |
-
|
| 236 |
"C:/Windows/Fonts/msyhbd.ttc",
|
| 237 |
"C:/Windows/Fonts/simhei.ttf",
|
| 238 |
-
"/usr/share/fonts/truetype/wqy/wqy-
|
|
|
|
| 239 |
]
|
| 240 |
-
font_paths =
|
| 241 |
|
|
|
|
| 242 |
for font_path in font_paths:
|
| 243 |
try:
|
| 244 |
-
|
|
|
|
| 245 |
except:
|
| 246 |
continue
|
| 247 |
|
| 248 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 249 |
|
| 250 |
-
def
|
| 251 |
-
"""
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
#
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
|
| 278 |
-
#
|
| 279 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
|
| 281 |
-
return
|
| 282 |
|
| 283 |
-
def create_food_infographic(self, title, items_with_prompts, generated_images,
|
| 284 |
-
background_image=None, background_path=None):
|
| 285 |
"""
|
| 286 |
-
Create infographic with
|
| 287 |
"""
|
| 288 |
-
# 9:16
|
| 289 |
width = 900
|
| 290 |
height = 1600
|
| 291 |
|
| 292 |
-
# Create
|
| 293 |
-
if background_image
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
else:
|
| 302 |
-
|
| 303 |
-
canvas = Image.new('RGB', (width, height), (250, 240, 220))
|
| 304 |
|
| 305 |
draw = ImageDraw.Draw(canvas)
|
| 306 |
|
| 307 |
-
|
| 308 |
-
overlay = Image.new('RGBA', (width, height), (255, 255, 255, 180))
|
| 309 |
-
canvas_rgba = canvas.convert('RGBA')
|
| 310 |
-
canvas_rgba = Image.alpha_composite(canvas_rgba, overlay)
|
| 311 |
-
canvas = canvas_rgba.convert('RGB')
|
| 312 |
-
draw = ImageDraw.Draw(canvas)
|
| 313 |
|
| 314 |
-
#
|
| 315 |
-
|
| 316 |
-
side_margin = 40
|
| 317 |
-
icon_size = 120
|
| 318 |
-
columns = 2
|
| 319 |
|
| 320 |
-
# Draw
|
| 321 |
-
|
| 322 |
-
draw.rectangle([15, 15, width-15, height-15], outline=self.accent_color, width=1)
|
| 323 |
|
| 324 |
-
#
|
| 325 |
-
title_font = self.get_font(50, bold=True)
|
| 326 |
title_bbox = draw.textbbox((0, 0), title, font=title_font)
|
| 327 |
title_width = title_bbox[2] - title_bbox[0]
|
| 328 |
-
title_height = title_bbox[3] - title_bbox[1]
|
| 329 |
title_x = (width - title_width) // 2
|
| 330 |
-
title_y =
|
| 331 |
-
|
| 332 |
-
#
|
| 333 |
-
draw.
|
| 334 |
-
[title_x-20, title_y-10, title_x+title_width+20, title_y+
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
)
|
| 339 |
|
| 340 |
-
# Draw title
|
| 341 |
draw.text((title_x, title_y), title, font=title_font, fill=self.title_color)
|
| 342 |
|
| 343 |
-
# Draw subtitle
|
| 344 |
-
subtitle_font = self.get_font(
|
| 345 |
-
subtitle = "Healthy Foods
|
| 346 |
subtitle_bbox = draw.textbbox((0, 0), subtitle, font=subtitle_font)
|
| 347 |
subtitle_width = subtitle_bbox[2] - subtitle_bbox[0]
|
| 348 |
subtitle_x = (width - subtitle_width) // 2
|
| 349 |
subtitle_y = title_y + 60
|
| 350 |
-
draw.text((subtitle_x, subtitle_y), subtitle, font=subtitle_font, fill=
|
| 351 |
-
|
| 352 |
-
# Calculate grid
|
| 353 |
-
num_items = len(items_with_prompts)
|
| 354 |
-
rows_per_column = (num_items + columns - 1) // columns
|
| 355 |
-
start_y = top_margin + 80
|
| 356 |
|
| 357 |
-
#
|
| 358 |
for i, ((food_name, _), img) in enumerate(zip(items_with_prompts, generated_images)):
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
if column == 0:
|
| 364 |
-
x_offset = side_margin
|
| 365 |
-
else:
|
| 366 |
-
x_offset = width // 2 + 20
|
| 367 |
-
|
| 368 |
-
y_offset = start_y + (row * 160)
|
| 369 |
-
|
| 370 |
-
# Item background for better readability
|
| 371 |
-
draw.rounded_rectangle(
|
| 372 |
-
[x_offset-5, y_offset-5, x_offset+icon_size+150, y_offset+icon_size+5],
|
| 373 |
-
radius=15,
|
| 374 |
-
fill=(255, 255, 255, 220),
|
| 375 |
-
outline=self.accent_color,
|
| 376 |
-
width=1
|
| 377 |
-
)
|
| 378 |
|
| 379 |
-
#
|
| 380 |
if img:
|
| 381 |
-
|
| 382 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 383 |
|
| 384 |
-
#
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 390 |
)
|
| 391 |
|
| 392 |
-
|
| 393 |
-
canvas.paste(
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
name_x = x_offset + icon_size + 15
|
| 398 |
-
name_y = y_offset + (icon_size // 2) - 25
|
| 399 |
-
draw.text((name_x, name_y), food_name, font=name_font, fill=self.text_color)
|
| 400 |
|
| 401 |
-
# Draw
|
| 402 |
-
|
| 403 |
-
number_text = f"#{i+1:02d}"
|
| 404 |
-
draw.text((name_x, name_y + 35), number_text, font=number_font, fill=self.accent_color)
|
| 405 |
|
| 406 |
-
#
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 413 |
footer_bbox = draw.textbbox((0, 0), footer_text, font=footer_font)
|
| 414 |
footer_width = footer_bbox[2] - footer_bbox[0]
|
| 415 |
footer_x = (width - footer_width) // 2
|
| 416 |
-
footer_y = height -
|
| 417 |
-
draw.text((footer_x, footer_y), footer_text, font=footer_font, fill=
|
| 418 |
|
| 419 |
return canvas
|
| 420 |
|
|
@@ -1022,10 +1107,10 @@ def generate_storybook_background(job_id: str):
|
|
| 1022 |
update_job_status(job_id, JobStatus.FAILED, 0, error_msg)
|
| 1023 |
|
| 1024 |
# =============================================
|
| 1025 |
-
#
|
| 1026 |
# =============================================
|
| 1027 |
def generate_infographic_background(job_id: str):
|
| 1028 |
-
"""Background task with
|
| 1029 |
try:
|
| 1030 |
job_data = job_storage[job_id]
|
| 1031 |
request = InfographicRequest(**job_data["request"])
|
|
@@ -1076,8 +1161,10 @@ def generate_infographic_background(job_id: str):
|
|
| 1076 |
try:
|
| 1077 |
print(f"๐ผ๏ธ Generating image for: {item.name}")
|
| 1078 |
|
| 1079 |
-
# Enhanced prompt for
|
| 1080 |
-
enhanced_prompt = f"food photography, {item.prompt},
|
|
|
|
|
|
|
| 1081 |
|
| 1082 |
image = generate_image_simple(
|
| 1083 |
enhanced_prompt,
|
|
@@ -1124,7 +1211,7 @@ def generate_infographic_background(job_id: str):
|
|
| 1124 |
|
| 1125 |
update_job_status(job_id, JobStatus.PROCESSING, 85, "Creating infographic layout...")
|
| 1126 |
|
| 1127 |
-
# Create the infographic with
|
| 1128 |
layout_generator = InfographicLayoutGenerator()
|
| 1129 |
items_with_prompts = [(item.name, item.prompt) for item in items]
|
| 1130 |
|
|
@@ -1132,7 +1219,7 @@ def generate_infographic_background(job_id: str):
|
|
| 1132 |
request.title,
|
| 1133 |
items_with_prompts,
|
| 1134 |
generated_images,
|
| 1135 |
-
background_image=background_image
|
| 1136 |
)
|
| 1137 |
|
| 1138 |
# Save infographic
|
|
@@ -1360,10 +1447,10 @@ async def get_local_images():
|
|
| 1360 |
return get_local_storage_info()
|
| 1361 |
|
| 1362 |
# =============================================
|
| 1363 |
-
#
|
| 1364 |
# =============================================
|
| 1365 |
def test_infographic(title, items_text, background_image=None):
|
| 1366 |
-
"""Test function
|
| 1367 |
try:
|
| 1368 |
# Parse items (format: "name:prompt;name2:prompt2")
|
| 1369 |
items = []
|
|
@@ -1388,7 +1475,9 @@ def test_infographic(title, items_text, background_image=None):
|
|
| 1388 |
for i, item in enumerate(items):
|
| 1389 |
print(f" ๐ผ๏ธ Generating image {i+1}/{len(items)}: {item['name']}")
|
| 1390 |
|
| 1391 |
-
enhanced_prompt = f"food photography, {item['prompt']},
|
|
|
|
|
|
|
| 1392 |
|
| 1393 |
img = generate_image_simple(
|
| 1394 |
enhanced_prompt,
|
|
@@ -1398,11 +1487,11 @@ def test_infographic(title, items_text, background_image=None):
|
|
| 1398 |
None
|
| 1399 |
)
|
| 1400 |
generated_images.append(img)
|
| 1401 |
-
time.sleep(0.5)
|
| 1402 |
|
| 1403 |
-
print(" ๐ Creating layout...")
|
| 1404 |
|
| 1405 |
-
# Create infographic with
|
| 1406 |
layout_gen = InfographicLayoutGenerator()
|
| 1407 |
items_with_prompts = [(item['name'], item['prompt']) for item in items]
|
| 1408 |
|
|
@@ -1410,10 +1499,10 @@ def test_infographic(title, items_text, background_image=None):
|
|
| 1410 |
title,
|
| 1411 |
items_with_prompts,
|
| 1412 |
generated_images,
|
| 1413 |
-
background_image=background_image
|
| 1414 |
)
|
| 1415 |
|
| 1416 |
-
return infographic, f"โ
Generated infographic with {len(items)} items"
|
| 1417 |
|
| 1418 |
finally:
|
| 1419 |
# Clean up
|
|
@@ -1464,13 +1553,13 @@ def create_gradio_interface():
|
|
| 1464 |
with gr.Row():
|
| 1465 |
with gr.Column():
|
| 1466 |
infographic_title = gr.Textbox(
|
| 1467 |
-
label="Title",
|
| 1468 |
-
value="
|
| 1469 |
-
placeholder="Enter infographic title
|
| 1470 |
)
|
| 1471 |
infographic_items = gr.Textbox(
|
| 1472 |
label="Items (name:prompt;name2:prompt2)",
|
| 1473 |
-
value="้
ธ
|
| 1474 |
lines=5,
|
| 1475 |
placeholder="item1:prompt1;item2:prompt2"
|
| 1476 |
)
|
|
@@ -1499,7 +1588,7 @@ def create_gradio_interface():
|
|
| 1499 |
- **Title**: Supports Chinese/English text
|
| 1500 |
- **Items**: Format as `name:prompt;name2:prompt2`
|
| 1501 |
- **Background**: Upload your own image for custom background
|
| 1502 |
-
- **
|
| 1503 |
""")
|
| 1504 |
|
| 1505 |
return demo
|
|
|
|
| 134 |
style: str = "food_photography"
|
| 135 |
project_id: Optional[str] = None
|
| 136 |
callback_url: Optional[str] = None
|
| 137 |
+
background_url: Optional[str] = None
|
| 138 |
|
| 139 |
class JobStatusResponse(BaseModel):
|
| 140 |
job_id: str
|
|
|
|
| 182 |
model_lock = threading.Lock()
|
| 183 |
|
| 184 |
# =============================================
|
| 185 |
+
# FIXED: Infographic Layout Generator with Responsive Design
|
| 186 |
# =============================================
|
| 187 |
class InfographicLayoutGenerator:
|
| 188 |
def __init__(self):
|
| 189 |
+
# Clean, minimal color palette - NO RED OUTLINES
|
| 190 |
+
self.title_color = (60, 60, 60) # Dark gray
|
| 191 |
+
self.text_color = (40, 40, 40) # Almost black
|
| 192 |
+
self.accent_color = (200, 160, 120) # Light brown (subtle)
|
| 193 |
+
self.border_color = (220, 220, 220) # Very light gray
|
| 194 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
def get_font(self, size, bold=False):
|
| 196 |
+
"""Enhanced font loading with extensive Chinese font support"""
|
| 197 |
+
# Comprehensive list of fonts that support Chinese
|
| 198 |
font_paths = [
|
| 199 |
+
# Windows fonts
|
| 200 |
+
"C:/Windows/Fonts/msyh.ttc", # Microsoft YaHei
|
| 201 |
+
"C:/Windows/Fonts/msyhbd.ttc", # Microsoft YaHei Bold
|
| 202 |
+
"C:/Windows/Fonts/simhei.ttf", # SimHei
|
| 203 |
+
"C:/Windows/Fonts/simsun.ttc", # SimSun
|
| 204 |
+
"C:/Windows/Fonts/STSONG.TTF", # STSong
|
| 205 |
+
"C:/Windows/Fonts/STKAITI.TTF", # STKaiti
|
| 206 |
+
# Linux fonts
|
| 207 |
"/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
|
| 208 |
+
"/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
|
| 209 |
"/usr/share/fonts/truetype/arphic/uming.ttc",
|
| 210 |
+
"/usr/share/fonts/truetype/arphic/ukai.ttc",
|
| 211 |
+
"/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc",
|
| 212 |
+
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
|
| 213 |
+
# Mac fonts
|
| 214 |
"/System/Library/Fonts/PingFang.ttc",
|
| 215 |
+
"/System/Library/Fonts/STHeiti Light.ttc",
|
| 216 |
+
"/System/Library/Fonts/STHeiti Medium.ttc",
|
| 217 |
# Android fonts
|
| 218 |
"/system/fonts/NotoSansCJK-Regular.ttc",
|
| 219 |
+
"/system/fonts/DroidSansFallback.ttf",
|
| 220 |
# Fallback
|
| 221 |
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
| 222 |
"arial.ttf",
|
| 223 |
]
|
| 224 |
|
| 225 |
if bold:
|
| 226 |
+
bold_fonts = [
|
| 227 |
"C:/Windows/Fonts/msyhbd.ttc",
|
| 228 |
"C:/Windows/Fonts/simhei.ttf",
|
| 229 |
+
"/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
|
| 230 |
+
"/usr/share/fonts/truetype/noto/NotoSansCJK-Bold.ttc",
|
| 231 |
]
|
| 232 |
+
font_paths = bold_fonts + font_paths
|
| 233 |
|
| 234 |
+
# Try each font
|
| 235 |
for font_path in font_paths:
|
| 236 |
try:
|
| 237 |
+
if os.path.exists(font_path):
|
| 238 |
+
return ImageFont.truetype(font_path, size)
|
| 239 |
except:
|
| 240 |
continue
|
| 241 |
|
| 242 |
+
# Ultimate fallback - use default font but try to set encoding
|
| 243 |
+
try:
|
| 244 |
+
return ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", size)
|
| 245 |
+
except:
|
| 246 |
+
return ImageFont.load_default()
|
| 247 |
|
| 248 |
+
def smart_remove_background(self, image):
|
| 249 |
+
"""Intelligently remove background while keeping the main subject"""
|
| 250 |
+
try:
|
| 251 |
+
# Convert to RGBA
|
| 252 |
+
if image.mode != 'RGBA':
|
| 253 |
+
image = image.convert('RGBA')
|
| 254 |
+
|
| 255 |
+
data = image.getdata()
|
| 256 |
+
new_data = []
|
| 257 |
+
|
| 258 |
+
# Get image dimensions
|
| 259 |
+
width, height = image.size
|
| 260 |
+
|
| 261 |
+
# Sample edges to determine background color
|
| 262 |
+
edge_samples = []
|
| 263 |
+
# Top edge
|
| 264 |
+
for x in range(0, width, 10):
|
| 265 |
+
edge_samples.append(data[x])
|
| 266 |
+
# Bottom edge
|
| 267 |
+
for x in range(0, width, 10):
|
| 268 |
+
edge_samples.append(data[(height-1) * width + x])
|
| 269 |
+
# Left edge
|
| 270 |
+
for y in range(0, height, 10):
|
| 271 |
+
edge_samples.append(data[y * width])
|
| 272 |
+
# Right edge
|
| 273 |
+
for y in range(0, height, 10):
|
| 274 |
+
edge_samples.append(data[y * width + (width-1)])
|
| 275 |
+
|
| 276 |
+
# Calculate average background color from edges
|
| 277 |
+
avg_r = sum(s[0] for s in edge_samples) // len(edge_samples)
|
| 278 |
+
avg_g = sum(s[1] for s in edge_samples) // len(edge_samples)
|
| 279 |
+
avg_b = sum(s[2] for s in edge_samples) // len(edge_samples)
|
| 280 |
+
|
| 281 |
+
# Threshold for background detection
|
| 282 |
+
threshold = 60
|
| 283 |
+
|
| 284 |
+
for item in data:
|
| 285 |
+
# Calculate color distance from average background
|
| 286 |
+
distance = ((item[0] - avg_r) ** 2 +
|
| 287 |
+
(item[1] - avg_g) ** 2 +
|
| 288 |
+
(item[2] - avg_b) ** 2) ** 0.5
|
| 289 |
+
|
| 290 |
+
# If pixel is close to background color, make transparent
|
| 291 |
+
if distance < threshold:
|
| 292 |
+
new_data.append((255, 255, 255, 0))
|
| 293 |
+
else:
|
| 294 |
+
new_data.append(item)
|
| 295 |
+
|
| 296 |
+
image.putdata(new_data)
|
| 297 |
+
return image
|
| 298 |
+
|
| 299 |
+
except Exception as e:
|
| 300 |
+
print(f"Background removal failed: {e}")
|
| 301 |
+
return image
|
| 302 |
+
|
| 303 |
+
def create_responsive_layout(self, num_items):
|
| 304 |
+
"""Calculate responsive layout based on number of items"""
|
| 305 |
+
# 9:16 dimensions
|
| 306 |
+
width = 900
|
| 307 |
+
height = 1600
|
| 308 |
|
| 309 |
+
# Responsive sizing
|
| 310 |
+
if num_items <= 3:
|
| 311 |
+
# Few items - larger icons
|
| 312 |
+
icon_size = 180
|
| 313 |
+
columns = 1
|
| 314 |
+
rows = num_items
|
| 315 |
+
vertical_spacing = 100
|
| 316 |
+
elif num_items <= 6:
|
| 317 |
+
# Medium items - medium icons
|
| 318 |
+
icon_size = 150
|
| 319 |
+
columns = 2
|
| 320 |
+
rows = (num_items + 1) // 2
|
| 321 |
+
vertical_spacing = 80
|
| 322 |
+
else:
|
| 323 |
+
# Many items - smaller icons
|
| 324 |
+
icon_size = 120
|
| 325 |
+
columns = 2
|
| 326 |
+
rows = (num_items + 1) // 2
|
| 327 |
+
vertical_spacing = 60
|
| 328 |
+
|
| 329 |
+
# Calculate positions
|
| 330 |
+
positions = []
|
| 331 |
+
available_height = height - 300 # Leave space for title and footer
|
| 332 |
+
start_y = 200
|
| 333 |
+
|
| 334 |
+
for i in range(num_items):
|
| 335 |
+
col = i % columns
|
| 336 |
+
row = i // columns
|
| 337 |
+
|
| 338 |
+
if columns == 1:
|
| 339 |
+
x = (width - icon_size) // 2
|
| 340 |
+
else:
|
| 341 |
+
if col == 0:
|
| 342 |
+
x = width // 4 - icon_size // 2
|
| 343 |
+
else:
|
| 344 |
+
x = 3 * width // 4 - icon_size // 2
|
| 345 |
+
|
| 346 |
+
# Distribute rows evenly
|
| 347 |
+
if rows > 1:
|
| 348 |
+
y = start_y + (row * (available_height // rows))
|
| 349 |
+
else:
|
| 350 |
+
y = start_y + (available_height - icon_size) // 2
|
| 351 |
+
|
| 352 |
+
positions.append((x, y, icon_size))
|
| 353 |
|
| 354 |
+
return positions, icon_size, columns
|
| 355 |
|
| 356 |
+
def create_food_infographic(self, title, items_with_prompts, generated_images, background_image=None):
|
|
|
|
| 357 |
"""
|
| 358 |
+
Create a responsive 9:16 infographic with proper Chinese text
|
| 359 |
"""
|
| 360 |
+
# 9:16 dimensions
|
| 361 |
width = 900
|
| 362 |
height = 1600
|
| 363 |
|
| 364 |
+
# Create clean background
|
| 365 |
+
if background_image:
|
| 366 |
+
# Resize background to fit
|
| 367 |
+
bg = background_image.resize((width, height), Image.Resampling.LANCZOS)
|
| 368 |
+
canvas = bg.convert('RGB')
|
| 369 |
+
else:
|
| 370 |
+
# Clean white background
|
| 371 |
+
canvas = Image.new('RGB', (width, height), (255, 255, 255))
|
| 372 |
+
|
| 373 |
+
# Add very subtle gray gradient at edges only
|
| 374 |
+
draw = ImageDraw.Draw(canvas)
|
| 375 |
+
for y in range(height):
|
| 376 |
+
if y < 20 or y > height-20:
|
| 377 |
+
opacity = 10
|
| 378 |
+
draw.line([(0, y), (width, y)], fill=(240, 240, 240))
|
| 379 |
else:
|
| 380 |
+
canvas = Image.new('RGB', (width, height), (255, 255, 255))
|
|
|
|
| 381 |
|
| 382 |
draw = ImageDraw.Draw(canvas)
|
| 383 |
|
| 384 |
+
num_items = len(items_with_prompts)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 385 |
|
| 386 |
+
# Get responsive layout
|
| 387 |
+
positions, icon_size, columns = self.create_responsive_layout(num_items)
|
|
|
|
|
|
|
|
|
|
| 388 |
|
| 389 |
+
# Draw title with Chinese support
|
| 390 |
+
title_font = self.get_font(48, bold=True)
|
|
|
|
| 391 |
|
| 392 |
+
# Title with very light background
|
|
|
|
| 393 |
title_bbox = draw.textbbox((0, 0), title, font=title_font)
|
| 394 |
title_width = title_bbox[2] - title_bbox[0]
|
|
|
|
| 395 |
title_x = (width - title_width) // 2
|
| 396 |
+
title_y = 60
|
| 397 |
+
|
| 398 |
+
# Very light gray background for title (NO RED)
|
| 399 |
+
draw.rounded_rectangle(
|
| 400 |
+
[title_x-20, title_y-10, title_x+title_width+20, title_y+60],
|
| 401 |
+
radius=20,
|
| 402 |
+
fill=(250, 250, 250),
|
| 403 |
+
outline=None # NO OUTLINE
|
| 404 |
)
|
| 405 |
|
| 406 |
+
# Draw title text
|
| 407 |
draw.text((title_x, title_y), title, font=title_font, fill=self.title_color)
|
| 408 |
|
| 409 |
+
# Draw subtitle in both languages
|
| 410 |
+
subtitle_font = self.get_font(24)
|
| 411 |
+
subtitle = "Healthy Foods ยท ๅฅๅบท้ฃๅ"
|
| 412 |
subtitle_bbox = draw.textbbox((0, 0), subtitle, font=subtitle_font)
|
| 413 |
subtitle_width = subtitle_bbox[2] - subtitle_bbox[0]
|
| 414 |
subtitle_x = (width - subtitle_width) // 2
|
| 415 |
subtitle_y = title_y + 60
|
| 416 |
+
draw.text((subtitle_x, subtitle_y), subtitle, font=subtitle_font, fill=(120, 120, 120))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 417 |
|
| 418 |
+
# Process and place each item
|
| 419 |
for i, ((food_name, _), img) in enumerate(zip(items_with_prompts, generated_images)):
|
| 420 |
+
if i >= len(positions):
|
| 421 |
+
break
|
| 422 |
+
|
| 423 |
+
x, y, size = positions[i]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 424 |
|
| 425 |
+
# Create clean icon from image
|
| 426 |
if img:
|
| 427 |
+
# Remove background
|
| 428 |
+
icon_img = self.smart_remove_background(img)
|
| 429 |
+
|
| 430 |
+
# Resize maintaining aspect ratio
|
| 431 |
+
icon_img.thumbnail((size, size), Image.Resampling.LANCZOS)
|
| 432 |
+
|
| 433 |
+
# Create circular mask for smooth edges
|
| 434 |
+
mask = Image.new('L', (size, size), 0)
|
| 435 |
+
mask_draw = ImageDraw.Draw(mask)
|
| 436 |
+
mask_draw.ellipse((0, 0, size, size), fill=255)
|
| 437 |
|
| 438 |
+
# Create final icon with transparency
|
| 439 |
+
icon_final = Image.new('RGBA', (size, size), (0, 0, 0, 0))
|
| 440 |
+
|
| 441 |
+
# Center the image
|
| 442 |
+
paste_x = (size - icon_img.width) // 2
|
| 443 |
+
paste_y = (size - icon_img.height) // 2
|
| 444 |
+
icon_final.paste(icon_img, (paste_x, paste_y), icon_img)
|
| 445 |
+
|
| 446 |
+
# Very subtle circular background (NO RED OUTLINE)
|
| 447 |
+
circle_bg = Image.new('RGBA', (size+20, size+20), (0, 0, 0, 0))
|
| 448 |
+
circle_draw = ImageDraw.Draw(circle_bg)
|
| 449 |
+
circle_draw.ellipse(
|
| 450 |
+
[5, 5, size+15, size+15],
|
| 451 |
+
fill=(245, 245, 245), # Very light gray
|
| 452 |
+
outline=None # NO OUTLINE
|
| 453 |
)
|
| 454 |
|
| 455 |
+
# Paste circle background
|
| 456 |
+
canvas.paste(circle_bg, (x-10, y-10), circle_bg)
|
| 457 |
+
|
| 458 |
+
# Paste icon
|
| 459 |
+
canvas.paste(icon_final, (x, y), icon_final)
|
|
|
|
|
|
|
|
|
|
| 460 |
|
| 461 |
+
# Draw food name with Chinese support
|
| 462 |
+
name_font = self.get_font(28 if num_items > 6 else 32, bold=True)
|
|
|
|
|
|
|
| 463 |
|
| 464 |
+
# Calculate name position based on layout
|
| 465 |
+
if columns == 1:
|
| 466 |
+
# Single column - name below icon
|
| 467 |
+
name_x = x + (size // 2)
|
| 468 |
+
name_y = y + size + 15
|
| 469 |
+
anchor = "mm" # Center alignment
|
| 470 |
+
|
| 471 |
+
# Draw name
|
| 472 |
+
draw.text((name_x, name_y), food_name, font=name_font,
|
| 473 |
+
fill=self.text_color, anchor=anchor)
|
| 474 |
+
|
| 475 |
+
# Draw number below name
|
| 476 |
+
number_font = self.get_font(20)
|
| 477 |
+
number_text = f"#{i+1:02d}"
|
| 478 |
+
draw.text((name_x, name_y + 35), number_text, font=number_font,
|
| 479 |
+
fill=(150, 150, 150), anchor=anchor)
|
| 480 |
+
|
| 481 |
+
else:
|
| 482 |
+
# Two columns - name beside icon
|
| 483 |
+
name_x = x + size + 15
|
| 484 |
+
name_y = y + (size // 2) - 20
|
| 485 |
+
|
| 486 |
+
# Draw name
|
| 487 |
+
draw.text((name_x, name_y), food_name, font=name_font, fill=self.text_color)
|
| 488 |
+
|
| 489 |
+
# Draw number
|
| 490 |
+
number_font = self.get_font(18)
|
| 491 |
+
number_text = f"#{i+1:02d}"
|
| 492 |
+
draw.text((name_x, name_y + 30), number_text, font=number_font,
|
| 493 |
+
fill=(150, 150, 150))
|
| 494 |
+
|
| 495 |
+
# Draw simple footer (NO RED)
|
| 496 |
+
footer_font = self.get_font(20)
|
| 497 |
+
footer_text = "ยท Fresh & Healthy ยท"
|
| 498 |
footer_bbox = draw.textbbox((0, 0), footer_text, font=footer_font)
|
| 499 |
footer_width = footer_bbox[2] - footer_bbox[0]
|
| 500 |
footer_x = (width - footer_width) // 2
|
| 501 |
+
footer_y = height - 50
|
| 502 |
+
draw.text((footer_x, footer_y), footer_text, font=footer_font, fill=(150, 150, 150))
|
| 503 |
|
| 504 |
return canvas
|
| 505 |
|
|
|
|
| 1107 |
update_job_status(job_id, JobStatus.FAILED, 0, error_msg)
|
| 1108 |
|
| 1109 |
# =============================================
|
| 1110 |
+
# FIXED: Infographic Background Task
|
| 1111 |
# =============================================
|
| 1112 |
def generate_infographic_background(job_id: str):
|
| 1113 |
+
"""Background task with responsive layout and proper Chinese support"""
|
| 1114 |
try:
|
| 1115 |
job_data = job_storage[job_id]
|
| 1116 |
request = InfographicRequest(**job_data["request"])
|
|
|
|
| 1161 |
try:
|
| 1162 |
print(f"๐ผ๏ธ Generating image for: {item.name}")
|
| 1163 |
|
| 1164 |
+
# Enhanced prompt for better subject isolation
|
| 1165 |
+
enhanced_prompt = f"professional food photography, {item.prompt}, " \
|
| 1166 |
+
f"isolated on white background, cut out, transparent style, " \
|
| 1167 |
+
f"sharp focus, studio lighting, clean edges"
|
| 1168 |
|
| 1169 |
image = generate_image_simple(
|
| 1170 |
enhanced_prompt,
|
|
|
|
| 1211 |
|
| 1212 |
update_job_status(job_id, JobStatus.PROCESSING, 85, "Creating infographic layout...")
|
| 1213 |
|
| 1214 |
+
# Create the infographic with responsive layout
|
| 1215 |
layout_generator = InfographicLayoutGenerator()
|
| 1216 |
items_with_prompts = [(item.name, item.prompt) for item in items]
|
| 1217 |
|
|
|
|
| 1219 |
request.title,
|
| 1220 |
items_with_prompts,
|
| 1221 |
generated_images,
|
| 1222 |
+
background_image=background_image
|
| 1223 |
)
|
| 1224 |
|
| 1225 |
# Save infographic
|
|
|
|
| 1447 |
return get_local_storage_info()
|
| 1448 |
|
| 1449 |
# =============================================
|
| 1450 |
+
# FIXED: Test Functions for Gradio
|
| 1451 |
# =============================================
|
| 1452 |
def test_infographic(title, items_text, background_image=None):
|
| 1453 |
+
"""Test function with proper Chinese support and responsive layout"""
|
| 1454 |
try:
|
| 1455 |
# Parse items (format: "name:prompt;name2:prompt2")
|
| 1456 |
items = []
|
|
|
|
| 1475 |
for i, item in enumerate(items):
|
| 1476 |
print(f" ๐ผ๏ธ Generating image {i+1}/{len(items)}: {item['name']}")
|
| 1477 |
|
| 1478 |
+
enhanced_prompt = f"professional food photography, {item['prompt']}, " \
|
| 1479 |
+
f"isolated on white background, cut out, transparent style, " \
|
| 1480 |
+
f"sharp focus, studio lighting, clean edges"
|
| 1481 |
|
| 1482 |
img = generate_image_simple(
|
| 1483 |
enhanced_prompt,
|
|
|
|
| 1487 |
None
|
| 1488 |
)
|
| 1489 |
generated_images.append(img)
|
| 1490 |
+
time.sleep(0.5)
|
| 1491 |
|
| 1492 |
+
print(" ๐ Creating responsive layout...")
|
| 1493 |
|
| 1494 |
+
# Create infographic with responsive layout
|
| 1495 |
layout_gen = InfographicLayoutGenerator()
|
| 1496 |
items_with_prompts = [(item['name'], item['prompt']) for item in items]
|
| 1497 |
|
|
|
|
| 1499 |
title,
|
| 1500 |
items_with_prompts,
|
| 1501 |
generated_images,
|
| 1502 |
+
background_image=background_image
|
| 1503 |
)
|
| 1504 |
|
| 1505 |
+
return infographic, f"โ
Generated responsive infographic with {len(items)} items"
|
| 1506 |
|
| 1507 |
finally:
|
| 1508 |
# Clean up
|
|
|
|
| 1553 |
with gr.Row():
|
| 1554 |
with gr.Column():
|
| 1555 |
infographic_title = gr.Textbox(
|
| 1556 |
+
label="Title (Supports Chinese)",
|
| 1557 |
+
value="7์์ ๋ง (Taste of July)",
|
| 1558 |
+
placeholder="Enter infographic title in Chinese/English..."
|
| 1559 |
)
|
| 1560 |
infographic_items = gr.Textbox(
|
| 1561 |
label="Items (name:prompt;name2:prompt2)",
|
| 1562 |
+
value="้
ธ่ (Kimchi):traditional korean cabbage kimchi;่ๅ (Kkakdugi):korean radish kimchi, cubed;ๆดพ (Pa-kimchi):green onion kimchi",
|
| 1563 |
lines=5,
|
| 1564 |
placeholder="item1:prompt1;item2:prompt2"
|
| 1565 |
)
|
|
|
|
| 1588 |
- **Title**: Supports Chinese/English text
|
| 1589 |
- **Items**: Format as `name:prompt;name2:prompt2`
|
| 1590 |
- **Background**: Upload your own image for custom background
|
| 1591 |
+
- **Responsive**: Layout adapts to number of items (1-10)
|
| 1592 |
""")
|
| 1593 |
|
| 1594 |
return demo
|