yukee1992 commited on
Commit
da3ded3
ยท
verified ยท
1 Parent(s): 0237812

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +278 -189
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 # New field for custom background
138
 
139
  class JobStatusResponse(BaseModel):
140
  job_id: str
@@ -182,239 +182,324 @@ current_pipe = None
182
  model_lock = threading.Lock()
183
 
184
  # =============================================
185
- # ENHANCED: Infographic Layout Generator with Custom Background Support
186
  # =============================================
187
  class InfographicLayoutGenerator:
188
  def __init__(self):
189
- # Warm, earthy color palette for text
190
- self.title_color = (139, 69, 19) # Saddle brown
191
- self.text_color = (80, 60, 40) # Dark brown
192
- self.accent_color = (205, 133, 63) # Peru brown
193
- self.custom_background = None
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
- """Get font with Chinese/English support"""
 
217
  font_paths = [
218
- # Windows Chinese fonts
219
- "C:/Windows/Fonts/msyh.ttc",
220
- "C:/Windows/Fonts/simhei.ttf",
221
- "C:/Windows/Fonts/simsun.ttc",
222
- # Linux Chinese fonts
 
 
 
223
  "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
 
224
  "/usr/share/fonts/truetype/arphic/uming.ttc",
225
- # Mac Chinese fonts
 
 
 
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
- bold_paths = [
236
  "C:/Windows/Fonts/msyhbd.ttc",
237
  "C:/Windows/Fonts/simhei.ttf",
238
- "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
 
239
  ]
240
- font_paths = bold_paths + font_paths
241
 
 
242
  for font_path in font_paths:
243
  try:
244
- return ImageFont.truetype(font_path, size)
 
245
  except:
246
  continue
247
 
248
- return ImageFont.load_default()
 
 
 
 
249
 
250
- def resize_background_to_fit(self, bg_image, target_width, target_height):
251
- """Resize background to fit target dimensions while maintaining aspect ratio"""
252
- # Get original dimensions
253
- bg_width, bg_height = bg_image.size
254
-
255
- # Calculate aspect ratios
256
- target_ratio = target_width / target_height
257
- bg_ratio = bg_width / bg_height
258
-
259
- if bg_ratio > target_ratio:
260
- # Background is wider - match height
261
- new_height = target_height
262
- new_width = int(bg_width * (target_height / bg_height))
263
- else:
264
- # Background is taller - match width
265
- new_width = target_width
266
- new_height = int(bg_height * (target_width / bg_width))
267
-
268
- # Resize background
269
- bg_resized = bg_image.resize((new_width, new_height), Image.Resampling.LANCZOS)
270
-
271
- # Create final canvas
272
- canvas = Image.new('RGB', (target_width, target_height))
273
-
274
- # Calculate crop position (center)
275
- left = (new_width - target_width) // 2
276
- top = (new_height - target_height) // 2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
 
278
- # Crop to target size
279
- canvas.paste(bg_resized.crop((left, top, left + target_width, top + target_height)))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
 
281
- return canvas
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 custom background
287
  """
288
- # 9:16 portrait dimensions
289
  width = 900
290
  height = 1600
291
 
292
- # Create or load background
293
- if background_image is not None:
294
- canvas = self.resize_background_to_fit(background_image, width, height)
295
- elif background_path is not None and os.path.exists(background_path):
296
- bg_image = Image.open(background_path).convert('RGB')
297
- canvas = self.resize_background_to_fit(bg_image, width, height)
298
- bg_image.close()
299
- elif self.custom_background is not None:
300
- canvas = self.resize_background_to_fit(self.custom_background, width, height)
 
 
 
 
 
 
301
  else:
302
- # Default cream background
303
- canvas = Image.new('RGB', (width, height), (250, 240, 220))
304
 
305
  draw = ImageDraw.Draw(canvas)
306
 
307
- # Add semi-transparent overlay for better text readability
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
- # Layout constants
315
- top_margin = 100
316
- side_margin = 40
317
- icon_size = 120
318
- columns = 2
319
 
320
- # Draw decorative border
321
- draw.rectangle([10, 10, width-10, height-10], outline=self.accent_color, width=3)
322
- draw.rectangle([15, 15, width-15, height-15], outline=self.accent_color, width=1)
323
 
324
- # Draw title with semi-transparent background
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 = 30
331
-
332
- # Title background
333
- draw.rectangle(
334
- [title_x-20, title_y-10, title_x+title_width+20, title_y+title_height+10],
335
- fill=(255, 255, 255, 200),
336
- outline=self.accent_color,
337
- width=2
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(25)
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=self.text_color)
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
- # Place items
358
  for i, ((food_name, _), img) in enumerate(zip(items_with_prompts, generated_images)):
359
- column = i // rows_per_column
360
- row = i % rows_per_column
361
-
362
- # Calculate position
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
- # Process and paste image
380
  if img:
381
- if img.mode != 'RGB':
382
- img = img.convert('RGB')
 
 
 
 
 
 
 
 
383
 
384
- # Add white background behind image
385
- draw.rectangle(
386
- [x_offset, y_offset, x_offset+icon_size, y_offset+icon_size],
387
- fill=(255, 255, 255),
388
- outline=self.accent_color,
389
- width=2
 
 
 
 
 
 
 
 
 
390
  )
391
 
392
- img_resized = img.resize((icon_size-4, icon_size-4), Image.Resampling.LANCZOS)
393
- canvas.paste(img_resized, (x_offset+2, y_offset+2))
394
-
395
- # Draw food name
396
- name_font = self.get_font(28, bold=True)
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 number
402
- number_font = self.get_font(20)
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
- # Decorative line
407
- line_y = name_y + 60
408
- draw.line([name_x, line_y, name_x+80, line_y], fill=self.accent_color, width=2)
409
-
410
- # Draw footer
411
- footer_font = self.get_font(22)
412
- footer_text = "ยท Fresh & Healthy ยท Made with Love ยท"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 - 60
417
- draw.text((footer_x, footer_y), footer_text, font=footer_font, fill=self.text_color)
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
- # MEMORY-OPTIMIZED: Infographic Background Task with Custom Background Support
1026
  # =============================================
1027
  def generate_infographic_background(job_id: str):
1028
- """Background task with custom background support"""
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 food photography
1080
- enhanced_prompt = f"food photography, {item.prompt}, white background, studio lighting, high quality"
 
 
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 custom background
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 # Pass custom background
1136
  )
1137
 
1138
  # Save infographic
@@ -1360,10 +1447,10 @@ async def get_local_images():
1360
  return get_local_storage_info()
1361
 
1362
  # =============================================
1363
- # ENHANCED: Test Functions for Gradio with Background Upload
1364
  # =============================================
1365
  def test_infographic(title, items_text, background_image=None):
1366
- """Test function for infographic generation with optional background"""
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']}, white background, studio lighting"
 
 
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) # Small delay to prevent overwhelming
1402
 
1403
- print(" ๐Ÿ“ Creating layout...")
1404
 
1405
- # Create infographic with custom background if provided
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 # Pass uploaded background
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="่‚ ้“ๅ‹ๅฅฝ็š„ 7็ง้ฃŸ็‰ฉ",
1469
- placeholder="Enter infographic title (supports Chinese)..."
1470
  )
1471
  infographic_items = gr.Textbox(
1472
  label="Items (name:prompt;name2:prompt2)",
1473
- value="้…ธๅฅถ:yogurt bowl with berries;็‡•้บฆ:bowl of oatmeal;่Šฆ็ฌ‹:fresh asparagus;ๅงœ:fresh ginger;Kimchi:korean kimchi;้ฆ™่•‰:bananas;bone broth:warm bone broth",
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
- - **Limit**: Maximum 10 items for best performance
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