Commit History

Fix gallery image outputs
30dc2ff
Running
verified

LeafCat79 commited on

Remove accidental pasted-code folder
bc1d515
verified

LeafCat79 commited on

Update README.md
0c8ccf5
verified

LeafCat79 commited on

Update requirements.txt
b61de4a
verified

LeafCat79 commited on

Update README.md
3ffc37d
verified

LeafCat79 commited on

Create README.md
d7e3123
verified

LeafCat79 commited on

Create requirements.txt
0c7c679
verified

LeafCat79 commited on

Create app.py
4d60f00
verified

LeafCat79 commited on

Create import base64import ioimport reimport timefrom dataclasses import dataclassfrom html import escapefrom urllib.parse import quoteimport gradio as grimport requestsfrom PIL import ImagePOLLINATIONS_URL = ( "https://image.pollinations.ai/prompt/{prompt}" "?width={width}&height={height}&model=flux&nologo=true&seed={seed}")STARTER_HTML = """<!DOCTYPE html><html><head> <meta charset="UTF-8" /> <title>Mini Forest Game</title> <style> body { margin: 0; background: #111; display: grid; place-items: center; min-height: 100vh; } canvas { border: 2px solid #222; background: #222; image-rendering: pixelated; } </style></head><body> <canvas id="gameCanvas" width="800" height="450"></canvas> <script> const canvas = document.getElementById("gameCanvas"); const ctx = canvas.getContext("2d"); const background = new Image(); background.src = "sprite_background.png"; const playerImg = new Image(); playerImg.src = "sprite_player.png"; const keys = new Set(); const player = { x: 380, y: 205, w: 48, h: 48, speed: 4 }; window.addEventListener("keydown", e => keys.add(e.key)); window.addEventListener("keyup", e => keys.delete(e.key)); function update() { if (keys.has("a") || keys.has("ArrowLeft")) player.x -= player.speed; if (keys.has("d") || keys.has("ArrowRight")) player.x += player.speed; if (keys.has("w") || keys.has("ArrowUp")) player.y -= player.speed; if (keys.has("s") || keys.has("ArrowDown")) player.y += player.speed; player.x = Math.max(0, Math.min(canvas.width - player.w, player.x)); player.y = Math.max(0, Math.min(canvas.height - player.h, player.y)); } function draw() { ctx.drawImage(background, 0, 0, canvas.width, canvas.height); ctx.drawImage(playerImg, player.x, player.y, player.w, player.h); ctx.fillStyle = "white"; ctx.font = "18px sans-serif"; ctx.fillText("Use WASD or arrow keys", 18, 28); } function loop() { update(); draw(); requestAnimationFrame(loop); } loop(); </script></body></html>"""DEFAULT_ROLES = """player: top-down pixel-art adventurer hero, transparent background, bright readable silhouettebackground: enchanted forest clearing game background, top-down view, soft moonlight, detailed but not too busy"""@dataclassclass AssetSpec: role: str prompt: str filename: str width: int height: intdef slugify(value: str) -> str: value = re.sub(r"[^a-zA-Z0-9]+", "_", value.strip().lower()).strip("_") return value or "asset"def parse_assets(raw_roles: str, style_hint: str) -> list[AssetSpec]: specs: list[AssetSpec] = [] for line in raw_roles.splitlines(): line = line.strip() if not line or line.startswith("#"): continue if ":" in line: role, prompt = line.split(":", 1) elif "=" in line: role, prompt = line.split("=", 1) else: role, prompt = line, line role = role.strip() prompt = prompt.strip() or role slug = slugify(role) is_background = any(word in slug for word in ("background", "backdrop", "scene", "map", "level")) width, height = (800, 450) if is_background else (128, 128) filename = f"sprite_{slug}.png" full_prompt = ( f"{prompt}, {style_hint}, game asset, clean readable shape, " "no text, no watermark" ).strip(", ") specs.append(AssetSpec(role=role, prompt=full_prompt, filename=filename, width=width, height=height)) return specsdef image_to_data_uri(content: bytes, width: int, height: int) -> str: image = Image.open(io.BytesIO(content)).convert("RGBA") image = image.resize((width, height), Image.LANCZOS) out = io.BytesIO() image.save(out, format="PNG") return "data:image/png;base64," + base64.b64encode(out.getvalue()).decode("ascii")def placeholder_data_uri(role: str, width: int, height: int) -> str: label = escape(role[:18]) svg = f"""<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}"><rect width="100%" height="100%" fill="#222"/><rect x="4" y="4" width="{width - 8}" height="{height - 8}" rx="12" fill="#3b82f6"/><text x="50%" y="50%" text-anchor="middle" dominant-baseline="middle" fill="white" font-family="Arial" font-size="18">{label}</text></svg>""" return "data:image/svg+xml;base64," + base64.b64encode(svg.encode("utf-8")).decode("ascii")def generate_asset(spec: AssetSpec, index: int) -> tuple[str, str | None]: seed = abs(hash((spec.role, spec.prompt, index))) % 999999 url = POLLINATIONS_URL.format( prompt=quote(spec.prompt), width=spec.width, height=spec.height, seed=seed, ) for attempt in range(3): try: response = requests.get(url, timeout=120) response.raise_for_status() return image_to_data_uri(response.content, spec.width, spec.height), None except Exception as exc: if attempt < 2: time.sleep(8) else: return placeholder_data_uri(spec.role, spec.width, spec.height), str(exc) return placeholder_data_uri(spec.role, spec.width, spec.height), "Unknown generation error"def replacement_names(spec: AssetSpec) -> set[str]: slug = slugify(spec.role) names = { spec.filename, f"{slug}.png", f"{slug}.jpg", f"{slug}.jpeg", f"{slug}.webp", f"asset_{slug}.png", f"{spec.role.strip()}.png", f"{{{{{slug}}}}}", f"{{{slug}}}", } if slug == "background": names.update({"background.png", "sprite_background.jpg", "background.jpg"}) if slug == "player": names.update({"player.png", "sprite_player.jpg", "hero.png"}) if slug == "enemy": names.update({"enemy.png", "monster.png", "sprite_enemy.jpg"}) return namesdef embed_assets(html_code: str, assets: dict[str, str], specs: list[AssetSpec]) -> str: output = html_code manifest_lines = ["<!-- Embedded game assets generated by Image Generator for HTML Games"] for spec in specs: data_uri = assets[spec.role] manifest_lines.append(f"{spec.role}: {spec.filename}") for name in replacement_names(spec): output = output.replace(f'"{name}"', f'"{data_uri}"') output = output.replace(f"'{name}'", f"'{data_uri}'") output = output.replace(name, data_uri) manifest_lines.append("-->") manifest = "\n".join(manifest_lines) + "\n" asset_map = ",\n".join( f" {slugify(spec.role)!r}: {assets[spec.role]!r}" for spec in specs ) helper_script = f"""<script> window.GENERATED_GAME_ASSETS = {{{asset_map} }};</script>""" if "</head>" in output: output = output.replace("</head>", helper_script + "\n</head>", 1) elif "<body" in output: output = output.replace("<body", helper_script + "\n<body", 1) else: output = helper_script + "\n" + output return manifest + outputdef build_preview(html_code: str) -> str: encoded = base64.b64encode(html_code.encode("utf-8")).decode("ascii") return ( f'<iframe src="data:text/html;base64,{encoded}" ' 'style="width:100%;height:560px;border:1px solid #333;border-radius:8px;background:#000;" ' 'sandbox="allow-scripts" title="Game preview"></iframe>' )def generate_images_and_game(html_code: str, roles: str, style_hint: str): if not html_code.strip(): return "", "Paste HTML game code first.", [], "" specs = parse_assets(roles, style_hint or "pixel art style") if not specs: return html_code, "Add at least one asset role, like `player: brave knight`.", [], build_preview(html_code) assets: dict[str, str] = {} gallery = [] errors = [] for index, spec in enumerate(specs): data_uri, error = generate_asset(spec, index) assets[spec.role] = data_uri gallery.append((data_uri, f"{spec.role} -> {spec.filename}")) if error: errors.append(f"{spec.role}: fallback used ({error})") rewritten = embed_assets(html_code, assets, specs) status = f"Generated and embedded {len(specs)} asset(s)." if errors: status += "\n\n" + "\n".join(errors) return rewritten, status, gallery, build_preview(rewritten)with gr.Blocks(title="Image Generator for HTML Games") as demo: gr.Markdown( "# Image Generator for HTML Games\n" "Paste an HTML canvas game, list the image roles you want, and generate a rewritten version " "with the images embedded directly into the code." ) with gr.Row(): with gr.Column(scale=1): roles = gr.Textbox( label="Image roles to generate", value=DEFAULT_ROLES, lines=8, info="One per line: role: image description. Example: player: blue robot hero", ) style = gr.Textbox( label="Shared visual style", value="cohesive 2D pixel-art game style, transparent subject sprites where possible", lines=2, ) generate_btn = gr.Button("Generate Images + Embed Game", variant="primary") status = gr.Markdown("Ready.") gallery = gr.Gallery(label="Generated assets", columns=2, height=300) with gr.Column(scale=2): html_input = gr.Code( label="Original HTML game code", value=STARTER_HTML, language="html", lines=18, ) output_code = gr.Code( label="Rewritten HTML with embedded images", language="html", lines=18, ) gr.Markdown("## Game preview") preview = gr.HTML(build_preview(STARTER_HTML)) generate_btn.click( fn=generate_images_and_game, inputs=[html_input, roles, style], outputs=[output_code, status, gallery, preview], )if __name__ == "__main__": demo.launch()
1a0aaee
verified

LeafCat79 commited on

initial commit
39ce61a
verified

LeafCat79 commited on