LeafCat79 commited on
Commit
e2b36e5
·
verified ·
1 Parent(s): 311fb99

Add shape-aware sprite families

Browse files
Files changed (1) hide show
  1. app.py +109 -4
app.py CHANGED
@@ -592,6 +592,100 @@ def draw_sprite(spec: AssetSpec, rng: random.Random) -> bytes:
592
  accent = colors[1] + (255,)
593
  trim = colors[3] + (255,)
594
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
595
  if role in ("bullet", "projectile", "laser", "shot") or any(word in role_prompt for word in ("bullet", "projectile", "laser", "energy")):
596
  glow = colors[1] + (85,)
597
  core_width = rng.randint(7, 13) * scale
@@ -944,23 +1038,29 @@ def build_prompt_preview(specs: list[AssetSpec]) -> str:
944
  return "\n\n".join(f"{spec.role}:\n{spec.prompt}" for spec in specs)
945
 
946
 
 
 
 
 
947
  def generate_images_and_game(html_code: str, roles: str, style_hint: str):
948
  if not html_code.strip():
949
- return "", "Paste HTML game code first.", [], "", ""
950
 
951
  specs = parse_assets(roles, style_hint or "pixel art style")
952
  if not specs:
953
- return html_code, "Add at least one asset role, like `player: brave knight`.", [], "", build_preview(html_code)
954
 
955
  assets: dict[str, str] = {}
956
  gallery = []
957
  errors = []
 
958
  run_id = time.time_ns()
959
 
960
  for index, spec in enumerate(specs):
961
  data_uri, gallery_path, error = generate_asset(spec, index, run_id)
962
  assets[spec.role] = data_uri
963
  gallery.append((gallery_path, f"{spec.role} -> {spec.filename}"))
 
964
  if error:
965
  errors.append(f"{spec.role}: fallback used ({error})")
966
 
@@ -970,7 +1070,7 @@ def generate_images_and_game(html_code: str, roles: str, style_hint: str):
970
  status = f"Generated and embedded {len(specs)} fresh asset(s) using {source}. Run {str(run_id)[-6:]}."
971
  if errors:
972
  status += "\n\n" + "\n".join(f"{item}: no HF image returned, used local style fallback" for item in [e.split(':', 1)[0] for e in errors])
973
- return rewritten, status, gallery, build_prompt_preview(specs), build_preview(rewritten)
974
 
975
 
976
  with gr.Blocks(title="Image Generator for HTML Games") as demo:
@@ -1014,6 +1114,11 @@ with gr.Blocks(title="Image Generator for HTML Games") as demo:
1014
  lines=8,
1015
  interactive=False,
1016
  )
 
 
 
 
 
1017
 
1018
  gr.Markdown("## Game preview")
1019
  preview = gr.HTML(build_preview(STARTER_HTML))
@@ -1021,7 +1126,7 @@ with gr.Blocks(title="Image Generator for HTML Games") as demo:
1021
  generate_btn.click(
1022
  fn=generate_images_and_game,
1023
  inputs=[html_input, roles, style],
1024
- outputs=[output_code, status, gallery, prompt_preview, preview],
1025
  )
1026
 
1027
 
 
592
  accent = colors[1] + (255,)
593
  trim = colors[3] + (255,)
594
 
595
+ if any(word in role_prompt for word in ("car", "racing", "racer", "buggy", "vehicle", "truck")):
596
+ shadow = (int(cx - 43 * scale), int(cy + 28 * scale), int(cx + 43 * scale), int(cy + 42 * scale))
597
+ draw.ellipse(shadow, fill=(0, 0, 0, 58))
598
+ draw.rounded_rectangle(
599
+ (cx - 34 * scale, cy - 42 * scale, cx + 34 * scale, cy + 42 * scale),
600
+ radius=int(18 * scale),
601
+ fill=outline,
602
+ )
603
+ draw.rounded_rectangle(
604
+ (cx - 26 * scale, cy - 34 * scale, cx + 26 * scale, cy + 34 * scale),
605
+ radius=int(13 * scale),
606
+ fill=body,
607
+ )
608
+ draw.rectangle((cx - 15 * scale, cy - 24 * scale, cx + 15 * scale, cy - 5 * scale), fill=accent)
609
+ draw.rectangle((cx - 18 * scale, cy + 8 * scale, cx + 18 * scale, cy + 27 * scale), fill=trim)
610
+ for side in (-1, 1):
611
+ draw.rounded_rectangle(
612
+ (cx + side * 27 * scale - 9 * scale, cy - 30 * scale, cx + side * 27 * scale + 9 * scale, cy - 13 * scale),
613
+ radius=int(5 * scale),
614
+ fill=(20, 22, 27, 255),
615
+ )
616
+ draw.rounded_rectangle(
617
+ (cx + side * 27 * scale - 9 * scale, cy + 15 * scale, cx + side * 27 * scale + 9 * scale, cy + 32 * scale),
618
+ radius=int(5 * scale),
619
+ fill=(20, 22, 27, 255),
620
+ )
621
+ out = io.BytesIO()
622
+ image.save(out, format="PNG")
623
+ return out.getvalue()
624
+
625
+ if any(word in role_prompt for word in ("turret", "tower", "cannon", "launcher")):
626
+ draw.ellipse((cx - 38 * scale, cy + 22 * scale, cx + 38 * scale, cy + 45 * scale), fill=(0, 0, 0, 60))
627
+ draw.ellipse((cx - 36 * scale, cy - 12 * scale, cx + 36 * scale, cy + 42 * scale), fill=outline)
628
+ draw.ellipse((cx - 28 * scale, cy - 5 * scale, cx + 28 * scale, cy + 34 * scale), fill=body)
629
+ barrel_angle = rng.choice((-0.45, -0.2, 0, 0.2, 0.45))
630
+ length = 52 * scale
631
+ bx = cx + math.sin(barrel_angle) * length
632
+ by = cy - 38 * scale
633
+ draw.line((cx, cy - 5 * scale, bx, by), fill=outline, width=max(10, int(17 * scale)))
634
+ draw.line((cx, cy - 5 * scale, bx, by), fill=trim, width=max(5, int(9 * scale)))
635
+ draw.rectangle((cx - 19 * scale, cy + 15 * scale, cx + 19 * scale, cy + 39 * scale), fill=accent)
636
+ out = io.BytesIO()
637
+ image.save(out, format="PNG")
638
+ return out.getvalue()
639
+
640
+ if role in ("coin", "gem", "key", "orb", "pickup", "collectible") or any(
641
+ word in role_prompt for word in ("coin", "gem", "key", "pickup", "collectible", "powerup", "power-up", "treasure")
642
+ ):
643
+ draw.ellipse((cx - 33 * scale, cy - 33 * scale, cx + 33 * scale, cy + 33 * scale), fill=outline)
644
+ if any(word in role_prompt for word in ("gem", "crystal", "diamond")):
645
+ points = [
646
+ (cx, cy - 43 * scale),
647
+ (cx + 36 * scale, cy - 8 * scale),
648
+ (cx + 20 * scale, cy + 40 * scale),
649
+ (cx - 20 * scale, cy + 40 * scale),
650
+ (cx - 36 * scale, cy - 8 * scale),
651
+ ]
652
+ draw.polygon(points, fill=body)
653
+ draw.polygon([(cx, cy - 34 * scale), (cx + 18 * scale, cy - 4 * scale), (cx, cy + 28 * scale), (cx - 18 * scale, cy - 4 * scale)], fill=accent)
654
+ elif "key" in role_prompt:
655
+ draw.ellipse((cx - 30 * scale, cy - 20 * scale, cx + 8 * scale, cy + 18 * scale), fill=body)
656
+ draw.ellipse((cx - 17 * scale, cy - 8 * scale, cx - 3 * scale, cy + 6 * scale), fill=(0, 0, 0, 0))
657
+ draw.rounded_rectangle((cx + 2 * scale, cy - 5 * scale, cx + 43 * scale, cy + 7 * scale), radius=int(5 * scale), fill=accent)
658
+ draw.rectangle((cx + 30 * scale, cy + 4 * scale, cx + 43 * scale, cy + 17 * scale), fill=trim)
659
+ else:
660
+ draw.ellipse((cx - 27 * scale, cy - 27 * scale, cx + 27 * scale, cy + 27 * scale), fill=body)
661
+ draw.ellipse((cx - 17 * scale, cy - 17 * scale, cx + 17 * scale, cy + 17 * scale), outline=trim, width=max(3, int(7 * scale)))
662
+ draw.arc((cx - 12 * scale, cy - 20 * scale, cx + 20 * scale, cy + 13 * scale), 195, 300, fill=(255, 255, 255, 180), width=max(2, int(4 * scale)))
663
+ out = io.BytesIO()
664
+ image.save(out, format="PNG")
665
+ return out.getvalue()
666
+
667
+ if any(word in role_prompt for word in ("slime", "blob", "gel", "jelly")):
668
+ draw.ellipse((cx - 40 * scale, cy + 24 * scale, cx + 40 * scale, cy + 42 * scale), fill=(0, 0, 0, 55))
669
+ draw.pieslice((cx - 40 * scale, cy - 28 * scale, cx + 40 * scale, cy + 52 * scale), 180, 360, fill=outline)
670
+ draw.pieslice((cx - 32 * scale, cy - 20 * scale, cx + 32 * scale, cy + 42 * scale), 180, 360, fill=body)
671
+ for side in (-1, 1):
672
+ draw.ellipse((cx + side * 11 * scale - 5 * scale, cy + 3 * scale, cx + side * 11 * scale + 5 * scale, cy + 13 * scale), fill=(255, 255, 255, 230))
673
+ draw.arc((cx - 12 * scale, cy + 12 * scale, cx + 12 * scale, cy + 27 * scale), 10, 170, fill=trim, width=max(2, int(4 * scale)))
674
+ out = io.BytesIO()
675
+ image.save(out, format="PNG")
676
+ return out.getvalue()
677
+
678
+ if any(word in role_prompt for word in ("beetle", "bug", "spider", "insect", "crawler", "creep")):
679
+ draw.ellipse((cx - 28 * scale, cy - 34 * scale, cx + 28 * scale, cy + 34 * scale), fill=outline)
680
+ draw.ellipse((cx - 21 * scale, cy - 27 * scale, cx + 21 * scale, cy + 29 * scale), fill=body)
681
+ draw.line((cx, cy - 25 * scale, cx, cy + 30 * scale), fill=trim, width=max(2, int(4 * scale)))
682
+ for y in (-18, 0, 18):
683
+ for side in (-1, 1):
684
+ draw.line((cx + side * 18 * scale, cy + y * scale, cx + side * 45 * scale, cy + (y + rng.choice((-10, 10))) * scale), fill=outline, width=max(3, int(5 * scale)))
685
+ out = io.BytesIO()
686
+ image.save(out, format="PNG")
687
+ return out.getvalue()
688
+
689
  if role in ("bullet", "projectile", "laser", "shot") or any(word in role_prompt for word in ("bullet", "projectile", "laser", "energy")):
690
  glow = colors[1] + (85,)
691
  core_width = rng.randint(7, 13) * scale
 
1038
  return "\n\n".join(f"{spec.role}:\n{spec.prompt}" for spec in specs)
1039
 
1040
 
1041
+ def build_model_report(rows: list[tuple[str, str]]) -> str:
1042
+ return "\n".join(f"{role}: {source}" for role, source in rows)
1043
+
1044
+
1045
  def generate_images_and_game(html_code: str, roles: str, style_hint: str):
1046
  if not html_code.strip():
1047
+ return "", "Paste HTML game code first.", [], "", "", ""
1048
 
1049
  specs = parse_assets(roles, style_hint or "pixel art style")
1050
  if not specs:
1051
+ return html_code, "Add at least one asset role, like `player: brave knight`.", [], "", "", build_preview(html_code)
1052
 
1053
  assets: dict[str, str] = {}
1054
  gallery = []
1055
  errors = []
1056
+ model_rows = []
1057
  run_id = time.time_ns()
1058
 
1059
  for index, spec in enumerate(specs):
1060
  data_uri, gallery_path, error = generate_asset(spec, index, run_id)
1061
  assets[spec.role] = data_uri
1062
  gallery.append((gallery_path, f"{spec.role} -> {spec.filename}"))
1063
+ model_rows.append((spec.role, HF_IMAGE_MODEL if error is None and HF_TOKEN else "local style fallback"))
1064
  if error:
1065
  errors.append(f"{spec.role}: fallback used ({error})")
1066
 
 
1070
  status = f"Generated and embedded {len(specs)} fresh asset(s) using {source}. Run {str(run_id)[-6:]}."
1071
  if errors:
1072
  status += "\n\n" + "\n".join(f"{item}: no HF image returned, used local style fallback" for item in [e.split(':', 1)[0] for e in errors])
1073
+ return rewritten, status, gallery, build_prompt_preview(specs), build_model_report(model_rows), build_preview(rewritten)
1074
 
1075
 
1076
  with gr.Blocks(title="Image Generator for HTML Games") as demo:
 
1114
  lines=8,
1115
  interactive=False,
1116
  )
1117
+ model_report = gr.Textbox(
1118
+ label="Model/source used by role",
1119
+ lines=5,
1120
+ interactive=False,
1121
+ )
1122
 
1123
  gr.Markdown("## Game preview")
1124
  preview = gr.HTML(build_preview(STARTER_HTML))
 
1126
  generate_btn.click(
1127
  fn=generate_images_and_game,
1128
  inputs=[html_input, roles, style],
1129
+ outputs=[output_code, status, gallery, prompt_preview, model_report, preview],
1130
  )
1131
 
1132