victordfmsc commited on
Commit
2302465
Β·
verified Β·
1 Parent(s): 3ea2df4

v10: Pillow speech bubbles overlay for legible dialogue + Gemini 3 Pro primary

Browse files
Files changed (1) hide show
  1. app.py +96 -0
app.py CHANGED
@@ -270,6 +270,98 @@ def build_page_prompt(page_data, script, style):
270
  return prompt[:1800]
271
 
272
  # ── GENERATE ONE PAGE ─────────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  def generate_page(page_data, script, style, title, page_num, total, api_key):
274
  prompt = build_page_prompt(page_data, script, style)
275
  seed = page_num * 137
@@ -291,6 +383,10 @@ def generate_page(page_data, script, style, title, page_num, total, api_key):
291
  print(' Using placeholder')
292
  art = placeholder(page_num, title)
293
 
 
 
 
 
294
  final = add_header_footer(art, page_num, total, title)
295
  print('Page ' + str(page_num) + ' done')
296
  return final
 
270
  return prompt[:1800]
271
 
272
  # ── GENERATE ONE PAGE ─────────────────────────────────────────────────────────
273
+
274
+ def draw_speech_bubbles(img, panels):
275
+ """Draw readable speech bubbles with Pillow over the comic page."""
276
+ draw = ImageDraw.Draw(img)
277
+ W, H = img.size
278
+
279
+ # Calculate panel zones (4 panels: top-wide, mid-left, mid-right, bottom-wide)
280
+ panel_zones = [
281
+ (0, 0, W, H//2), # Panel 1 top half
282
+ (0, H//2, W//2, H), # Panel 2 bottom-left
283
+ (W//2, H//2, W, H), # Panel 3 bottom-right
284
+ ]
285
+ if len(panels) >= 4:
286
+ panel_zones = [
287
+ (0, 0, W, H//3), # Panel 1 top strip
288
+ (0, H//3, W//2, 2*H//3), # Panel 2 mid-left
289
+ (W//2, H//3, W, 2*H//3), # Panel 3 mid-right
290
+ (0, 2*H//3, W, H), # Panel 4 bottom strip
291
+ ]
292
+
293
+ font_b = fnt(_B, 22)
294
+ font_r = fnt(_R, 20)
295
+
296
+ for i, panel in enumerate(panels[:len(panel_zones)]):
297
+ if i >= len(panel_zones):
298
+ break
299
+ px0, py0, px1, py1 = panel_zones[i]
300
+ dialogues = panel.get('dialogue', [])
301
+ caption = panel.get('caption', '')
302
+
303
+ bubble_y = py1 - 15 # Start from bottom of panel
304
+
305
+ # Draw dialogues (from bottom up)
306
+ for j, dlg in enumerate(reversed(dialogues[-2:])): # max 2 bubbles per panel
307
+ char = dlg.get('character', '')
308
+ text = dlg.get('text', '')
309
+ if not text:
310
+ continue
311
+
312
+ # Wrap text
313
+ max_chars = 25
314
+ words = text.split()
315
+ wrapped_lines = []
316
+ line = ''
317
+ for w in words:
318
+ if len(line) + len(w) + 1 <= max_chars:
319
+ line = (line + ' ' + w).strip()
320
+ else:
321
+ if line: wrapped_lines.append(line)
322
+ line = w
323
+ if line: wrapped_lines.append(line)
324
+ wrapped_lines = wrapped_lines[:3] # max 3 lines
325
+
326
+ # Calculate bubble dimensions
327
+ line_h = 26
328
+ text_h = len(wrapped_lines) * line_h + 8
329
+ char_w = max(len(l) for l in wrapped_lines) * 13 + 20
330
+ char_w = min(char_w, (px1 - px0) - 20)
331
+ bub_h = text_h + 30 # space for char name
332
+
333
+ # Position bubble
334
+ bub_x0 = px0 + 15
335
+ bub_y1 = bubble_y
336
+ bub_y0 = bub_y1 - bub_h
337
+ bub_x1 = bub_x0 + char_w
338
+
339
+ if bub_y0 < py0 + 10:
340
+ break # No space
341
+
342
+ # Draw white rounded bubble
343
+ draw.rounded_rectangle([bub_x0, bub_y0, bub_x1, bub_y1], radius=12,
344
+ fill=(255,255,255), outline=(20,20,20), width=2)
345
+
346
+ # Character name in bold
347
+ draw.text((bub_x0+10, bub_y0+6), char[:12] + ':', font=font_b, fill=(20,20,80))
348
+
349
+ # Dialogue text
350
+ for k, wl in enumerate(wrapped_lines):
351
+ draw.text((bub_x0+10, bub_y0+28+k*line_h), wl, font=font_r, fill=(20,20,20))
352
+
353
+ bubble_y = bub_y0 - 8 # Stack bubbles upward
354
+
355
+ # Draw caption box at top of panel if exists
356
+ if caption:
357
+ cap_text = caption[:50]
358
+ cap_w = len(cap_text) * 10 + 20
359
+ cap_w = min(cap_w, px1 - px0 - 10)
360
+ draw.rectangle([px0+5, py0+5, px0+cap_w, py0+30], fill=(255,255,220), outline=(100,100,0), width=1)
361
+ draw.text((px0+10, py0+8), cap_text, font=fnt(_R, 16), fill=(60,40,0))
362
+
363
+ return img
364
+
365
  def generate_page(page_data, script, style, title, page_num, total, api_key):
366
  prompt = build_page_prompt(page_data, script, style)
367
  seed = page_num * 137
 
383
  print(' Using placeholder')
384
  art = placeholder(page_num, title)
385
 
386
+ # Add readable speech bubbles with Pillow (works even with Pollinations)
387
+ panels = page_data.get('panels', [])
388
+ art = draw_speech_bubbles(art.copy(), panels)
389
+
390
  final = add_header_footer(art, page_num, total, title)
391
  print('Page ' + str(page_num) + ' done')
392
  return final