LeafCat79 commited on
Commit
3e284d8
路
verified 路
1 Parent(s): 8be5bab

Update app.py

Browse files

Guide neural sprites by camera and palette

Files changed (1) hide show
  1. app.py +98 -18
app.py CHANGED
@@ -126,6 +126,7 @@ PRIMARY_LORA_MODEL = os.environ.get("PRIMARY_LORA_MODEL", "latent-consistency/lc
126
  PRIMARY_IMAGE_STEPS = int(os.environ.get("PRIMARY_IMAGE_STEPS", "4"))
127
  PRIMARY_GUIDANCE_SCALE = float(os.environ.get("PRIMARY_GUIDANCE_SCALE", "1.5"))
128
  PRIMARY_BACKGROUND_STRENGTH = float(os.environ.get("PRIMARY_BACKGROUND_STRENGTH", "0.72"))
 
129
  USE_PRIMARY_IMAGE_MODEL = os.environ.get("USE_PRIMARY_IMAGE_MODEL", "1") == "1"
130
  REQUIRE_PRIMARY_IMAGE_MODEL = os.environ.get(
131
  "REQUIRE_PRIMARY_IMAGE_MODEL",
@@ -1371,6 +1372,70 @@ def draw_sprite(spec: AssetSpec, rng: random.Random) -> bytes:
1371
  image.save(out, format="PNG")
1372
  return out.getvalue()
1373
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1374
  shadow = (int(cx - 36 * scale), int(cy + 34 * scale), int(cx + 36 * scale), int(cy + 48 * scale))
1375
  draw.ellipse(shadow, fill=(0, 0, 0, 65))
1376
 
@@ -1476,7 +1541,11 @@ def diffusion_negative_prompt(spec: AssetSpec) -> str:
1476
  "particle effect, particles, confetti, sparkles, floating dots, glowing orbs, embers, lens flare"
1477
  + camera_negative
1478
  )
1479
- return common + ", landscape, environment scene, room, horizon, opaque background, drop shadow"
 
 
 
 
1480
 
1481
 
1482
  def build_camera_control_image(spec: AssetSpec, width: int = 1024, height: int = 576) -> Image.Image:
@@ -1581,10 +1650,24 @@ def primary_diffusion_prompt(spec: AssetSpec) -> str:
1581
  )
1582
  return (
1583
  f"{spec.prompt} One complete isolated game sprite only, centered, fully visible, readable silhouette, "
1584
- "camera angle exactly as specified, on a plain uniform white studio background."
 
1585
  )
1586
 
1587
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1588
  def primary_diffusion_png(spec: AssetSpec, index: int, run_id: int) -> tuple[bytes | None, str | None]:
1589
  """Generate every production asset with SSD-1B; backgrounds use camera-guided img2img."""
1590
  if PRIMARY_MODEL_ERROR:
@@ -1606,18 +1689,15 @@ def primary_diffusion_png(spec: AssetSpec, index: int, run_id: int) -> tuple[byt
1606
  "guidance_scale": PRIMARY_GUIDANCE_SCALE,
1607
  "generator": generator,
1608
  }
1609
- if is_background_spec(spec):
1610
- if PRIMARY_IMAGE_TO_IMAGE_PIPE is None:
1611
- return None, "primary camera-guided image-to-image pipeline is unavailable"
1612
- guide_png = local_asset_png(spec, index, run_id)
1613
- guide = Image.open(io.BytesIO(guide_png)).convert("RGB").resize((1344, 768), Image.LANCZOS)
1614
- image = PRIMARY_IMAGE_TO_IMAGE_PIPE(
1615
- image=guide,
1616
- strength=PRIMARY_BACKGROUND_STRENGTH,
1617
- **common,
1618
- ).images[0]
1619
- else:
1620
- image = PRIMARY_TEXT_PIPE(width=1024, height=1024, **common).images[0]
1621
  return polish_diffusion_asset(image, spec), None
1622
  except Exception as exc:
1623
  return None, short_error(exc)
@@ -2033,7 +2113,7 @@ def model_source_note(image_model: str) -> str:
2033
  if "via fal-ai" in lowered or "via replicate" in lowered:
2034
  return "routed image-to-image refinement鈥攑reserves a layout guide but consumes account inference credit"
2035
  if "zerogpu" in lowered:
2036
- return "locally hosted neural diffusion鈥攏o per-image API credit; backgrounds are camera-guided and still require visual review"
2037
  return "diffusion output鈥攈igher visual variety; verify subject, perspective, alpha edges, and prompt compliance"
2038
 
2039
 
@@ -2042,7 +2122,7 @@ def model_configuration_summary() -> str:
2042
  sprite_chain = []
2043
  primary_source = f"{PRIMARY_IMAGE_MODEL} + {PRIMARY_LORA_MODEL} on ZeroGPU"
2044
  if USE_PRIMARY_IMAGE_MODEL:
2045
- sprite_chain.append(primary_source)
2046
  if USE_DIFFUSION_FOR_SPRITES:
2047
  sprite_chain.append(FREE_IMAGE_MODEL)
2048
  if USE_HF_IMAGE_PROVIDER:
@@ -2089,8 +2169,8 @@ def model_configuration_summary() -> str:
2089
  f"prompts: `{prompt_source}` 路 sprites: `{sprite_source}` 路 backgrounds: `{background_source}` 路 "
2090
  f"remote image fallback: `{remote_fallback}` 路 neural image models: `{neural_status}` 路 "
2091
  f"neural enforcement: `{enforcement}` 路 primary readiness: `{readiness}`. "
2092
- "Every production image is generated by the primary neural model. Backgrounds start from a deterministic "
2093
- "camera guide and are refined with image-to-image diffusion; the procedural renderer remains an emergency "
2094
  "development fallback and is blocked in the deployed Space when the primary model fails."
2095
  )
2096
 
 
126
  PRIMARY_IMAGE_STEPS = int(os.environ.get("PRIMARY_IMAGE_STEPS", "4"))
127
  PRIMARY_GUIDANCE_SCALE = float(os.environ.get("PRIMARY_GUIDANCE_SCALE", "1.5"))
128
  PRIMARY_BACKGROUND_STRENGTH = float(os.environ.get("PRIMARY_BACKGROUND_STRENGTH", "0.72"))
129
+ PRIMARY_SPRITE_STRENGTH = float(os.environ.get("PRIMARY_SPRITE_STRENGTH", "0.72"))
130
  USE_PRIMARY_IMAGE_MODEL = os.environ.get("USE_PRIMARY_IMAGE_MODEL", "1") == "1"
131
  REQUIRE_PRIMARY_IMAGE_MODEL = os.environ.get(
132
  "REQUIRE_PRIMARY_IMAGE_MODEL",
 
1372
  image.save(out, format="PNG")
1373
  return out.getvalue()
1374
 
1375
+ if spec.camera == "side_view":
1376
+ # Profile silhouette used both by the procedural fallback and as the
1377
+ # neural img2img camera guide. A single visible eye and forward nose
1378
+ # prevent the model from reverting to its common front-facing pose.
1379
+ draw.ellipse((cx - 28 * scale, cy + 31 * scale, cx + 31 * scale, cy + 46 * scale), fill=(0, 0, 0, 55))
1380
+ draw.rounded_rectangle(
1381
+ (cx - 15 * scale, cy - 16 * scale, cx + 17 * scale, cy + 32 * scale),
1382
+ radius=max(4, int(10 * scale)),
1383
+ fill=outline,
1384
+ )
1385
+ draw.rounded_rectangle(
1386
+ (cx - 10 * scale, cy - 11 * scale, cx + 12 * scale, cy + 27 * scale),
1387
+ radius=max(3, int(7 * scale)),
1388
+ fill=body,
1389
+ )
1390
+ draw.ellipse((cx - 17 * scale, cy - 50 * scale, cx + 17 * scale, cy - 16 * scale), fill=outline)
1391
+ draw.ellipse((cx - 12 * scale, cy - 45 * scale, cx + 13 * scale, cy - 20 * scale), fill=accent)
1392
+ draw.polygon(
1393
+ ((cx + 12 * scale, cy - 36 * scale), (cx + 24 * scale, cy - 31 * scale), (cx + 12 * scale, cy - 27 * scale)),
1394
+ fill=accent,
1395
+ )
1396
+ draw.ellipse((cx + 5 * scale, cy - 37 * scale, cx + 10 * scale, cy - 32 * scale), fill=(255, 255, 255, 245))
1397
+ draw.line((cx + 7 * scale, cy - 4 * scale, cx + 35 * scale, cy + 11 * scale), fill=outline, width=max(5, int(9 * scale)))
1398
+ draw.line((cx + 7 * scale, cy - 4 * scale, cx + 35 * scale, cy + 11 * scale), fill=trim, width=max(2, int(5 * scale)))
1399
+ draw.line((cx - 5 * scale, cy + 27 * scale, cx - 19 * scale, cy + 52 * scale), fill=outline, width=max(5, int(10 * scale)))
1400
+ draw.line((cx + 7 * scale, cy + 27 * scale, cx + 24 * scale, cy + 50 * scale), fill=outline, width=max(5, int(10 * scale)))
1401
+ if "bow" in role_prompt or "archer" in role_prompt or "ranger" in role_prompt:
1402
+ draw.arc((cx + 25 * scale, cy - 19 * scale, cx + 56 * scale, cy + 35 * scale), 95, 265, fill=trim, width=max(2, int(4 * scale)))
1403
+ out = io.BytesIO()
1404
+ image.save(out, format="PNG")
1405
+ return out.getvalue()
1406
+
1407
+ if spec.camera == "isometric":
1408
+ # Offset limbs and facial features establish a three-quarter pose rather
1409
+ # than the symmetric front view used by the generic fallback.
1410
+ draw.ellipse((cx - 31 * scale, cy + 29 * scale, cx + 38 * scale, cy + 46 * scale), fill=(0, 0, 0, 58))
1411
+ draw.polygon(
1412
+ (
1413
+ (cx - 21 * scale, cy - 12 * scale),
1414
+ (cx + 12 * scale, cy - 20 * scale),
1415
+ (cx + 26 * scale, cy + 24 * scale),
1416
+ (cx - 7 * scale, cy + 35 * scale),
1417
+ ),
1418
+ fill=outline,
1419
+ )
1420
+ draw.polygon(
1421
+ (
1422
+ (cx - 14 * scale, cy - 8 * scale),
1423
+ (cx + 8 * scale, cy - 14 * scale),
1424
+ (cx + 19 * scale, cy + 20 * scale),
1425
+ (cx - 4 * scale, cy + 27 * scale),
1426
+ ),
1427
+ fill=body,
1428
+ )
1429
+ draw.ellipse((cx - 18 * scale, cy - 49 * scale, cx + 18 * scale, cy - 14 * scale), fill=outline)
1430
+ draw.ellipse((cx - 12 * scale, cy - 43 * scale, cx + 13 * scale, cy - 19 * scale), fill=accent)
1431
+ draw.ellipse((cx + 4 * scale, cy - 35 * scale, cx + 9 * scale, cy - 30 * scale), fill=(255, 255, 255, 245))
1432
+ draw.line((cx + 11 * scale, cy - 4 * scale, cx + 39 * scale, cy + 9 * scale), fill=trim, width=max(4, int(8 * scale)))
1433
+ draw.line((cx - 3 * scale, cy + 28 * scale, cx - 22 * scale, cy + 50 * scale), fill=outline, width=max(5, int(10 * scale)))
1434
+ draw.line((cx + 12 * scale, cy + 24 * scale, cx + 31 * scale, cy + 43 * scale), fill=outline, width=max(5, int(10 * scale)))
1435
+ out = io.BytesIO()
1436
+ image.save(out, format="PNG")
1437
+ return out.getvalue()
1438
+
1439
  shadow = (int(cx - 36 * scale), int(cy + 34 * scale), int(cx + 36 * scale), int(cy + 48 * scale))
1440
  draw.ellipse(shadow, fill=(0, 0, 0, 65))
1441
 
 
1541
  "particle effect, particles, confetti, sparkles, floating dots, glowing orbs, embers, lens flare"
1542
  + camera_negative
1543
  )
1544
+ return (
1545
+ common
1546
+ + ", landscape, environment scene, room, horizon, opaque background, drop shadow, "
1547
+ "trees, foliage, ground, floor, scenery, background objects, frame, border"
1548
+ )
1549
 
1550
 
1551
  def build_camera_control_image(spec: AssetSpec, width: int = 1024, height: int = 576) -> Image.Image:
 
1650
  )
1651
  return (
1652
  f"{spec.prompt} One complete isolated game sprite only, centered, fully visible, readable silhouette, "
1653
+ "camera angle and pose exactly matching the supplied guide, on a plain uniform white studio background. "
1654
+ "No scenery, floor, trees, foliage, frame, or environmental props."
1655
  )
1656
 
1657
 
1658
+ def primary_image_guide(spec: AssetSpec, index: int, run_id: int) -> Image.Image:
1659
+ """Build a clean camera-and-palette guide for the neural image-to-image pass."""
1660
+ guide_png = local_asset_png(spec, index, run_id)
1661
+ guide = Image.open(io.BytesIO(guide_png)).convert("RGBA")
1662
+ if is_background_spec(spec):
1663
+ return guide.convert("RGB").resize((1344, 768), Image.LANCZOS)
1664
+
1665
+ guide = guide.resize((1024, 1024), Image.LANCZOS)
1666
+ white = Image.new("RGB", guide.size, (255, 255, 255))
1667
+ white.paste(guide.convert("RGB"), mask=guide.getchannel("A"))
1668
+ return white
1669
+
1670
+
1671
  def primary_diffusion_png(spec: AssetSpec, index: int, run_id: int) -> tuple[bytes | None, str | None]:
1672
  """Generate every production asset with SSD-1B; backgrounds use camera-guided img2img."""
1673
  if PRIMARY_MODEL_ERROR:
 
1689
  "guidance_scale": PRIMARY_GUIDANCE_SCALE,
1690
  "generator": generator,
1691
  }
1692
+ if PRIMARY_IMAGE_TO_IMAGE_PIPE is None:
1693
+ return None, "primary camera-guided image-to-image pipeline is unavailable"
1694
+ guide = primary_image_guide(spec, index, run_id)
1695
+ strength = PRIMARY_BACKGROUND_STRENGTH if is_background_spec(spec) else PRIMARY_SPRITE_STRENGTH
1696
+ image = PRIMARY_IMAGE_TO_IMAGE_PIPE(
1697
+ image=guide,
1698
+ strength=strength,
1699
+ **common,
1700
+ ).images[0]
 
 
 
1701
  return polish_diffusion_asset(image, spec), None
1702
  except Exception as exc:
1703
  return None, short_error(exc)
 
2113
  if "via fal-ai" in lowered or "via replicate" in lowered:
2114
  return "routed image-to-image refinement鈥攑reserves a layout guide but consumes account inference credit"
2115
  if "zerogpu" in lowered:
2116
+ return "locally hosted neural diffusion鈥攏o per-image API credit; assets are camera-guided and still require visual review"
2117
  return "diffusion output鈥攈igher visual variety; verify subject, perspective, alpha edges, and prompt compliance"
2118
 
2119
 
 
2122
  sprite_chain = []
2123
  primary_source = f"{PRIMARY_IMAGE_MODEL} + {PRIMARY_LORA_MODEL} on ZeroGPU"
2124
  if USE_PRIMARY_IMAGE_MODEL:
2125
+ sprite_chain.append(f"camera-guided {primary_source}")
2126
  if USE_DIFFUSION_FOR_SPRITES:
2127
  sprite_chain.append(FREE_IMAGE_MODEL)
2128
  if USE_HF_IMAGE_PROVIDER:
 
2169
  f"prompts: `{prompt_source}` 路 sprites: `{sprite_source}` 路 backgrounds: `{background_source}` 路 "
2170
  f"remote image fallback: `{remote_fallback}` 路 neural image models: `{neural_status}` 路 "
2171
  f"neural enforcement: `{enforcement}` 路 primary readiness: `{readiness}`. "
2172
+ "Every production image is generated by the primary neural model. Sprites and backgrounds start from deterministic "
2173
+ "camera-and-palette guides and are refined with image-to-image diffusion; the procedural renderer remains an emergency "
2174
  "development fallback and is blocked in the deployed Space when the primary model fails."
2175
  )
2176