Spaces:
Sleeping
Sleeping
File size: 7,144 Bytes
ffe3e3c 8c2076c ffe3e3c 8c2076c ffe3e3c c5a0947 ffe3e3c c5a0947 ffe3e3c c5a0947 ffe3e3c 747c419 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | """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
@spaces.GPU(duration=5)
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)
|