"""Greenscreen chroma key for #00FF00 bait/fish sprites.""" from __future__ import annotations from io import BytesIO from typing import Any import numpy as np from PIL import Image def chroma_key_green( image: Image.Image, *, key: tuple[int, int, int] = (0, 255, 0), tolerance: int = 90, feather: int = 40, spill_suppression: float = 0.5, ) -> Image.Image: """Replace green-screen pixels with transparency (vectorized).""" img = image.convert("RGBA") arr = np.asarray(img).astype(np.float32) r, g, b = arr[..., 0], arr[..., 1], arr[..., 2] kr, kg, kb = key dist = np.sqrt((r - kr) ** 2 + (g - kg) ** 2 + (b - kb) ** 2) greenness = g - np.maximum(r, b) green_dominant = (greenness > 60) & (g > 120) alpha = np.clip((dist - tolerance) / max(feather, 1), 0.0, 1.0) * 255.0 alpha = np.where(green_dominant & (dist < tolerance + feather * 2), 0.0, alpha) arr[..., 3] = np.minimum(arr[..., 3], alpha) if spill_suppression > 0: spill = np.clip(greenness, 0, None) * spill_suppression arr[..., 1] = np.clip(g - spill, 0, 255) return Image.fromarray(arr.astype(np.uint8), "RGBA") def trim_transparent(image: Image.Image, padding: int = 4) -> Image.Image: """Crop away fully transparent borders so sprites sit tight in the UI.""" img = image.convert("RGBA") alpha = np.asarray(img)[..., 3] rows = np.any(alpha > 8, axis=1) cols = np.any(alpha > 8, axis=0) if not rows.any() or not cols.any(): return img top, bottom = np.argmax(rows), len(rows) - np.argmax(rows[::-1]) left, right = np.argmax(cols), len(cols) - np.argmax(cols[::-1]) top = max(0, top - padding) left = max(0, left - padding) bottom = min(img.height, bottom + padding) right = min(img.width, right + padding) return img.crop((left, top, right, bottom)) def image_to_png_bytes(image: Image.Image) -> bytes: buf = BytesIO() image.save(buf, format="PNG") return buf.getvalue() def bait_prompt(bait: str) -> str: return ( f'A "{bait}" cartoon game item sprite, Stardew Valley chibi pixel art style, ' "single object centered, bold flat colours, solid bright green #00FF00 " "greenscreen background, flat uniform background colour, no shadows on background" ) def _fish_label(fish: dict[str, Any], bait: str = "") -> str: name = fish.get("name", "fish") appearance = fish.get( "appearance", f"cute cartoon fish themed after {bait}" if bait else "cute cartoon fish", ) if bait: return f"{name} (bait was {bait}): {appearance}" return f"{name}: {appearance}" def fish_underwater_prompt(fish: dict[str, Any], bait: str = "") -> str: label = _fish_label(fish, bait) return ( f"Stardew Valley cartoon pixel art sprite, side view of one silly fish — {label}. " "Chibi proportions, big eye, bold flat colours, exaggerated cute shape. " "The fish body clearly mimics the bait theme. Simple underwater bubbles, " "soft colours. NOT photorealistic, NOT realistic." ) def fish_edit_prompt(fish: dict[str, Any], bait: str = "") -> str: label = _fish_label(fish, bait) return ( f"Keep this exact cartoon fish sprite ({label}) unchanged. Replace background " "with solid bright green #00FF00 greenscreen. No water. Same pixel art style." ) def fish_greenscreen_prompt(fish: dict[str, Any], bait: str = "") -> str: label = _fish_label(fish, bait) return ( f"Stardew Valley cartoon pixel art sprite, side view — {label}. " "Chibi fish, bait-themed body, big eye, fins and tail. " "Solid bright green #00FF00 greenscreen background. NOT photorealistic." )