Generate assets directly from text prompts
Browse filesRemove procedural image guides from the production diffusion path. Generate sprites and backgrounds directly from written prompts, keep fail-closed neural enforcement, and make realistic style parsing negation-aware.
app.py
CHANGED
|
@@ -126,8 +126,6 @@ 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_SPRITE_STEPS = int(os.environ.get("PRIMARY_SPRITE_STEPS", "6"))
|
| 128 |
PRIMARY_GUIDANCE_SCALE = float(os.environ.get("PRIMARY_GUIDANCE_SCALE", "1.5"))
|
| 129 |
-
PRIMARY_BACKGROUND_STRENGTH = float(os.environ.get("PRIMARY_BACKGROUND_STRENGTH", "0.72"))
|
| 130 |
-
PRIMARY_SPRITE_STRENGTH = float(os.environ.get("PRIMARY_SPRITE_STRENGTH", "0.82"))
|
| 131 |
USE_PRIMARY_IMAGE_MODEL = os.environ.get("USE_PRIMARY_IMAGE_MODEL", "1") == "1"
|
| 132 |
REQUIRE_PRIMARY_IMAGE_MODEL = os.environ.get(
|
| 133 |
"REQUIRE_PRIMARY_IMAGE_MODEL",
|
|
@@ -154,7 +152,6 @@ FREE_DIFFUSION_ERROR = None
|
|
| 154 |
CONTROLNET_PIPE = None
|
| 155 |
CONTROLNET_ERROR = None
|
| 156 |
PRIMARY_TEXT_PIPE = None
|
| 157 |
-
PRIMARY_IMAGE_TO_IMAGE_PIPE = None
|
| 158 |
PRIMARY_MODEL_ERROR = None
|
| 159 |
|
| 160 |
|
|
@@ -225,7 +222,18 @@ def interpret_style_hint(style_hint: str) -> StylePlan:
|
|
| 225 |
tags: list[str] = []
|
| 226 |
|
| 227 |
def has(*words: str) -> bool:
|
| 228 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
|
| 230 |
if has("watercolor", "ink wash", "gouache", "paper"):
|
| 231 |
tags.append("watercolor")
|
|
@@ -255,6 +263,13 @@ def interpret_style_hint(style_hint: str) -> StylePlan:
|
|
| 255 |
texture = "visible bristle strokes and canvas-like texture"
|
| 256 |
lighting = "dramatic directional light"
|
| 257 |
linework = "painted edges instead of hard outlines"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 258 |
elif has("pixel", "8-bit", "16-bit", "retro"):
|
| 259 |
tags.append("pixel")
|
| 260 |
medium = "retro pixel-art game asset"
|
|
@@ -1374,9 +1389,7 @@ def draw_sprite(spec: AssetSpec, rng: random.Random) -> bytes:
|
|
| 1374 |
return out.getvalue()
|
| 1375 |
|
| 1376 |
if spec.camera == "side_view":
|
| 1377 |
-
# Profile silhouette
|
| 1378 |
-
# neural img2img camera guide. A single visible eye and forward nose
|
| 1379 |
-
# prevent the model from reverting to its common front-facing pose.
|
| 1380 |
draw.ellipse((cx - 28 * scale, cy + 31 * scale, cx + 31 * scale, cy + 46 * scale), fill=(0, 0, 0, 55))
|
| 1381 |
draw.rounded_rectangle(
|
| 1382 |
(cx - 15 * scale, cy - 16 * scale, cx + 17 * scale, cy + 32 * scale),
|
|
@@ -1544,7 +1557,7 @@ def diffusion_negative_prompt(spec: AssetSpec) -> str:
|
|
| 1544 |
)
|
| 1545 |
return (
|
| 1546 |
common
|
| 1547 |
-
+ ", landscape, environment scene, room, horizon,
|
| 1548 |
"trees, foliage, ground, floor, scenery, background objects, frame, border"
|
| 1549 |
)
|
| 1550 |
|
|
@@ -1602,8 +1615,8 @@ def build_camera_control_image(spec: AssetSpec, width: int = 1024, height: int =
|
|
| 1602 |
|
| 1603 |
|
| 1604 |
def initialize_primary_image_model() -> None:
|
| 1605 |
-
"""Load the production
|
| 1606 |
-
global PRIMARY_TEXT_PIPE,
|
| 1607 |
if not USE_PRIMARY_IMAGE_MODEL or PRIMARY_TEXT_PIPE is not None or PRIMARY_MODEL_ERROR:
|
| 1608 |
return
|
| 1609 |
if not os.environ.get("SPACE_ID"):
|
|
@@ -1611,7 +1624,7 @@ def initialize_primary_image_model() -> None:
|
|
| 1611 |
|
| 1612 |
try:
|
| 1613 |
import torch
|
| 1614 |
-
from diffusers import
|
| 1615 |
|
| 1616 |
model_kwargs = {
|
| 1617 |
"torch_dtype": torch.float16,
|
|
@@ -1631,13 +1644,7 @@ def initialize_primary_image_model() -> None:
|
|
| 1631 |
if hasattr(text_pipe, "enable_vae_slicing"):
|
| 1632 |
text_pipe.enable_vae_slicing()
|
| 1633 |
|
| 1634 |
-
image_pipe = AutoPipelineForImage2Image.from_pipe(text_pipe)
|
| 1635 |
-
image_pipe.scheduler = LCMScheduler.from_config(image_pipe.scheduler.config)
|
| 1636 |
-
if hasattr(image_pipe, "set_progress_bar_config"):
|
| 1637 |
-
image_pipe.set_progress_bar_config(disable=True)
|
| 1638 |
-
|
| 1639 |
PRIMARY_TEXT_PIPE = text_pipe
|
| 1640 |
-
PRIMARY_IMAGE_TO_IMAGE_PIPE = image_pipe
|
| 1641 |
except Exception as exc:
|
| 1642 |
PRIMARY_MODEL_ERROR = short_error(exc)
|
| 1643 |
|
|
@@ -1645,33 +1652,21 @@ def initialize_primary_image_model() -> None:
|
|
| 1645 |
def primary_diffusion_prompt(spec: AssetSpec) -> str:
|
| 1646 |
if is_background_spec(spec):
|
| 1647 |
return (
|
| 1648 |
-
f"{spec.prompt}
|
| 1649 |
-
"
|
| 1650 |
-
"No characters and no decorative overlay."
|
| 1651 |
)
|
| 1652 |
return (
|
| 1653 |
f"{spec.prompt} One complete isolated game sprite only, centered, fully visible, readable silhouette, "
|
| 1654 |
-
"
|
|
|
|
| 1655 |
"Add recognizable costume, equipment, materials, and character details. "
|
| 1656 |
"No scenery, floor, trees, foliage, frame, or environmental props."
|
| 1657 |
)
|
| 1658 |
|
| 1659 |
|
| 1660 |
-
def primary_image_guide(spec: AssetSpec, index: int, run_id: int) -> Image.Image:
|
| 1661 |
-
"""Build a clean camera-and-palette guide for the neural image-to-image pass."""
|
| 1662 |
-
guide_png = local_asset_png(spec, index, run_id)
|
| 1663 |
-
guide = Image.open(io.BytesIO(guide_png)).convert("RGBA")
|
| 1664 |
-
if is_background_spec(spec):
|
| 1665 |
-
return guide.convert("RGB").resize((1344, 768), Image.LANCZOS)
|
| 1666 |
-
|
| 1667 |
-
guide = guide.resize((1024, 1024), Image.LANCZOS)
|
| 1668 |
-
white = Image.new("RGB", guide.size, (255, 255, 255))
|
| 1669 |
-
white.paste(guide.convert("RGB"), mask=guide.getchannel("A"))
|
| 1670 |
-
return white
|
| 1671 |
-
|
| 1672 |
-
|
| 1673 |
def primary_diffusion_png(spec: AssetSpec, index: int, run_id: int) -> tuple[bytes | None, str | None]:
|
| 1674 |
-
"""Generate every production asset
|
| 1675 |
if PRIMARY_MODEL_ERROR:
|
| 1676 |
return None, PRIMARY_MODEL_ERROR
|
| 1677 |
if PRIMARY_TEXT_PIPE is None:
|
|
@@ -1691,13 +1686,10 @@ def primary_diffusion_png(spec: AssetSpec, index: int, run_id: int) -> tuple[byt
|
|
| 1691 |
"guidance_scale": PRIMARY_GUIDANCE_SCALE,
|
| 1692 |
"generator": generator,
|
| 1693 |
}
|
| 1694 |
-
if
|
| 1695 |
-
|
| 1696 |
-
|
| 1697 |
-
|
| 1698 |
-
image = PRIMARY_IMAGE_TO_IMAGE_PIPE(
|
| 1699 |
-
image=guide,
|
| 1700 |
-
strength=strength,
|
| 1701 |
**common,
|
| 1702 |
).images[0]
|
| 1703 |
return polish_diffusion_asset(image, spec), None
|
|
@@ -2115,7 +2107,7 @@ def model_source_note(image_model: str) -> str:
|
|
| 2115 |
if "via fal-ai" in lowered or "via replicate" in lowered:
|
| 2116 |
return "routed image-to-image refinement—preserves a layout guide but consumes account inference credit"
|
| 2117 |
if "zerogpu" in lowered:
|
| 2118 |
-
return "locally hosted
|
| 2119 |
return "diffusion output—higher visual variety; verify subject, perspective, alpha edges, and prompt compliance"
|
| 2120 |
|
| 2121 |
|
|
@@ -2124,16 +2116,17 @@ def model_configuration_summary() -> str:
|
|
| 2124 |
sprite_chain = []
|
| 2125 |
primary_source = f"{PRIMARY_IMAGE_MODEL} + {PRIMARY_LORA_MODEL} on ZeroGPU"
|
| 2126 |
if USE_PRIMARY_IMAGE_MODEL:
|
| 2127 |
-
sprite_chain.append(f"
|
| 2128 |
if USE_DIFFUSION_FOR_SPRITES:
|
| 2129 |
sprite_chain.append(FREE_IMAGE_MODEL)
|
| 2130 |
if USE_HF_IMAGE_PROVIDER:
|
| 2131 |
sprite_chain.append(HF_IMAGE_MODEL)
|
| 2132 |
-
|
|
|
|
| 2133 |
sprite_source = " → ".join(sprite_chain)
|
| 2134 |
background_chain = []
|
| 2135 |
if USE_PRIMARY_IMAGE_MODEL:
|
| 2136 |
-
background_chain.append(f"
|
| 2137 |
if USE_CONTROLNET_FOR_BACKGROUNDS:
|
| 2138 |
background_chain.append(f"{CONTROLNET_BASE_MODEL} + {CONTROLNET_MODEL}")
|
| 2139 |
if USE_HF_STRUCTURE_PROVIDER:
|
|
@@ -2142,7 +2135,8 @@ def model_configuration_summary() -> str:
|
|
| 2142 |
background_chain.append(FREE_IMAGE_MODEL)
|
| 2143 |
if USE_HF_IMAGE_PROVIDER:
|
| 2144 |
background_chain.append(HF_IMAGE_MODEL)
|
| 2145 |
-
|
|
|
|
| 2146 |
background_source = " → ".join(background_chain)
|
| 2147 |
remote_sources = []
|
| 2148 |
if USE_HF_STRUCTURE_PROVIDER:
|
|
@@ -2171,9 +2165,9 @@ def model_configuration_summary() -> str:
|
|
| 2171 |
f"prompts: `{prompt_source}` · sprites: `{sprite_source}` · backgrounds: `{background_source}` · "
|
| 2172 |
f"remote image fallback: `{remote_fallback}` · neural image models: `{neural_status}` · "
|
| 2173 |
f"neural enforcement: `{enforcement}` · primary readiness: `{readiness}`. "
|
| 2174 |
-
"Every production image is generated by the primary
|
| 2175 |
-
"
|
| 2176 |
-
"
|
| 2177 |
)
|
| 2178 |
|
| 2179 |
|
|
|
|
| 126 |
PRIMARY_IMAGE_STEPS = int(os.environ.get("PRIMARY_IMAGE_STEPS", "4"))
|
| 127 |
PRIMARY_SPRITE_STEPS = int(os.environ.get("PRIMARY_SPRITE_STEPS", "6"))
|
| 128 |
PRIMARY_GUIDANCE_SCALE = float(os.environ.get("PRIMARY_GUIDANCE_SCALE", "1.5"))
|
|
|
|
|
|
|
| 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",
|
|
|
|
| 152 |
CONTROLNET_PIPE = None
|
| 153 |
CONTROLNET_ERROR = None
|
| 154 |
PRIMARY_TEXT_PIPE = None
|
|
|
|
| 155 |
PRIMARY_MODEL_ERROR = None
|
| 156 |
|
| 157 |
|
|
|
|
| 222 |
tags: list[str] = []
|
| 223 |
|
| 224 |
def has(*words: str) -> bool:
|
| 225 |
+
negation = re.compile(
|
| 226 |
+
r"(?:\bno\b|\bnot\b|\bwithout\b|\bavoid(?:ing)?\b|"
|
| 227 |
+
r"\bexclude(?:d|ing)?\b|\brather\s+than\b|\bnon[- ]?)\s+"
|
| 228 |
+
r"(?:[\w-]+\s+){0,3}$"
|
| 229 |
+
)
|
| 230 |
+
for word in words:
|
| 231 |
+
pattern = re.compile(rf"(?<![\w-]){re.escape(word)}(?![\w-])")
|
| 232 |
+
for match in pattern.finditer(text):
|
| 233 |
+
prefix = text[max(0, match.start() - 48) : match.start()]
|
| 234 |
+
if not negation.search(prefix):
|
| 235 |
+
return True
|
| 236 |
+
return False
|
| 237 |
|
| 238 |
if has("watercolor", "ink wash", "gouache", "paper"):
|
| 239 |
tags.append("watercolor")
|
|
|
|
| 263 |
texture = "visible bristle strokes and canvas-like texture"
|
| 264 |
lighting = "dramatic directional light"
|
| 265 |
linework = "painted edges instead of hard outlines"
|
| 266 |
+
elif has("photorealistic", "photo-realistic", "realistic", "cinematic realism", "lifelike"):
|
| 267 |
+
tags.append("realistic")
|
| 268 |
+
medium = "realistic game concept art"
|
| 269 |
+
palette = "natural material colors with controlled cinematic contrast"
|
| 270 |
+
texture = "physically plausible fabric, skin, metal, and surface detail"
|
| 271 |
+
lighting = "cinematic directional light with realistic shadows"
|
| 272 |
+
linework = "natural painted edges without graphic outlines"
|
| 273 |
elif has("pixel", "8-bit", "16-bit", "retro"):
|
| 274 |
tags.append("pixel")
|
| 275 |
medium = "retro pixel-art game asset"
|
|
|
|
| 1389 |
return out.getvalue()
|
| 1390 |
|
| 1391 |
if spec.camera == "side_view":
|
| 1392 |
+
# Profile silhouette for the optional local procedural fallback.
|
|
|
|
|
|
|
| 1393 |
draw.ellipse((cx - 28 * scale, cy + 31 * scale, cx + 31 * scale, cy + 46 * scale), fill=(0, 0, 0, 55))
|
| 1394 |
draw.rounded_rectangle(
|
| 1395 |
(cx - 15 * scale, cy - 16 * scale, cx + 17 * scale, cy + 32 * scale),
|
|
|
|
| 1557 |
)
|
| 1558 |
return (
|
| 1559 |
common
|
| 1560 |
+
+ ", landscape, environment scene, room, horizon, complex background, drop shadow, "
|
| 1561 |
"trees, foliage, ground, floor, scenery, background objects, frame, border"
|
| 1562 |
)
|
| 1563 |
|
|
|
|
| 1615 |
|
| 1616 |
|
| 1617 |
def initialize_primary_image_model() -> None:
|
| 1618 |
+
"""Load the production text-to-image pipeline once under ZeroGPU CUDA emulation."""
|
| 1619 |
+
global PRIMARY_TEXT_PIPE, PRIMARY_MODEL_ERROR
|
| 1620 |
if not USE_PRIMARY_IMAGE_MODEL or PRIMARY_TEXT_PIPE is not None or PRIMARY_MODEL_ERROR:
|
| 1621 |
return
|
| 1622 |
if not os.environ.get("SPACE_ID"):
|
|
|
|
| 1624 |
|
| 1625 |
try:
|
| 1626 |
import torch
|
| 1627 |
+
from diffusers import AutoPipelineForText2Image, LCMScheduler
|
| 1628 |
|
| 1629 |
model_kwargs = {
|
| 1630 |
"torch_dtype": torch.float16,
|
|
|
|
| 1644 |
if hasattr(text_pipe, "enable_vae_slicing"):
|
| 1645 |
text_pipe.enable_vae_slicing()
|
| 1646 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1647 |
PRIMARY_TEXT_PIPE = text_pipe
|
|
|
|
| 1648 |
except Exception as exc:
|
| 1649 |
PRIMARY_MODEL_ERROR = short_error(exc)
|
| 1650 |
|
|
|
|
| 1652 |
def primary_diffusion_prompt(spec: AssetSpec) -> str:
|
| 1653 |
if is_background_spec(spec):
|
| 1654 |
return (
|
| 1655 |
+
f"{spec.prompt} Generate this scene directly from the written description as a production-quality "
|
| 1656 |
+
"2D game background. Follow the stated camera projection exactly and keep gameplay space readable. "
|
| 1657 |
+
"Use connected environmental forms and clean terrain structure. No characters and no decorative overlay."
|
| 1658 |
)
|
| 1659 |
return (
|
| 1660 |
f"{spec.prompt} One complete isolated game sprite only, centered, fully visible, readable silhouette, "
|
| 1661 |
+
"with its anatomy, body shape, camera angle, and pose derived directly from the written description. "
|
| 1662 |
+
"Use a plain uniform white studio background for clean extraction. "
|
| 1663 |
"Add recognizable costume, equipment, materials, and character details. "
|
| 1664 |
"No scenery, floor, trees, foliage, frame, or environmental props."
|
| 1665 |
)
|
| 1666 |
|
| 1667 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1668 |
def primary_diffusion_png(spec: AssetSpec, index: int, run_id: int) -> tuple[bytes | None, str | None]:
|
| 1669 |
+
"""Generate every production asset directly from text with the primary model."""
|
| 1670 |
if PRIMARY_MODEL_ERROR:
|
| 1671 |
return None, PRIMARY_MODEL_ERROR
|
| 1672 |
if PRIMARY_TEXT_PIPE is None:
|
|
|
|
| 1686 |
"guidance_scale": PRIMARY_GUIDANCE_SCALE,
|
| 1687 |
"generator": generator,
|
| 1688 |
}
|
| 1689 |
+
width, height = (1344, 768) if is_background_spec(spec) else (1024, 1024)
|
| 1690 |
+
image = PRIMARY_TEXT_PIPE(
|
| 1691 |
+
width=width,
|
| 1692 |
+
height=height,
|
|
|
|
|
|
|
|
|
|
| 1693 |
**common,
|
| 1694 |
).images[0]
|
| 1695 |
return polish_diffusion_asset(image, spec), None
|
|
|
|
| 2107 |
if "via fal-ai" in lowered or "via replicate" in lowered:
|
| 2108 |
return "routed image-to-image refinement—preserves a layout guide but consumes account inference credit"
|
| 2109 |
if "zerogpu" in lowered:
|
| 2110 |
+
return "direct locally hosted text-to-image diffusion—no procedural guide or per-image API credit; visual review required"
|
| 2111 |
return "diffusion output—higher visual variety; verify subject, perspective, alpha edges, and prompt compliance"
|
| 2112 |
|
| 2113 |
|
|
|
|
| 2116 |
sprite_chain = []
|
| 2117 |
primary_source = f"{PRIMARY_IMAGE_MODEL} + {PRIMARY_LORA_MODEL} on ZeroGPU"
|
| 2118 |
if USE_PRIMARY_IMAGE_MODEL:
|
| 2119 |
+
sprite_chain.append(f"direct text-to-image {primary_source}")
|
| 2120 |
if USE_DIFFUSION_FOR_SPRITES:
|
| 2121 |
sprite_chain.append(FREE_IMAGE_MODEL)
|
| 2122 |
if USE_HF_IMAGE_PROVIDER:
|
| 2123 |
sprite_chain.append(HF_IMAGE_MODEL)
|
| 2124 |
+
if not (USE_PRIMARY_IMAGE_MODEL and REQUIRE_PRIMARY_IMAGE_MODEL):
|
| 2125 |
+
sprite_chain.append("shape-aware procedural renderer (development fallback only)")
|
| 2126 |
sprite_source = " → ".join(sprite_chain)
|
| 2127 |
background_chain = []
|
| 2128 |
if USE_PRIMARY_IMAGE_MODEL:
|
| 2129 |
+
background_chain.append(f"direct text-to-image {primary_source}")
|
| 2130 |
if USE_CONTROLNET_FOR_BACKGROUNDS:
|
| 2131 |
background_chain.append(f"{CONTROLNET_BASE_MODEL} + {CONTROLNET_MODEL}")
|
| 2132 |
if USE_HF_STRUCTURE_PROVIDER:
|
|
|
|
| 2135 |
background_chain.append(FREE_IMAGE_MODEL)
|
| 2136 |
if USE_HF_IMAGE_PROVIDER:
|
| 2137 |
background_chain.append(HF_IMAGE_MODEL)
|
| 2138 |
+
if not (USE_PRIMARY_IMAGE_MODEL and REQUIRE_PRIMARY_IMAGE_MODEL):
|
| 2139 |
+
background_chain.append("camera-safe procedural renderer (development fallback only)")
|
| 2140 |
background_source = " → ".join(background_chain)
|
| 2141 |
remote_sources = []
|
| 2142 |
if USE_HF_STRUCTURE_PROVIDER:
|
|
|
|
| 2165 |
f"prompts: `{prompt_source}` · sprites: `{sprite_source}` · backgrounds: `{background_source}` · "
|
| 2166 |
f"remote image fallback: `{remote_fallback}` · neural image models: `{neural_status}` · "
|
| 2167 |
f"neural enforcement: `{enforcement}` · primary readiness: `{readiness}`. "
|
| 2168 |
+
"Every production image is generated directly from its written prompt by the primary text-to-image model. "
|
| 2169 |
+
"No procedural guide is supplied to the model. The procedural renderer is development-only and is blocked in "
|
| 2170 |
+
"the deployed Space when the primary model fails."
|
| 2171 |
)
|
| 2172 |
|
| 2173 |
|