Spaces:
Sleeping
Sleeping
Download app.py from wisamWR/ditherforge-api: direct link, hf CLI and curl.
- Browser
- Download file 7.14 kB
-
https://huggingface.co/spaces/wisamWR/ditherforge-api/resolve/main/app.py
- Command line
-
hf download hf://spaces/wisamWR/ditherforge-api/app.py
-
curl -L -o app.py https://huggingface.co/spaces/wisamWR/ditherforge-api/resolve/main/app.py
7.14 kB
| """DitherForge API — Gradio app with text, avatar, and banner endpoints.""" | |
| from __future__ import annotations | |
| import gradio as gr | |
| import spaces | |
| import requests | |
| from io import BytesIO | |
| from PIL import Image | |
| import numpy as np | |
| from effects import binary_effect, ascii_effect, braille_effect, image_to_gray | |
| from svg_builder import text_to_svg | |
| STYLES = {"binary": binary_effect, "ascii": ascii_effect, "braille": braille_effect} | |
| ANIMATIONS = ["none", "reveal", "scanline", "flicker", "matrix"] | |
| def text_endpoint( | |
| text: str, | |
| style: str = "ascii", | |
| color: str = "#00ff41", | |
| bg_color: str = "#0d1117", | |
| animation: str = "none", | |
| duration: float = 2.0, | |
| font_size: int = 14, | |
| ) -> tuple[str, str]: | |
| """Convert text string to styled SVG. Returns (html_preview, svg_source).""" | |
| effect_fn = STYLES.get(style, ascii_effect) | |
| lines = text.split("\n") if "\n" in text else [text] | |
| max_len = max(len(line) for line in lines) | |
| h_pixels = len(lines) * 16 | |
| w_pixels = max_len * 10 | |
| gray = np.zeros((h_pixels, w_pixels), dtype=np.uint8) | |
| for row_idx, line in enumerate(lines): | |
| for col_idx, char in enumerate(line): | |
| brightness = (ord(char) * 37) % 256 | |
| y0, y1 = row_idx * 16, (row_idx + 1) * 16 | |
| x0, x1 = col_idx * 10, (col_idx + 1) * 10 | |
| if y1 <= h_pixels and x1 <= w_pixels: | |
| gray[y0:y1, x0:x1] = brightness | |
| art_text = effect_fn(gray, cols=max_len) | |
| svg = text_to_svg(art_text, font_size, color, bg_color, animation, duration) | |
| return svg, svg | |
| def avatar_endpoint( | |
| image: Image.Image | None = None, | |
| image_url: str = "", | |
| style: str = "ascii", | |
| cols: int = 60, | |
| color: str = "#00ff41", | |
| bg_color: str = "#0d1117", | |
| animation: str = "none", | |
| duration: float = 2.0, | |
| ) -> tuple[str, str]: | |
| """Convert uploaded image or image URL to ASCII art SVG.""" | |
| if image is None and image_url: | |
| resp = requests.get(image_url, timeout=10) | |
| resp.raise_for_status() | |
| image = Image.open(BytesIO(resp.content)) | |
| if image is None: | |
| svg = text_to_svg("No image provided", 14, color, bg_color) | |
| return svg, svg | |
| gray = image_to_gray(image) | |
| effect_fn = STYLES.get(style, ascii_effect) | |
| art_text = effect_fn(gray, cols=cols) | |
| svg = text_to_svg(art_text, 14, color, bg_color, animation, duration) | |
| return svg, svg | |
| def banner_endpoint( | |
| text: str = "DitherForge", | |
| style: str = "binary", | |
| cols: int = 40, | |
| color: str = "#00ff41", | |
| bg_color: str = "#0d1117", | |
| animation: str = "reveal", | |
| duration: float = 2.0, | |
| ) -> tuple[str, str]: | |
| """Generate a banner SVG from parameters.""" | |
| from PIL import ImageDraw, ImageFont | |
| font_size = 40 | |
| char_w = font_size * 0.6 | |
| img_w = max(int(len(text) * char_w) + 20, 200) | |
| img_h = font_size + 20 | |
| img = Image.new("L", (img_w, img_h), 255) | |
| draw = ImageDraw.Draw(img) | |
| try: | |
| font = ImageFont.truetype( | |
| "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf", font_size | |
| ) | |
| except (OSError, IOError): | |
| font = ImageFont.load_default() | |
| draw.text((10, 10), text, fill=0, font=font) | |
| gray = np.array(img, dtype=np.uint8) | |
| effect_fn = STYLES.get(style, binary_effect) | |
| art_text = effect_fn(gray, cols=cols) | |
| svg = text_to_svg(art_text, 12, color, bg_color, animation, duration) | |
| return svg, svg | |
| # --- Gradio UI --- | |
| with gr.Blocks(title="DitherForge API") as demo: | |
| gr.Markdown( | |
| "# DitherForge API\n" | |
| "Generate ASCII/binary/braille art as SVG for GitHub README embeds." | |
| ) | |
| with gr.Tab("Text to SVG"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| txt_input = gr.Textbox(label="Text", value="Hello World") | |
| txt_style = gr.Dropdown( | |
| list(STYLES.keys()), value="ascii", label="Style" | |
| ) | |
| txt_color = gr.ColorPicker(label="Text Color", value="#00ff41") | |
| txt_bg = gr.ColorPicker(label="Background", value="#0d1117") | |
| txt_anim = gr.Dropdown(ANIMATIONS, value="none", label="Animation") | |
| txt_dur = gr.Slider( | |
| 0.5, 5.0, value=2.0, step=0.1, label="Duration (s)" | |
| ) | |
| txt_btn = gr.Button("Generate", variant="primary") | |
| with gr.Column(): | |
| txt_out = gr.HTML(label="Preview") | |
| txt_svg = gr.Code(label="SVG Source", language="html") | |
| txt_btn.click( | |
| text_endpoint, | |
| [txt_input, txt_style, txt_color, txt_bg, txt_anim, txt_dur], | |
| [txt_out, txt_svg], | |
| api_name="text", | |
| ) | |
| with gr.Tab("Image to ASCII SVG"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| img_input = gr.Image(type="pil", label="Upload Image") | |
| img_url = gr.Textbox( | |
| label="Or paste image URL", placeholder="https://..." | |
| ) | |
| img_style = gr.Dropdown( | |
| list(STYLES.keys()), value="ascii", label="Style" | |
| ) | |
| img_cols = gr.Slider(20, 120, value=60, step=1, label="Columns") | |
| img_color = gr.ColorPicker(label="Text Color", value="#00ff41") | |
| img_bg = gr.ColorPicker(label="Background", value="#0d1117") | |
| img_anim = gr.Dropdown(ANIMATIONS, value="none", label="Animation") | |
| img_btn = gr.Button("Generate", variant="primary") | |
| with gr.Column(): | |
| img_out = gr.HTML(label="Preview") | |
| img_svg = gr.Code(label="SVG Source", language="html") | |
| img_btn.click( | |
| avatar_endpoint, | |
| [img_input, img_url, img_style, img_cols, img_color, img_bg, img_anim], | |
| [img_out, img_svg], | |
| api_name="avatar", | |
| ) | |
| with gr.Tab("Banner"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| ban_text = gr.Textbox(label="Banner Text", value="DitherForge") | |
| ban_style = gr.Dropdown( | |
| list(STYLES.keys()), value="binary", label="Style" | |
| ) | |
| ban_cols = gr.Slider(20, 100, value=40, step=1, label="Columns") | |
| ban_color = gr.ColorPicker(label="Color", value="#00ff41") | |
| ban_bg = gr.ColorPicker(label="Background", value="#0d1117") | |
| ban_anim = gr.Dropdown( | |
| ANIMATIONS, value="reveal", label="Animation" | |
| ) | |
| ban_dur = gr.Slider( | |
| 0.5, 5.0, value=2.0, step=0.1, label="Duration (s)" | |
| ) | |
| ban_btn = gr.Button("Generate", variant="primary") | |
| with gr.Column(): | |
| ban_out = gr.HTML(label="Preview") | |
| ban_svg = gr.Code(label="SVG Source", language="html") | |
| ban_btn.click( | |
| banner_endpoint, | |
| [ban_text, ban_style, ban_cols, ban_color, ban_bg, ban_anim, ban_dur], | |
| [ban_out, ban_svg], | |
| api_name="banner", | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch(ssr_mode=False) | |