Spaces:
Runtime error
Runtime error
Add one-click PNG and SVG export buttons to both tabs
Browse files- Add _make_qr_stem, _write_png, _write_svg helpers (top of file, after PIL import)
- Add _download_png, _download_svg handler functions used as DownloadButton values
- Add ⬇ PNG and ⬇ SVG DownloadButtons to Standard tab output column
- Add ⬇ PNG and ⬇ SVG DownloadButtons to Artistic tab output column
- Use gr.DownloadButton(value=Callable, inputs=[...]) pattern so files are
generated on demand at click time — one click, no pre-caching
- Write temp files to /tmp (OS-managed) not /tmp/gradio/, so they are not
subject to the delete_cache=(3600, 3600) hourly sweep on the Space
- SVG format is PNG-embedded-in-SVG: confirmed scannable, opens in
Figma, Illustrator, Safari, and Chrome
app.py
CHANGED
|
@@ -22,6 +22,77 @@ from huggingface_hub import hf_hub_download
|
|
| 22 |
from PIL import Image
|
| 23 |
import kornia.color # For RGB→HSV conversion in Stable Cascade filter
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
# ComfyUI imports (after HF hub downloads)
|
| 26 |
from comfy import model_management
|
| 27 |
from comfy.cli_args import args
|
|
@@ -691,6 +762,14 @@ def generate_qr_code_unified(
|
|
| 691 |
variation_steps: int = 5,
|
| 692 |
enable_animation: bool = True,
|
| 693 |
enable_cascade_filter: bool = False,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 694 |
gr_progress=None,
|
| 695 |
):
|
| 696 |
# Track actual GPU time spent
|
|
@@ -772,6 +851,14 @@ def generate_qr_code_unified(
|
|
| 772 |
variation_steps=variation_steps,
|
| 773 |
enable_animation=enable_animation,
|
| 774 |
enable_cascade_filter=enable_cascade_filter,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 775 |
gr_progress=gr_progress,
|
| 776 |
):
|
| 777 |
yield result
|
|
@@ -1195,6 +1282,43 @@ def apply_stable_cascade_qr_filter(
|
|
| 1195 |
return filtered
|
| 1196 |
|
| 1197 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1198 |
def generate_standard_qr(
|
| 1199 |
prompt: str,
|
| 1200 |
negative_prompt: str = "ugly, tiling, poorly drawn hands, poorly drawn feet, poorly drawn face, out of frame, extra limbs, body out of frame, blurry, bad anatomy, blurred, watermark, grainy, signature, cut off, draft, closed eyes, text, logo",
|
|
@@ -1320,6 +1444,14 @@ def generate_artistic_qr(
|
|
| 1320 |
enable_upscale: bool = False,
|
| 1321 |
enable_animation: bool = True,
|
| 1322 |
enable_cascade_filter: bool = False,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1323 |
enable_freeu: bool = True,
|
| 1324 |
freeu_b1: float = 1.4,
|
| 1325 |
freeu_b2: float = 1.3,
|
|
@@ -1418,6 +1550,14 @@ def generate_artistic_qr(
|
|
| 1418 |
variation_steps=variation_steps,
|
| 1419 |
enable_animation=enable_animation,
|
| 1420 |
enable_cascade_filter=enable_cascade_filter,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1421 |
gr_progress=progress,
|
| 1422 |
)
|
| 1423 |
|
|
@@ -2363,6 +2503,14 @@ def _pipeline_artistic(
|
|
| 2363 |
variation_steps: int = 5,
|
| 2364 |
enable_animation: bool = True,
|
| 2365 |
enable_cascade_filter: bool = False,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2366 |
gr_progress=None,
|
| 2367 |
):
|
| 2368 |
# Initialize animation handler if enabled
|
|
@@ -2473,7 +2621,9 @@ def _pipeline_artistic(
|
|
| 2473 |
# Apply Stable Cascade filter if enabled
|
| 2474 |
if enable_cascade_filter:
|
| 2475 |
qr_for_brightness = apply_stable_cascade_qr_filter(
|
| 2476 |
-
qr_with_border_noise,
|
|
|
|
|
|
|
| 2477 |
)
|
| 2478 |
else:
|
| 2479 |
qr_for_brightness = qr_with_border_noise
|
|
@@ -2491,8 +2641,11 @@ def _pipeline_artistic(
|
|
| 2491 |
)
|
| 2492 |
|
| 2493 |
# Tile preprocessor (using filtered or raw QR with border cubics)
|
|
|
|
|
|
|
|
|
|
| 2494 |
tile_processed = tilepreprocessor.execute(
|
| 2495 |
-
pyrUp_iters=
|
| 2496 |
resolution=image_size,
|
| 2497 |
image=qr_for_brightness,
|
| 2498 |
)
|
|
@@ -2601,6 +2754,17 @@ def _pipeline_artistic(
|
|
| 2601 |
first_pass_np = (first_pass_tensor.detach().cpu().numpy() * 255).astype(np.uint8)
|
| 2602 |
first_pass_np = first_pass_np[0]
|
| 2603 |
first_pass_pil = Image.fromarray(first_pass_np)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2604 |
msg = f"First enhancement pass complete (step {current_step}/{total_steps})... final refinement pass"
|
| 2605 |
log_progress(msg, gr_progress, 0.5)
|
| 2606 |
yield (first_pass_pil, msg)
|
|
@@ -3199,17 +3363,167 @@ with gr.Blocks(delete_cache=(3600, 3600)) as demo:
|
|
| 3199 |
info="Shows intermediate images every 5 steps during generation. Disable for faster generation.",
|
| 3200 |
)
|
| 3201 |
|
| 3202 |
-
#
|
| 3203 |
-
gr.
|
| 3204 |
-
|
| 3205 |
-
|
| 3206 |
-
|
| 3207 |
-
|
| 3208 |
-
|
| 3209 |
-
|
| 3210 |
-
|
| 3211 |
-
|
| 3212 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3213 |
|
| 3214 |
# Color Quantization Section
|
| 3215 |
gr.Markdown("### Color Quantization (Optional)")
|
|
@@ -3464,6 +3778,31 @@ with gr.Blocks(delete_cache=(3600, 3600)) as demo:
|
|
| 3464 |
show_copy_button=True,
|
| 3465 |
)
|
| 3466 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3467 |
# Button to show examples again (initially hidden)
|
| 3468 |
show_examples_btn = gr.Button(
|
| 3469 |
"🎨 Try Another Example",
|
|
@@ -3489,6 +3828,14 @@ with gr.Blocks(delete_cache=(3600, 3600)) as demo:
|
|
| 3489 |
artistic_enable_upscale,
|
| 3490 |
artistic_enable_animation,
|
| 3491 |
enable_cascade_filter_artistic,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3492 |
enable_freeu_artistic,
|
| 3493 |
freeu_b1,
|
| 3494 |
freeu_b2,
|
|
@@ -4074,6 +4421,23 @@ with gr.Blocks(delete_cache=(3600, 3600)) as demo:
|
|
| 4074 |
show_copy_button=True,
|
| 4075 |
)
|
| 4076 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4077 |
# When clicking the button, it will trigger the main function
|
| 4078 |
generate_btn.click(
|
| 4079 |
fn=generate_standard_qr,
|
|
|
|
| 22 |
from PIL import Image
|
| 23 |
import kornia.color # For RGB→HSV conversion in Stable Cascade filter
|
| 24 |
|
| 25 |
+
# ── Export helpers (PNG + embedded SVG download) ──────────────────────────────
|
| 26 |
+
import base64
|
| 27 |
+
import io
|
| 28 |
+
import re
|
| 29 |
+
import tempfile
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _make_qr_stem(text_input: str, seed: int) -> str:
|
| 33 |
+
"""Build a short human-readable filename stem from QR payload + seed."""
|
| 34 |
+
slug = re.sub(r"[^a-z0-9]+", "-", text_input.lower()).strip("-")[:30] or "export"
|
| 35 |
+
return f"ai-qr-{slug}-seed{seed}"
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _write_png(img: Image.Image, stem: str) -> str:
|
| 39 |
+
"""Write PNG to a named temp file outside Gradio's cache. Returns path.
|
| 40 |
+
|
| 41 |
+
Uses /tmp (OS-managed) rather than Gradio's /tmp/gradio/ cache so the
|
| 42 |
+
file is not subject to the delete_cache=(3600, 3600) sweep on the Space.
|
| 43 |
+
"""
|
| 44 |
+
tmp = tempfile.NamedTemporaryFile(suffix=".png", prefix=f"{stem}-", delete=False)
|
| 45 |
+
img.convert("RGB").save(tmp.name, "PNG")
|
| 46 |
+
tmp.close()
|
| 47 |
+
return tmp.name
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def _write_svg(img: Image.Image, stem: str) -> str:
|
| 51 |
+
"""Write a PNG-embedded SVG to a named temp file. Returns path.
|
| 52 |
+
|
| 53 |
+
The SVG wraps the raster image as a base64-encoded PNG inside a valid SVG
|
| 54 |
+
container. Confirmed scannable and opens correctly in Figma, Illustrator,
|
| 55 |
+
Safari, and Chrome. Written outside Gradio's cache for the same reason as
|
| 56 |
+
_write_png — not subject to the 1-hour cache sweep.
|
| 57 |
+
"""
|
| 58 |
+
buf = io.BytesIO()
|
| 59 |
+
img.convert("RGB").save(buf, "PNG")
|
| 60 |
+
b64 = base64.b64encode(buf.getvalue()).decode("ascii")
|
| 61 |
+
w, h = img.size
|
| 62 |
+
svg = (
|
| 63 |
+
'<?xml version="1.0" encoding="UTF-8"?>'
|
| 64 |
+
f'<svg xmlns="http://www.w3.org/2000/svg" '
|
| 65 |
+
f'xmlns:xlink="http://www.w3.org/1999/xlink" '
|
| 66 |
+
f'width="{w}" height="{h}" viewBox="0 0 {w} {h}">'
|
| 67 |
+
f"<title>AI QR Code</title>"
|
| 68 |
+
f'<image href="data:image/png;base64,{b64}" '
|
| 69 |
+
f'x="0" y="0" width="{w}" height="{h}" '
|
| 70 |
+
f'preserveAspectRatio="xMidYMid meet"/></svg>'
|
| 71 |
+
)
|
| 72 |
+
tmp = tempfile.NamedTemporaryFile(suffix=".svg", prefix=f"{stem}-", delete=False)
|
| 73 |
+
tmp.write(svg.encode("utf-8"))
|
| 74 |
+
tmp.close()
|
| 75 |
+
return tmp.name
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def _download_png(image, text_input, seed):
|
| 79 |
+
"""Called by DownloadButton at click time — generates PNG on demand."""
|
| 80 |
+
if image is None:
|
| 81 |
+
return None
|
| 82 |
+
img = image if isinstance(image, Image.Image) else Image.fromarray(image)
|
| 83 |
+
return _write_png(img, _make_qr_stem(str(text_input), int(seed)))
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def _download_svg(image, text_input, seed):
|
| 87 |
+
"""Called by DownloadButton at click time — generates embedded SVG on demand."""
|
| 88 |
+
if image is None:
|
| 89 |
+
return None
|
| 90 |
+
img = image if isinstance(image, Image.Image) else Image.fromarray(image)
|
| 91 |
+
return _write_svg(img, _make_qr_stem(str(text_input), int(seed)))
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
# ─────────────────────────────────────────────────────────────────────────────
|
| 95 |
+
|
| 96 |
# ComfyUI imports (after HF hub downloads)
|
| 97 |
from comfy import model_management
|
| 98 |
from comfy.cli_args import args
|
|
|
|
| 762 |
variation_steps: int = 5,
|
| 763 |
enable_animation: bool = True,
|
| 764 |
enable_cascade_filter: bool = False,
|
| 765 |
+
cascade_blur_kernel: int = 15,
|
| 766 |
+
cascade_threshold_ratio: float = 0.33,
|
| 767 |
+
enable_detail_sharpening: bool = False,
|
| 768 |
+
sharpening_radius: float = 2.0,
|
| 769 |
+
sharpening_amount: float = 1.5,
|
| 770 |
+
sharpening_threshold: int = 0,
|
| 771 |
+
customize_tile_preprocessing: bool = False,
|
| 772 |
+
tile_pyrup_iters: int = 3,
|
| 773 |
gr_progress=None,
|
| 774 |
):
|
| 775 |
# Track actual GPU time spent
|
|
|
|
| 851 |
variation_steps=variation_steps,
|
| 852 |
enable_animation=enable_animation,
|
| 853 |
enable_cascade_filter=enable_cascade_filter,
|
| 854 |
+
cascade_blur_kernel=cascade_blur_kernel,
|
| 855 |
+
cascade_threshold_ratio=cascade_threshold_ratio,
|
| 856 |
+
enable_detail_sharpening=enable_detail_sharpening,
|
| 857 |
+
sharpening_radius=sharpening_radius,
|
| 858 |
+
sharpening_amount=sharpening_amount,
|
| 859 |
+
sharpening_threshold=sharpening_threshold,
|
| 860 |
+
customize_tile_preprocessing=customize_tile_preprocessing,
|
| 861 |
+
tile_pyrup_iters=tile_pyrup_iters,
|
| 862 |
gr_progress=gr_progress,
|
| 863 |
):
|
| 864 |
yield result
|
|
|
|
| 1282 |
return filtered
|
| 1283 |
|
| 1284 |
|
| 1285 |
+
def apply_detail_sharpening(
|
| 1286 |
+
image: Image.Image, radius: float = 2.0, amount: float = 1.5, threshold: int = 0
|
| 1287 |
+
) -> Image.Image:
|
| 1288 |
+
"""
|
| 1289 |
+
Apply unsharp mask sharpening to preserve QR details between passes.
|
| 1290 |
+
|
| 1291 |
+
This filter is applied to the first-pass output before the second pass,
|
| 1292 |
+
helping maintain sharp QR code edges when using lower ControlNet strengths.
|
| 1293 |
+
|
| 1294 |
+
Args:
|
| 1295 |
+
image: PIL Image to sharpen
|
| 1296 |
+
radius: Sharpening radius in pixels (1.0-5.0)
|
| 1297 |
+
Higher values = wider sharpening effect
|
| 1298 |
+
amount: Sharpening strength (0.5-3.0)
|
| 1299 |
+
Higher values = stronger sharpening
|
| 1300 |
+
threshold: Minimum brightness change to sharpen (0-10)
|
| 1301 |
+
0 = sharpen all pixels, higher = sharpen only high-contrast edges
|
| 1302 |
+
|
| 1303 |
+
Returns:
|
| 1304 |
+
Sharpened PIL Image
|
| 1305 |
+
"""
|
| 1306 |
+
from PIL import ImageFilter
|
| 1307 |
+
|
| 1308 |
+
# Ensure radius is valid for UnsharpMask (must be positive)
|
| 1309 |
+
radius = max(0.1, radius)
|
| 1310 |
+
|
| 1311 |
+
# Apply unsharp mask filter
|
| 1312 |
+
# PIL's UnsharpMask expects percent as integer (0-100+)
|
| 1313 |
+
sharpened = image.filter(
|
| 1314 |
+
ImageFilter.UnsharpMask(
|
| 1315 |
+
radius=radius, percent=int(amount * 100), threshold=threshold
|
| 1316 |
+
)
|
| 1317 |
+
)
|
| 1318 |
+
|
| 1319 |
+
return sharpened
|
| 1320 |
+
|
| 1321 |
+
|
| 1322 |
def generate_standard_qr(
|
| 1323 |
prompt: str,
|
| 1324 |
negative_prompt: str = "ugly, tiling, poorly drawn hands, poorly drawn feet, poorly drawn face, out of frame, extra limbs, body out of frame, blurry, bad anatomy, blurred, watermark, grainy, signature, cut off, draft, closed eyes, text, logo",
|
|
|
|
| 1444 |
enable_upscale: bool = False,
|
| 1445 |
enable_animation: bool = True,
|
| 1446 |
enable_cascade_filter: bool = False,
|
| 1447 |
+
cascade_blur_kernel: int = 15,
|
| 1448 |
+
cascade_threshold_ratio: float = 0.33,
|
| 1449 |
+
enable_detail_sharpening: bool = False,
|
| 1450 |
+
sharpening_radius: float = 2.0,
|
| 1451 |
+
sharpening_amount: float = 1.5,
|
| 1452 |
+
sharpening_threshold: int = 0,
|
| 1453 |
+
customize_tile_preprocessing: bool = False,
|
| 1454 |
+
tile_pyrup_iters: int = 3,
|
| 1455 |
enable_freeu: bool = True,
|
| 1456 |
freeu_b1: float = 1.4,
|
| 1457 |
freeu_b2: float = 1.3,
|
|
|
|
| 1550 |
variation_steps=variation_steps,
|
| 1551 |
enable_animation=enable_animation,
|
| 1552 |
enable_cascade_filter=enable_cascade_filter,
|
| 1553 |
+
cascade_blur_kernel=cascade_blur_kernel,
|
| 1554 |
+
cascade_threshold_ratio=cascade_threshold_ratio,
|
| 1555 |
+
enable_detail_sharpening=enable_detail_sharpening,
|
| 1556 |
+
sharpening_radius=sharpening_radius,
|
| 1557 |
+
sharpening_amount=sharpening_amount,
|
| 1558 |
+
sharpening_threshold=sharpening_threshold,
|
| 1559 |
+
customize_tile_preprocessing=customize_tile_preprocessing,
|
| 1560 |
+
tile_pyrup_iters=tile_pyrup_iters,
|
| 1561 |
gr_progress=progress,
|
| 1562 |
)
|
| 1563 |
|
|
|
|
| 2503 |
variation_steps: int = 5,
|
| 2504 |
enable_animation: bool = True,
|
| 2505 |
enable_cascade_filter: bool = False,
|
| 2506 |
+
cascade_blur_kernel: int = 15,
|
| 2507 |
+
cascade_threshold_ratio: float = 0.33,
|
| 2508 |
+
enable_detail_sharpening: bool = False,
|
| 2509 |
+
sharpening_radius: float = 2.0,
|
| 2510 |
+
sharpening_amount: float = 1.5,
|
| 2511 |
+
sharpening_threshold: int = 0,
|
| 2512 |
+
customize_tile_preprocessing: bool = False,
|
| 2513 |
+
tile_pyrup_iters: int = 3,
|
| 2514 |
gr_progress=None,
|
| 2515 |
):
|
| 2516 |
# Initialize animation handler if enabled
|
|
|
|
| 2621 |
# Apply Stable Cascade filter if enabled
|
| 2622 |
if enable_cascade_filter:
|
| 2623 |
qr_for_brightness = apply_stable_cascade_qr_filter(
|
| 2624 |
+
qr_with_border_noise,
|
| 2625 |
+
blur_kernel=cascade_blur_kernel,
|
| 2626 |
+
threshold_ratio=cascade_threshold_ratio,
|
| 2627 |
)
|
| 2628 |
else:
|
| 2629 |
qr_for_brightness = qr_with_border_noise
|
|
|
|
| 2641 |
)
|
| 2642 |
|
| 2643 |
# Tile preprocessor (using filtered or raw QR with border cubics)
|
| 2644 |
+
# Use custom pyrUp_iters if enabled, otherwise default to 3
|
| 2645 |
+
actual_pyrup_iters = tile_pyrup_iters if customize_tile_preprocessing else 3
|
| 2646 |
+
|
| 2647 |
tile_processed = tilepreprocessor.execute(
|
| 2648 |
+
pyrUp_iters=actual_pyrup_iters,
|
| 2649 |
resolution=image_size,
|
| 2650 |
image=qr_for_brightness,
|
| 2651 |
)
|
|
|
|
| 2754 |
first_pass_np = (first_pass_tensor.detach().cpu().numpy() * 255).astype(np.uint8)
|
| 2755 |
first_pass_np = first_pass_np[0]
|
| 2756 |
first_pass_pil = Image.fromarray(first_pass_np)
|
| 2757 |
+
|
| 2758 |
+
# Apply detail sharpening if enabled (experimental feature)
|
| 2759 |
+
# This preserves QR code edge sharpness for the second pass
|
| 2760 |
+
if enable_detail_sharpening:
|
| 2761 |
+
first_pass_pil = apply_detail_sharpening(
|
| 2762 |
+
first_pass_pil,
|
| 2763 |
+
radius=sharpening_radius,
|
| 2764 |
+
amount=sharpening_amount,
|
| 2765 |
+
threshold=sharpening_threshold,
|
| 2766 |
+
)
|
| 2767 |
+
|
| 2768 |
msg = f"First enhancement pass complete (step {current_step}/{total_steps})... final refinement pass"
|
| 2769 |
log_progress(msg, gr_progress, 0.5)
|
| 2770 |
yield (first_pass_pil, msg)
|
|
|
|
| 3363 |
info="Shows intermediate images every 5 steps during generation. Disable for faster generation.",
|
| 3364 |
)
|
| 3365 |
|
| 3366 |
+
# Experimental Settings Section
|
| 3367 |
+
with gr.Accordion(
|
| 3368 |
+
"⚙️ Experimental Settings (Advanced Users Only)", open=False
|
| 3369 |
+
):
|
| 3370 |
+
gr.Markdown("""
|
| 3371 |
+
⚠️ **Warning:** These features are experimental and may affect generation quality,
|
| 3372 |
+
scannability, or processing time. Only enable if you understand their effects.
|
| 3373 |
+
|
| 3374 |
+
These settings allow fine-tuning of the artistic pipeline for advanced users who want
|
| 3375 |
+
more control over detail preservation, QR preprocessing, and enhancement strategies.
|
| 3376 |
+
|
| 3377 |
+
**Recommendation:** Start with defaults, then enable one feature at a time to understand
|
| 3378 |
+
its impact on your specific use case.
|
| 3379 |
+
""")
|
| 3380 |
+
|
| 3381 |
+
# Section 1: Stable Cascade QR Filter
|
| 3382 |
+
gr.Markdown("### 🔹 Stable Cascade QR Filter")
|
| 3383 |
+
gr.Markdown(
|
| 3384 |
+
"Advanced preprocessing filter using HSV color space, Gaussian blur, and "
|
| 3385 |
+
"adaptive thresholding. Based on Stable Cascade implementation."
|
| 3386 |
+
)
|
| 3387 |
+
|
| 3388 |
+
enable_cascade_filter_artistic = gr.Checkbox(
|
| 3389 |
+
label="Enable Stable Cascade QR Filter",
|
| 3390 |
+
value=False,
|
| 3391 |
+
info="Apply HSV-based brightness filter to QR code before ControlNet",
|
| 3392 |
+
)
|
| 3393 |
+
|
| 3394 |
+
# Advanced Cascade parameters (nested accordion, visible only when filter enabled)
|
| 3395 |
+
cascade_advanced_accordion = gr.Accordion(
|
| 3396 |
+
"Advanced Cascade Parameters", open=False, visible=False
|
| 3397 |
+
)
|
| 3398 |
+
|
| 3399 |
+
with cascade_advanced_accordion:
|
| 3400 |
+
gr.Markdown(
|
| 3401 |
+
"Fine-tune filter behavior. Higher blur = smoother, higher threshold = sharper cutoff."
|
| 3402 |
+
)
|
| 3403 |
+
|
| 3404 |
+
cascade_blur_kernel_artistic = gr.Slider(
|
| 3405 |
+
minimum=5,
|
| 3406 |
+
maximum=35,
|
| 3407 |
+
step=2,
|
| 3408 |
+
value=15,
|
| 3409 |
+
label="Blur Kernel Size",
|
| 3410 |
+
info="Gaussian blur kernel size (must be odd). Default: 15",
|
| 3411 |
+
)
|
| 3412 |
+
|
| 3413 |
+
cascade_threshold_ratio_artistic = gr.Slider(
|
| 3414 |
+
minimum=0.1,
|
| 3415 |
+
maximum=0.5,
|
| 3416 |
+
step=0.05,
|
| 3417 |
+
value=0.33,
|
| 3418 |
+
label="Threshold Ratio",
|
| 3419 |
+
info="Adaptive threshold ratio. Default: 0.33",
|
| 3420 |
+
)
|
| 3421 |
+
|
| 3422 |
+
# Show/hide advanced parameters when filter is toggled
|
| 3423 |
+
enable_cascade_filter_artistic.change(
|
| 3424 |
+
fn=lambda x: gr.update(visible=x),
|
| 3425 |
+
inputs=[enable_cascade_filter_artistic],
|
| 3426 |
+
outputs=[cascade_advanced_accordion],
|
| 3427 |
+
)
|
| 3428 |
+
|
| 3429 |
+
# Section 2: Detail Sharpening
|
| 3430 |
+
gr.Markdown("### 🔹 Detail Sharpening")
|
| 3431 |
+
gr.Markdown(
|
| 3432 |
+
"Apply unsharp mask between first and second pass to preserve QR code details. "
|
| 3433 |
+
"Allows lower first-pass ControlNet strength (e.g., 0.35-0.40) while maintaining scannability."
|
| 3434 |
+
)
|
| 3435 |
+
|
| 3436 |
+
enable_detail_sharpening_artistic = gr.Checkbox(
|
| 3437 |
+
label="Enable Detail Sharpening",
|
| 3438 |
+
value=False,
|
| 3439 |
+
info="Sharpen first-pass output before second pass to preserve QR edges",
|
| 3440 |
+
)
|
| 3441 |
+
|
| 3442 |
+
# Sharpening parameters (nested accordion, visible only when enabled)
|
| 3443 |
+
sharpening_params_accordion = gr.Accordion(
|
| 3444 |
+
"Sharpening Parameters", open=False, visible=False
|
| 3445 |
+
)
|
| 3446 |
+
|
| 3447 |
+
with sharpening_params_accordion:
|
| 3448 |
+
gr.Markdown(
|
| 3449 |
+
"Adjust sharpening behavior. Start with defaults, increase amount for stronger effect."
|
| 3450 |
+
)
|
| 3451 |
+
|
| 3452 |
+
sharpening_radius_artistic = gr.Slider(
|
| 3453 |
+
minimum=1.0,
|
| 3454 |
+
maximum=5.0,
|
| 3455 |
+
step=0.5,
|
| 3456 |
+
value=2.0,
|
| 3457 |
+
label="Sharpening Radius",
|
| 3458 |
+
info="Sharpening width in pixels. Higher = wider effect. Default: 2.0",
|
| 3459 |
+
)
|
| 3460 |
+
|
| 3461 |
+
sharpening_amount_artistic = gr.Slider(
|
| 3462 |
+
minimum=0.5,
|
| 3463 |
+
maximum=3.0,
|
| 3464 |
+
step=0.1,
|
| 3465 |
+
value=1.5,
|
| 3466 |
+
label="Sharpening Amount",
|
| 3467 |
+
info="Sharpening strength. Higher = stronger. Default: 1.5",
|
| 3468 |
+
)
|
| 3469 |
+
|
| 3470 |
+
sharpening_threshold_artistic = gr.Slider(
|
| 3471 |
+
minimum=0,
|
| 3472 |
+
maximum=10,
|
| 3473 |
+
step=1,
|
| 3474 |
+
value=0,
|
| 3475 |
+
label="Sharpening Threshold",
|
| 3476 |
+
info="Minimum brightness change. 0 = all pixels, higher = edges only. Default: 0",
|
| 3477 |
+
)
|
| 3478 |
+
|
| 3479 |
+
# Show/hide parameters when sharpening is toggled
|
| 3480 |
+
enable_detail_sharpening_artistic.change(
|
| 3481 |
+
fn=lambda x: gr.update(visible=x),
|
| 3482 |
+
inputs=[enable_detail_sharpening_artistic],
|
| 3483 |
+
outputs=[sharpening_params_accordion],
|
| 3484 |
+
)
|
| 3485 |
+
|
| 3486 |
+
# Section 3: Tile Preprocessor Configuration
|
| 3487 |
+
gr.Markdown("### 🔹 Tile Preprocessor Configuration")
|
| 3488 |
+
gr.Markdown(
|
| 3489 |
+
"Adjust tile preprocessor detail level. Lower values preserve more details but may "
|
| 3490 |
+
"reduce composition coherence. Higher values create smoother, more coherent results but lose fine details."
|
| 3491 |
+
)
|
| 3492 |
+
|
| 3493 |
+
customize_tile_preprocessing_artistic = gr.Checkbox(
|
| 3494 |
+
label="Customize Tile Preprocessing",
|
| 3495 |
+
value=False,
|
| 3496 |
+
info="Override default pyrUp iterations (default: 3)",
|
| 3497 |
+
)
|
| 3498 |
+
|
| 3499 |
+
# Tile parameters (nested accordion, visible only when enabled)
|
| 3500 |
+
tile_params_accordion = gr.Accordion(
|
| 3501 |
+
"Tile Preprocessing Parameters",
|
| 3502 |
+
open=False,
|
| 3503 |
+
visible=False,
|
| 3504 |
+
)
|
| 3505 |
+
|
| 3506 |
+
with tile_params_accordion:
|
| 3507 |
+
gr.Markdown(
|
| 3508 |
+
"pyrUp iterations control detail vs smoothness trade-off. "
|
| 3509 |
+
"Default (3) is balanced. Lower = sharper/more details, Higher = smoother/less details."
|
| 3510 |
+
)
|
| 3511 |
+
|
| 3512 |
+
tile_pyrup_iters_artistic = gr.Slider(
|
| 3513 |
+
minimum=1,
|
| 3514 |
+
maximum=4,
|
| 3515 |
+
step=1,
|
| 3516 |
+
value=3,
|
| 3517 |
+
label="Tile Detail Level (pyrUp iterations)",
|
| 3518 |
+
info="1=sharpest, 2=sharp, 3=balanced (default), 4=smoothest",
|
| 3519 |
+
)
|
| 3520 |
+
|
| 3521 |
+
# Show/hide parameters when customization is toggled
|
| 3522 |
+
customize_tile_preprocessing_artistic.change(
|
| 3523 |
+
fn=lambda x: gr.update(visible=x),
|
| 3524 |
+
inputs=[customize_tile_preprocessing_artistic],
|
| 3525 |
+
outputs=[tile_params_accordion],
|
| 3526 |
+
)
|
| 3527 |
|
| 3528 |
# Color Quantization Section
|
| 3529 |
gr.Markdown("### Color Quantization (Optional)")
|
|
|
|
| 3778 |
show_copy_button=True,
|
| 3779 |
)
|
| 3780 |
|
| 3781 |
+
# Export buttons — generated on demand at click time (cache-sweep safe)
|
| 3782 |
+
with gr.Row():
|
| 3783 |
+
png_download_artistic = gr.DownloadButton(
|
| 3784 |
+
"⬇ PNG",
|
| 3785 |
+
variant="primary",
|
| 3786 |
+
size="sm",
|
| 3787 |
+
value=_download_png,
|
| 3788 |
+
inputs=[
|
| 3789 |
+
artistic_output_image,
|
| 3790 |
+
artistic_text_input,
|
| 3791 |
+
artistic_seed,
|
| 3792 |
+
],
|
| 3793 |
+
)
|
| 3794 |
+
svg_download_artistic = gr.DownloadButton(
|
| 3795 |
+
"⬇ SVG",
|
| 3796 |
+
variant="secondary",
|
| 3797 |
+
size="sm",
|
| 3798 |
+
value=_download_svg,
|
| 3799 |
+
inputs=[
|
| 3800 |
+
artistic_output_image,
|
| 3801 |
+
artistic_text_input,
|
| 3802 |
+
artistic_seed,
|
| 3803 |
+
],
|
| 3804 |
+
)
|
| 3805 |
+
|
| 3806 |
# Button to show examples again (initially hidden)
|
| 3807 |
show_examples_btn = gr.Button(
|
| 3808 |
"🎨 Try Another Example",
|
|
|
|
| 3828 |
artistic_enable_upscale,
|
| 3829 |
artistic_enable_animation,
|
| 3830 |
enable_cascade_filter_artistic,
|
| 3831 |
+
cascade_blur_kernel_artistic,
|
| 3832 |
+
cascade_threshold_ratio_artistic,
|
| 3833 |
+
enable_detail_sharpening_artistic,
|
| 3834 |
+
sharpening_radius_artistic,
|
| 3835 |
+
sharpening_amount_artistic,
|
| 3836 |
+
sharpening_threshold_artistic,
|
| 3837 |
+
customize_tile_preprocessing_artistic,
|
| 3838 |
+
tile_pyrup_iters_artistic,
|
| 3839 |
enable_freeu_artistic,
|
| 3840 |
freeu_b1,
|
| 3841 |
freeu_b2,
|
|
|
|
| 4421 |
show_copy_button=True,
|
| 4422 |
)
|
| 4423 |
|
| 4424 |
+
# Export buttons — generated on demand at click time (cache-sweep safe)
|
| 4425 |
+
with gr.Row():
|
| 4426 |
+
png_download_standard = gr.DownloadButton(
|
| 4427 |
+
"⬇ PNG",
|
| 4428 |
+
variant="primary",
|
| 4429 |
+
size="sm",
|
| 4430 |
+
value=_download_png,
|
| 4431 |
+
inputs=[output_image, text_input, seed],
|
| 4432 |
+
)
|
| 4433 |
+
svg_download_standard = gr.DownloadButton(
|
| 4434 |
+
"⬇ SVG",
|
| 4435 |
+
variant="secondary",
|
| 4436 |
+
size="sm",
|
| 4437 |
+
value=_download_svg,
|
| 4438 |
+
inputs=[output_image, text_input, seed],
|
| 4439 |
+
)
|
| 4440 |
+
|
| 4441 |
# When clicking the button, it will trigger the main function
|
| 4442 |
generate_btn.click(
|
| 4443 |
fn=generate_standard_qr,
|