CoolFace
Apppublic

ysharma/generative_art_lab

sourceHugging Facemitupdated 2mo agoView on Hugging Face
0likes
app.py115 linesDownload Raw Back to root
1"""201 · Generative Art Lab   (LOCAL — no token)   ·   the JSON + bind path3======================================================================4 5This app ships a hand-authored `workflow.json` (schema v2) with proper **image**6ports and a fan-out, plus the Python functions the nodes call. The canvas loads7the JSON; `bind=` supplies the implementations.8 9    gr.Workflow("workflow.json", bind={"generate": generate, ...}).launch()10 11Graph:                                   ┌─▶ posterize ─▶ [Posterized]12    [seed] [palette]                     │13    [complexity] [size]  ─▶  generate ───┼─▶ edge_glow ─▶ [Edge glow]14                              (image)     │15                                          └─▶ kaleidoscope ─▶ [Kaleidoscope]16                                          └────────────────▶ [Base]17 18Why data-URIs? A bound function returns JSON-serializable values, and a base6419`data:image/png` string renders directly in an image port with zero file20plumbing — so images flow node→node reliably. (Space/Model nodes handle files21for you; see app 04.)22 23Run it:24  python apps/01_generative_art_lab/app.py25"""26 27import base6428import io29import os30 31import gradio as gr32import numpy as np33from PIL import Image, ImageFilter, ImageOps34 35PALETTES = {36    "Sunset": [(15, 10, 40), (120, 20, 80), (230, 80, 60), (255, 180, 80), (255, 240, 200)],37    "Ocean":  [(2, 8, 40), (6, 40, 90), (10, 90, 140), (80, 180, 190), (220, 245, 240)],38    "Forest": [(10, 20, 15), (20, 60, 35), (60, 110, 50), (150, 180, 90), (240, 245, 200)],39    "Neon":   [(5, 0, 20), (80, 0, 120), (200, 0, 160), (0, 220, 220), (240, 255, 120)],40    "Mono":   [(0, 0, 0), (60, 60, 60), (130, 130, 130), (200, 200, 200), (255, 255, 255)],41}42 43 44# --- image <-> data-URI helpers (how functions exchange images on the canvas) ---45def _to_uri(im: Image.Image) -> str:46    buf = io.BytesIO()47    im.convert("RGB").save(buf, format="PNG")48    return "data:image/png;base64," + base64.b64encode(buf.getvalue()).decode()49 50 51def _from_uri(value) -> Image.Image:52    if isinstance(value, dict):                     # {path|url|...} from a component53        value = value.get("path") or value.get("url") or ""54    if isinstance(value, str) and value.startswith("data:"):55        return Image.open(io.BytesIO(base64.b64decode(value.split(",", 1)[1]))).convert("RGB")56    return Image.open(value).convert("RGB")         # a filepath, just in case57 58 59def _lut(stops, n=256):60    stops = np.array(stops, float)61    xs, grid = np.linspace(0, 1, len(stops)), np.linspace(0, 1, n)62    return np.stack([np.interp(grid, xs, stops[:, c]) for c in range(3)], axis=1)63 64 65# ----------------------------------------------------------------- bound functions66def generate(seed: float, palette: str, complexity: float, size: float) -> str:67    """Domain-warped plasma mapped through a palette. Deterministic from seed."""68    rng = np.random.default_rng(int(seed))69    n = int(size)70    y, x = np.mgrid[0:n, 0:n] / n71    field = np.zeros((n, n))72    for o in range(int(complexity)):73        f = (2 ** o) * 1.574        a1, a2 = rng.uniform(0, 2 * np.pi, 2)75        field += (np.sin(f * (x * np.cos(a1) + y * np.sin(a1)) + rng.uniform(0, 6.28))76                  * np.cos(f * (y * np.cos(a2) - x * np.sin(a2)) + rng.uniform(0, 6.28))) / (o + 1)77    field += 0.15 * int(complexity) * np.sin(field * 3.0 + rng.uniform(0, 6.28))78    field = (field - field.min()) / (np.ptp(field) + 1e-9)79    rgb = _lut(PALETTES.get(palette, PALETTES["Neon"]))[(field * 255).astype(np.uint8)]80    return _to_uri(Image.fromarray(rgb.astype(np.uint8), "RGB"))81 82 83def posterize(image: str) -> str:84    im = ImageOps.posterize(_from_uri(image), 3)85    return _to_uri(ImageOps.autocontrast(im, cutoff=1))86 87 88def edge_glow(image: str) -> str:89    im = _from_uri(image)90    edges = im.filter(ImageFilter.FIND_EDGES).filter(ImageFilter.GaussianBlur(1.2))91    dark = ImageOps.autocontrast(im).point(lambda p: int(p * 0.35))92    return _to_uri(Image.blend(dark, ImageOps.autocontrast(edges), 0.75))93 94 95def kaleidoscope(image: str) -> str:96    im = _from_uri(image)97    w, h = im.size98    q = im.crop((0, 0, w // 2, h // 2))99    top = Image.new("RGB", (w, h // 2))100    top.paste(q, (0, 0)); top.paste(ImageOps.mirror(q), (w // 2, 0))101    full = Image.new("RGB", (w, h))102    full.paste(top, (0, 0)); full.paste(ImageOps.flip(top), (0, h // 2))103    return _to_uri(full)104 105 106BIND = {"generate": generate, "posterize": posterize,107        "edge_glow": edge_glow, "kaleidoscope": kaleidoscope}108 109# Resolve the graph next to THIS file, so it runs from any working directory.110WORKFLOW = os.path.join(os.path.dirname(os.path.abspath(__file__)), "workflow.json")111demo = gr.Workflow(WORKFLOW, bind=BIND)112 113if __name__ == "__main__":114    demo.launch()115