yukee1992 commited on
Commit
44ccb40
·
verified ·
1 Parent(s): 4b91e05

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +248 -43
app.py CHANGED
@@ -98,7 +98,7 @@ class JobStatus(str, Enum):
98
  FAILED = "failed"
99
 
100
  # =============================================
101
- # ENHANCED: Style and Size Configuration
102
  # =============================================
103
 
104
  # Available image styles with their prompts
@@ -185,7 +185,7 @@ STYLE_CONFIGS = {
185
  }
186
  }
187
 
188
- # Size presets
189
  SIZE_PRESETS = {
190
  "square": {"width": 768, "height": 768, "description": "1:1 Square"},
191
  "portrait": {"width": 768, "height": 1024, "description": "3:4 Portrait"},
@@ -194,8 +194,8 @@ SIZE_PRESETS = {
194
  "ultrawide": {"width": 1920, "height": 768, "description": "2.5:1 Ultrawide"},
195
  "instagram": {"width": 1080, "height": 1080, "description": "1:1 Instagram"},
196
  "story": {"width": 1080, "height": 1920, "description": "9:16 Story"},
197
- "facebook": {"width": 1200, "height": 630, "description": "1.91:1 Facebook"},
198
- "twitter": {"width": 1600, "height": 900, "description": "16:9 Twitter"},
199
  "custom": {"width": 768, "height": 1024, "description": "Custom Size"}
200
  }
201
 
@@ -214,9 +214,9 @@ class StorybookRequest(BaseModel):
214
  characters: List[CharacterDescription] = []
215
  model_choice: str = "dreamshaper-8"
216
  style: str = "childrens_book"
217
- image_size: str = "portrait" # New: size preset
218
- custom_width: Optional[int] = None # New: custom width
219
- custom_height: Optional[int] = None # New: custom height
220
  callback_url: Optional[str] = None
221
  consistency_seed: Optional[int] = None
222
  project_id: Optional[str] = None
@@ -230,7 +230,7 @@ class InfographicRequest(BaseModel):
230
  title: str
231
  items: List[InfographicItem]
232
  style: str = "food_photography"
233
- image_size: str = "story" # New: size preset for infographic
234
  custom_width: Optional[int] = None
235
  custom_height: Optional[int] = None
236
  project_id: Optional[str] = None
@@ -275,41 +275,51 @@ current_pipe = None
275
  model_lock = threading.Lock()
276
 
277
  # =============================================
278
- # HELPER FUNCTIONS
279
  # =============================================
280
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  def get_image_dimensions(size_preset, custom_width=None, custom_height=None):
282
- """Get image dimensions based on preset or custom values"""
283
  if size_preset == "custom" and custom_width and custom_height:
284
- return custom_width, custom_height
285
  elif size_preset in SIZE_PRESETS:
286
  preset = SIZE_PRESETS[size_preset]
287
- return preset["width"], preset["height"]
288
  else:
289
  # Default to portrait
290
- return 768, 1024
 
 
 
 
291
 
292
  def get_style_config(style_name):
293
  """Get style configuration or return default"""
294
  if style_name in STYLE_CONFIGS:
295
  return STYLE_CONFIGS[style_name]
296
- # Default to children's book style
297
  return STYLE_CONFIGS["childrens_book"]
298
 
299
  def enhance_prompt_with_style(scene_visual, style="childrens_book", custom_style_prompt=None):
300
  """Enhanced prompt with style configuration"""
301
-
302
- # Get style configuration
303
  if custom_style_prompt:
304
  style_prefix = custom_style_prompt
305
  else:
306
  style_config = get_style_config(style)
307
  style_prefix = style_config["prompt_prefix"]
308
 
309
- # Combine with user prompt
310
  enhanced_prompt = f"{style_prefix}, {scene_visual}"
311
-
312
- # Get negative prompt from style config
313
  negative_prompt = style_config.get("negative", "blurry, low quality, bad anatomy, deformed, ugly")
314
 
315
  return enhanced_prompt, negative_prompt
@@ -318,6 +328,9 @@ def generate_image_with_settings(prompt, model_choice, style, scene_number, cons
318
  width=768, height=1024, custom_style_prompt=None):
319
  """Generate image with customizable style and dimensions"""
320
 
 
 
 
321
  # Get enhanced prompt with style
322
  enhanced_prompt, negative_prompt = enhance_prompt_with_style(prompt, style, custom_style_prompt)
323
 
@@ -330,6 +343,12 @@ def generate_image_with_settings(prompt, model_choice, style, scene_number, cons
330
  try:
331
  pipe = load_model(model_choice)
332
 
 
 
 
 
 
 
333
  image = pipe(
334
  prompt=enhanced_prompt,
335
  negative_prompt=negative_prompt,
@@ -337,18 +356,20 @@ def generate_image_with_settings(prompt, model_choice, style, scene_number, cons
337
  guidance_scale=7.5,
338
  width=width,
339
  height=height,
340
- generator=torch.Generator(device="cpu").manual_seed(scene_seed)
341
  ).images[0]
342
 
343
- print(f"✅ Generated image - Style: {style}, Size: {width}x{height}")
344
  return image
345
 
346
  except Exception as e:
347
  print(f"❌ Generation failed: {str(e)}")
 
 
348
  raise
349
 
350
  # =============================================
351
- # EXISTING FUNCTIONS (Keep your existing functions)
352
  # =============================================
353
 
354
  def ensure_dataset_exists():
@@ -406,6 +427,10 @@ def upload_image_to_hf_dataset(image, project_id, page_number, prompt, style="")
406
  print(f"❌ Failed to upload: {e}")
407
  return None
408
 
 
 
 
 
409
  def get_memory_usage():
410
  """Get current memory usage statistics"""
411
  process = psutil.Process()
@@ -481,6 +506,10 @@ print("🚀 Initializing Enhanced Generator API...")
481
  load_model("dreamshaper-8")
482
  print("✅ Model loaded and ready!")
483
 
 
 
 
 
484
  def save_image_to_local(image, prompt, style="test"):
485
  """Save image to local storage"""
486
  try:
@@ -548,6 +577,10 @@ def refresh_local_images():
548
  except Exception as e:
549
  return []
550
 
 
 
 
 
551
  def create_job(request) -> str:
552
  job_id = str(uuid.uuid4())
553
  job_storage[job_id] = {
@@ -578,7 +611,7 @@ def update_job_status(job_id: str, status: JobStatus, progress: int, message: st
578
  return True
579
 
580
  # =============================================
581
- # ENHANCED: Storybook Generation with Style & Size
582
  # =============================================
583
 
584
  def generate_storybook_background(job_id: str):
@@ -589,14 +622,13 @@ def generate_storybook_background(job_id: str):
589
 
590
  project_id = story_request.project_id or story_request.story_title.replace(' ', '_').lower()
591
 
592
- # Get dimensions based on size preset
593
  width, height = get_image_dimensions(
594
  story_request.image_size,
595
  story_request.custom_width,
596
  story_request.custom_height
597
  )
598
 
599
- print(f"🎬 Starting storybook generation - Style: {story_request.style}, Size: {width}x{height}")
600
 
601
  update_job_status(job_id, JobStatus.PROCESSING, 5, "Starting generation...")
602
 
@@ -609,7 +641,6 @@ def generate_storybook_background(job_id: str):
609
  update_job_status(job_id, JobStatus.PROCESSING, progress, f"Generating page {i+1}/{total_scenes}")
610
 
611
  try:
612
- # Generate image with style and size
613
  image = generate_image_with_settings(
614
  scene.visual,
615
  story_request.model_choice,
@@ -659,24 +690,204 @@ def generate_storybook_background(job_id: str):
659
  update_job_status(job_id, JobStatus.FAILED, 0, f"Failed: {str(e)}")
660
 
661
  # =============================================
662
- # ENHANCED: Infographic Generation with Style & Size
663
  # =============================================
664
 
665
  class InfographicLayoutGenerator:
666
- # Keep your existing InfographicLayoutGenerator class here
667
- # (I'm keeping it minimal for brevity - include your full class)
668
  def __init__(self):
669
  self.title_color = (60, 60, 60)
670
  self.text_color = (40, 40, 40)
671
  self.accent_color = (200, 160, 120)
672
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
673
  def create_food_infographic(self, title, items_with_prompts, generated_images, background_image=None, width=900, height=1600):
674
  """Create infographic with custom dimensions"""
675
- # Your existing implementation here
676
- canvas = Image.new('RGB', (width, height), (255, 255, 255))
677
- # ... rest of your infographic code
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
678
  return canvas
679
 
 
 
 
 
680
  def generate_infographic_background(job_id: str):
681
  """Background task with style and size options"""
682
  try:
@@ -684,7 +895,6 @@ def generate_infographic_background(job_id: str):
684
  request = InfographicRequest(**job_data["request"])
685
  project_id = request.project_id or f"infographic_{int(time.time())}"
686
 
687
- # Get dimensions for infographic (usually story/portrait)
688
  width, height = get_image_dimensions(
689
  request.image_size,
690
  request.custom_width,
@@ -715,14 +925,13 @@ def generate_infographic_background(job_id: str):
715
  update_job_status(job_id, JobStatus.PROCESSING, progress, f"Generating {item.name}")
716
 
717
  try:
718
- # Use the enhanced generation with style
719
  image = generate_image_with_settings(
720
  item.prompt,
721
  "dreamshaper-8",
722
- "realistic", # Food photos use realistic style
723
  i + 1,
724
  None,
725
- 768, 1024 # Standard size for food images
726
  )
727
  generated_images.append(image)
728
 
@@ -775,7 +984,7 @@ def generate_infographic_background(job_id: str):
775
  "image_size": f"{width}x{height}"
776
  }
777
 
778
- update_job_status(job_id, JobStatus.COMPLETED, 100, f"Completed!", result)
779
 
780
  except Exception as e:
781
  update_job_status(job_id, JobStatus.FAILED, 0, f"Failed: {str(e)}")
@@ -788,7 +997,7 @@ def generate_infographic_background(job_id: str):
788
  async def generate_storybook(request: dict, background_tasks: BackgroundTasks):
789
  """Main endpoint with style and size options"""
790
  try:
791
- print(f"📥 Received storybook request - Style: {request.get('style', 'childrens_book')}, Size: {request.get('image_size', 'portrait')}")
792
 
793
  if 'consistency_seed' not in request:
794
  request['consistency_seed'] = random.randint(1000, 9999)
@@ -803,7 +1012,6 @@ async def generate_storybook(request: dict, background_tasks: BackgroundTasks):
803
  job_id = create_job(story_request)
804
  background_tasks.add_task(generate_storybook_background, job_id)
805
 
806
- # Get dimensions for response
807
  width, height = get_image_dimensions(
808
  request.get('image_size', 'portrait'),
809
  request.get('custom_width'),
@@ -826,7 +1034,7 @@ async def generate_storybook(request: dict, background_tasks: BackgroundTasks):
826
  async def generate_infographic(request: dict, background_tasks: BackgroundTasks):
827
  """Generate infographic with style and size options"""
828
  try:
829
- print(f"📥 Received infographic request - Style: {request.get('style', 'food_photography')}, Size: {request.get('image_size', 'story')}")
830
 
831
  if 'project_id' not in request:
832
  request['project_id'] = f"infographic_{int(time.time())}"
@@ -837,7 +1045,6 @@ async def generate_infographic(request: dict, background_tasks: BackgroundTasks)
837
  job_id = create_job(infographic_request)
838
  background_tasks.add_task(generate_infographic_background, job_id)
839
 
840
- # Get dimensions for response
841
  width, height = get_image_dimensions(
842
  request.get('image_size', 'story'),
843
  request.get('custom_width'),
@@ -922,13 +1129,11 @@ def test_generation(prompt, model_choice, style, size_preset, custom_width, cust
922
  if not prompt.strip():
923
  return None, "❌ Please enter a prompt"
924
  try:
925
- # Get dimensions
926
  if size_preset == "custom" and custom_width and custom_height:
927
  width, height = custom_width, custom_height
928
  else:
929
  width, height = SIZE_PRESETS.get(size_preset, (768, 1024))
930
 
931
- # Generate image
932
  image = generate_image_with_settings(
933
  prompt, model_choice, style, 1, None, width, height
934
  )
 
98
  FAILED = "failed"
99
 
100
  # =============================================
101
+ # Style and Size Configuration
102
  # =============================================
103
 
104
  # Available image styles with their prompts
 
185
  }
186
  }
187
 
188
+ # Size presets with valid dimensions (divisible by 8)
189
  SIZE_PRESETS = {
190
  "square": {"width": 768, "height": 768, "description": "1:1 Square"},
191
  "portrait": {"width": 768, "height": 1024, "description": "3:4 Portrait"},
 
194
  "ultrawide": {"width": 1920, "height": 768, "description": "2.5:1 Ultrawide"},
195
  "instagram": {"width": 1080, "height": 1080, "description": "1:1 Instagram"},
196
  "story": {"width": 1080, "height": 1920, "description": "9:16 Story"},
197
+ "facebook": {"width": 1200, "height": 632, "description": "1.91:1 Facebook"},
198
+ "twitter": {"width": 1600, "height": 896, "description": "16:9 Twitter"},
199
  "custom": {"width": 768, "height": 1024, "description": "Custom Size"}
200
  }
201
 
 
214
  characters: List[CharacterDescription] = []
215
  model_choice: str = "dreamshaper-8"
216
  style: str = "childrens_book"
217
+ image_size: str = "portrait"
218
+ custom_width: Optional[int] = None
219
+ custom_height: Optional[int] = None
220
  callback_url: Optional[str] = None
221
  consistency_seed: Optional[int] = None
222
  project_id: Optional[str] = None
 
230
  title: str
231
  items: List[InfographicItem]
232
  style: str = "food_photography"
233
+ image_size: str = "story"
234
  custom_width: Optional[int] = None
235
  custom_height: Optional[int] = None
236
  project_id: Optional[str] = None
 
275
  model_lock = threading.Lock()
276
 
277
  # =============================================
278
+ # HELPER FUNCTIONS WITH DIMENSION VALIDATION
279
  # =============================================
280
 
281
+ def validate_dimensions(width, height):
282
+ """Ensure dimensions are compatible with Stable Diffusion (divisible by 8)"""
283
+ # SD models require dimensions divisible by 8
284
+ width = (width // 8) * 8
285
+ height = (height // 8) * 8
286
+
287
+ # Minimum and maximum constraints
288
+ width = max(256, min(1024, width))
289
+ height = max(256, min(1024, height))
290
+
291
+ return width, height
292
+
293
  def get_image_dimensions(size_preset, custom_width=None, custom_height=None):
294
+ """Get image dimensions based on preset or custom values with validation"""
295
  if size_preset == "custom" and custom_width and custom_height:
296
+ width, height = custom_width, custom_height
297
  elif size_preset in SIZE_PRESETS:
298
  preset = SIZE_PRESETS[size_preset]
299
+ width, height = preset["width"], preset["height"]
300
  else:
301
  # Default to portrait
302
+ width, height = 768, 1024
303
+
304
+ # Validate dimensions
305
+ width, height = validate_dimensions(width, height)
306
+ return width, height
307
 
308
  def get_style_config(style_name):
309
  """Get style configuration or return default"""
310
  if style_name in STYLE_CONFIGS:
311
  return STYLE_CONFIGS[style_name]
 
312
  return STYLE_CONFIGS["childrens_book"]
313
 
314
  def enhance_prompt_with_style(scene_visual, style="childrens_book", custom_style_prompt=None):
315
  """Enhanced prompt with style configuration"""
 
 
316
  if custom_style_prompt:
317
  style_prefix = custom_style_prompt
318
  else:
319
  style_config = get_style_config(style)
320
  style_prefix = style_config["prompt_prefix"]
321
 
 
322
  enhanced_prompt = f"{style_prefix}, {scene_visual}"
 
 
323
  negative_prompt = style_config.get("negative", "blurry, low quality, bad anatomy, deformed, ugly")
324
 
325
  return enhanced_prompt, negative_prompt
 
328
  width=768, height=1024, custom_style_prompt=None):
329
  """Generate image with customizable style and dimensions"""
330
 
331
+ # Validate dimensions before generation
332
+ width, height = validate_dimensions(width, height)
333
+
334
  # Get enhanced prompt with style
335
  enhanced_prompt, negative_prompt = enhance_prompt_with_style(prompt, style, custom_style_prompt)
336
 
 
343
  try:
344
  pipe = load_model(model_choice)
345
 
346
+ if pipe is None:
347
+ raise Exception("Model pipeline not loaded")
348
+
349
+ generator = torch.Generator(device="cpu")
350
+ generator.manual_seed(scene_seed)
351
+
352
  image = pipe(
353
  prompt=enhanced_prompt,
354
  negative_prompt=negative_prompt,
 
356
  guidance_scale=7.5,
357
  width=width,
358
  height=height,
359
+ generator=generator
360
  ).images[0]
361
 
362
+ print(f"✅ Generated image - Style: {style}, Size: {width}x{height}, Seed: {scene_seed}")
363
  return image
364
 
365
  except Exception as e:
366
  print(f"❌ Generation failed: {str(e)}")
367
+ print(f" - Style: {style}")
368
+ print(f" - Size: {width}x{height}")
369
  raise
370
 
371
  # =============================================
372
+ # DATASET FUNCTIONS
373
  # =============================================
374
 
375
  def ensure_dataset_exists():
 
427
  print(f"❌ Failed to upload: {e}")
428
  return None
429
 
430
+ # =============================================
431
+ # MEMORY MANAGEMENT
432
+ # =============================================
433
+
434
  def get_memory_usage():
435
  """Get current memory usage statistics"""
436
  process = psutil.Process()
 
506
  load_model("dreamshaper-8")
507
  print("✅ Model loaded and ready!")
508
 
509
+ # =============================================
510
+ # LOCAL FILE MANAGEMENT
511
+ # =============================================
512
+
513
  def save_image_to_local(image, prompt, style="test"):
514
  """Save image to local storage"""
515
  try:
 
577
  except Exception as e:
578
  return []
579
 
580
+ # =============================================
581
+ # JOB MANAGEMENT
582
+ # =============================================
583
+
584
  def create_job(request) -> str:
585
  job_id = str(uuid.uuid4())
586
  job_storage[job_id] = {
 
611
  return True
612
 
613
  # =============================================
614
+ # STORYBOOK GENERATION
615
  # =============================================
616
 
617
  def generate_storybook_background(job_id: str):
 
622
 
623
  project_id = story_request.project_id or story_request.story_title.replace(' ', '_').lower()
624
 
 
625
  width, height = get_image_dimensions(
626
  story_request.image_size,
627
  story_request.custom_width,
628
  story_request.custom_height
629
  )
630
 
631
+ print(f"🎬 Starting storybook - Style: {story_request.style}, Size: {width}x{height}")
632
 
633
  update_job_status(job_id, JobStatus.PROCESSING, 5, "Starting generation...")
634
 
 
641
  update_job_status(job_id, JobStatus.PROCESSING, progress, f"Generating page {i+1}/{total_scenes}")
642
 
643
  try:
 
644
  image = generate_image_with_settings(
645
  scene.visual,
646
  story_request.model_choice,
 
690
  update_job_status(job_id, JobStatus.FAILED, 0, f"Failed: {str(e)}")
691
 
692
  # =============================================
693
+ # INFOGRAPHIC LAYOUT GENERATOR
694
  # =============================================
695
 
696
  class InfographicLayoutGenerator:
 
 
697
  def __init__(self):
698
  self.title_color = (60, 60, 60)
699
  self.text_color = (40, 40, 40)
700
  self.accent_color = (200, 160, 120)
701
 
702
+ def get_font(self, size, bold=False):
703
+ """Simple font loading"""
704
+ try:
705
+ return ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", size)
706
+ except:
707
+ try:
708
+ return ImageFont.truetype("arial.ttf", size)
709
+ except:
710
+ return ImageFont.load_default()
711
+
712
+ def smart_remove_background(self, image):
713
+ """Simple background removal"""
714
+ try:
715
+ if image.mode != 'RGBA':
716
+ image = image.convert('RGBA')
717
+
718
+ data = image.getdata()
719
+ new_data = []
720
+
721
+ width, height = image.size
722
+
723
+ # Sample edges
724
+ edge_samples = []
725
+ for x in range(0, width, 10):
726
+ if x < len(data):
727
+ edge_samples.append(data[x])
728
+ for x in range(0, width, 10):
729
+ idx = (height-1) * width + x
730
+ if idx < len(data):
731
+ edge_samples.append(data[idx])
732
+
733
+ if not edge_samples:
734
+ return image
735
+
736
+ avg_r = sum(s[0] for s in edge_samples) // len(edge_samples)
737
+ avg_g = sum(s[1] for s in edge_samples) // len(edge_samples)
738
+ avg_b = sum(s[2] for s in edge_samples) // len(edge_samples)
739
+
740
+ threshold = 60
741
+
742
+ for item in data:
743
+ distance = ((item[0] - avg_r) ** 2 +
744
+ (item[1] - avg_g) ** 2 +
745
+ (item[2] - avg_b) ** 2) ** 0.5
746
+
747
+ if distance < threshold:
748
+ new_data.append((255, 255, 255, 0))
749
+ else:
750
+ new_data.append(item)
751
+
752
+ image.putdata(new_data)
753
+ return image
754
+
755
+ except Exception as e:
756
+ return image
757
+
758
+ def create_responsive_layout(self, num_items):
759
+ """Calculate responsive layout"""
760
+ width = 900
761
+ height = 1600
762
+
763
+ if num_items <= 3:
764
+ icon_size = 180
765
+ columns = 1
766
+ rows = num_items
767
+ elif num_items <= 6:
768
+ icon_size = 150
769
+ columns = 2
770
+ rows = (num_items + 1) // 2
771
+ else:
772
+ icon_size = 120
773
+ columns = 2
774
+ rows = (num_items + 1) // 2
775
+
776
+ positions = []
777
+ available_height = height - 300
778
+ start_y = 200
779
+
780
+ for i in range(num_items):
781
+ col = i % columns
782
+ row = i // columns
783
+
784
+ if columns == 1:
785
+ x = (width - icon_size) // 2
786
+ else:
787
+ if col == 0:
788
+ x = width // 4 - icon_size // 2
789
+ else:
790
+ x = 3 * width // 4 - icon_size // 2
791
+
792
+ if rows > 1:
793
+ y = start_y + (row * (available_height // rows))
794
+ else:
795
+ y = start_y + (available_height - icon_size) // 2
796
+
797
+ positions.append((x, y, icon_size))
798
+
799
+ return positions, icon_size, columns
800
+
801
  def create_food_infographic(self, title, items_with_prompts, generated_images, background_image=None, width=900, height=1600):
802
  """Create infographic with custom dimensions"""
803
+ if background_image:
804
+ bg = background_image.resize((width, height), Image.Resampling.LANCZOS)
805
+ canvas = bg.convert('RGB')
806
+ else:
807
+ canvas = Image.new('RGB', (width, height), (255, 255, 255))
808
+
809
+ draw = ImageDraw.Draw(canvas)
810
+ num_items = len(items_with_prompts)
811
+ positions, icon_size, columns = self.create_responsive_layout(num_items)
812
+
813
+ # Draw title
814
+ title_font = self.get_font(48, bold=True)
815
+ title_bbox = draw.textbbox((0, 0), title, font=title_font)
816
+ title_width = title_bbox[2] - title_bbox[0]
817
+ title_x = (width - title_width) // 2
818
+ title_y = 60
819
+
820
+ draw.rounded_rectangle(
821
+ [title_x-20, title_y-10, title_x+title_width+20, title_y+60],
822
+ radius=20, fill=(250, 250, 250), outline=None
823
+ )
824
+ draw.text((title_x, title_y), title, font=title_font, fill=self.title_color)
825
+
826
+ # Draw subtitle
827
+ subtitle_font = self.get_font(24)
828
+ subtitle = "Healthy Foods · 健康食品"
829
+ subtitle_bbox = draw.textbbox((0, 0), subtitle, font=subtitle_font)
830
+ subtitle_width = subtitle_bbox[2] - subtitle_bbox[0]
831
+ subtitle_x = (width - subtitle_width) // 2
832
+ subtitle_y = title_y + 60
833
+ draw.text((subtitle_x, subtitle_y), subtitle, font=subtitle_font, fill=(120, 120, 120))
834
+
835
+ # Place items
836
+ for i, ((food_name, _), img) in enumerate(zip(items_with_prompts, generated_images)):
837
+ if i >= len(positions):
838
+ break
839
+
840
+ x, y, size = positions[i]
841
+
842
+ if img:
843
+ icon_img = self.smart_remove_background(img)
844
+ icon_img.thumbnail((size, size), Image.Resampling.LANCZOS)
845
+
846
+ mask = Image.new('L', (size, size), 0)
847
+ mask_draw = ImageDraw.Draw(mask)
848
+ mask_draw.ellipse((0, 0, size, size), fill=255)
849
+
850
+ icon_final = Image.new('RGBA', (size, size), (0, 0, 0, 0))
851
+ paste_x = (size - icon_img.width) // 2
852
+ paste_y = (size - icon_img.height) // 2
853
+ icon_final.paste(icon_img, (paste_x, paste_y), icon_img)
854
+
855
+ circle_bg = Image.new('RGBA', (size+20, size+20), (0, 0, 0, 0))
856
+ circle_draw = ImageDraw.Draw(circle_bg)
857
+ circle_draw.ellipse([5, 5, size+15, size+15], fill=(245, 245, 245), outline=None)
858
+ canvas.paste(circle_bg, (x-10, y-10), circle_bg)
859
+ canvas.paste(icon_final, (x, y), icon_final)
860
+
861
+ name_font = self.get_font(28 if num_items > 6 else 32, bold=True)
862
+
863
+ if columns == 1:
864
+ name_x = x + (size // 2)
865
+ name_y = y + size + 15
866
+ draw.text((name_x, name_y), food_name, font=name_font, fill=self.text_color, anchor="mm")
867
+ number_font = self.get_font(20)
868
+ draw.text((name_x, name_y + 35), f"#{i+1:02d}", font=number_font, fill=(150, 150, 150), anchor="mm")
869
+ else:
870
+ name_x = x + size + 15
871
+ name_y = y + (size // 2) - 20
872
+ draw.text((name_x, name_y), food_name, font=name_font, fill=self.text_color)
873
+ number_font = self.get_font(18)
874
+ draw.text((name_x, name_y + 30), f"#{i+1:02d}", font=number_font, fill=(150, 150, 150))
875
+
876
+ # Draw footer
877
+ footer_font = self.get_font(20)
878
+ footer_text = "· Fresh & Healthy ·"
879
+ footer_bbox = draw.textbbox((0, 0), footer_text, font=footer_font)
880
+ footer_width = footer_bbox[2] - footer_bbox[0]
881
+ footer_x = (width - footer_width) // 2
882
+ footer_y = height - 50
883
+ draw.text((footer_x, footer_y), footer_text, font=footer_font, fill=(150, 150, 150))
884
+
885
  return canvas
886
 
887
+ # =============================================
888
+ # INFOGRAPHIC GENERATION
889
+ # =============================================
890
+
891
  def generate_infographic_background(job_id: str):
892
  """Background task with style and size options"""
893
  try:
 
895
  request = InfographicRequest(**job_data["request"])
896
  project_id = request.project_id or f"infographic_{int(time.time())}"
897
 
 
898
  width, height = get_image_dimensions(
899
  request.image_size,
900
  request.custom_width,
 
925
  update_job_status(job_id, JobStatus.PROCESSING, progress, f"Generating {item.name}")
926
 
927
  try:
 
928
  image = generate_image_with_settings(
929
  item.prompt,
930
  "dreamshaper-8",
931
+ "realistic",
932
  i + 1,
933
  None,
934
+ 768, 1024
935
  )
936
  generated_images.append(image)
937
 
 
984
  "image_size": f"{width}x{height}"
985
  }
986
 
987
+ update_job_status(job_id, JobStatus.COMPLETED, 100, "Completed!", result)
988
 
989
  except Exception as e:
990
  update_job_status(job_id, JobStatus.FAILED, 0, f"Failed: {str(e)}")
 
997
  async def generate_storybook(request: dict, background_tasks: BackgroundTasks):
998
  """Main endpoint with style and size options"""
999
  try:
1000
+ print(f"📥 Received storybook request - Style: {request.get('style', 'childrens_book')}")
1001
 
1002
  if 'consistency_seed' not in request:
1003
  request['consistency_seed'] = random.randint(1000, 9999)
 
1012
  job_id = create_job(story_request)
1013
  background_tasks.add_task(generate_storybook_background, job_id)
1014
 
 
1015
  width, height = get_image_dimensions(
1016
  request.get('image_size', 'portrait'),
1017
  request.get('custom_width'),
 
1034
  async def generate_infographic(request: dict, background_tasks: BackgroundTasks):
1035
  """Generate infographic with style and size options"""
1036
  try:
1037
+ print(f"📥 Received infographic request - Style: {request.get('style', 'food_photography')}")
1038
 
1039
  if 'project_id' not in request:
1040
  request['project_id'] = f"infographic_{int(time.time())}"
 
1045
  job_id = create_job(infographic_request)
1046
  background_tasks.add_task(generate_infographic_background, job_id)
1047
 
 
1048
  width, height = get_image_dimensions(
1049
  request.get('image_size', 'story'),
1050
  request.get('custom_width'),
 
1129
  if not prompt.strip():
1130
  return None, "❌ Please enter a prompt"
1131
  try:
 
1132
  if size_preset == "custom" and custom_width and custom_height:
1133
  width, height = custom_width, custom_height
1134
  else:
1135
  width, height = SIZE_PRESETS.get(size_preset, (768, 1024))
1136
 
 
1137
  image = generate_image_with_settings(
1138
  prompt, model_choice, style, 1, None, width, height
1139
  )