Tanut
commited on
Commit
·
67e5d0e
1
Parent(s):
3dc7a6e
refine
Browse files
.DS_Store
ADDED
|
Binary file (6.15 kB). View file
|
|
|
app.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
import os, gc, random, re, inspect
|
| 2 |
from contextlib import nullcontext
|
| 3 |
|
| 4 |
-
from PIL import Image, ImageOps
|
| 5 |
|
| 6 |
import gradio as gr
|
| 7 |
import torch, spaces
|
|
@@ -127,15 +127,6 @@ def normalize_color(c):
|
|
| 127 |
return s
|
| 128 |
return "white"
|
| 129 |
|
| 130 |
-
def make_qr(url="https://example.com", size=768, border=12, back_color="#FFFFFF", blur_radius=0.0):
|
| 131 |
-
qr = qrcode.QRCode(version=None, error_correction=ERROR_CORRECT_H, box_size=10, border=int(border))
|
| 132 |
-
qr.add_data(url.strip()); qr.make(fit=True)
|
| 133 |
-
img = qr.make_image(fill_color="black", back_color=normalize_color(back_color)).convert("RGB")
|
| 134 |
-
img = img.resize((int(size), int(size)), Image.NEAREST)
|
| 135 |
-
if blur_radius and blur_radius > 0:
|
| 136 |
-
img = img.filter(ImageFilter.GaussianBlur(radius=float(blur_radius)))
|
| 137 |
-
return img
|
| 138 |
-
|
| 139 |
def enforce_qr_contrast(stylized: Image.Image, qr_img: Image.Image, strength: float = 0.0, feather: float = 1.0) -> Image.Image:
|
| 140 |
if strength <= 0: return stylized
|
| 141 |
q = qr_img.convert("L")
|
|
@@ -148,29 +139,113 @@ def enforce_qr_contrast(stylized: Image.Image, qr_img: Image.Image, strength: fl
|
|
| 148 |
s = np.clip(s, 0.0, 1.0)
|
| 149 |
return Image.fromarray((s * 255.0).astype(np.uint8), mode="RGB")
|
| 150 |
|
| 151 |
-
def
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
im = qr_upload.convert("RGB")
|
| 160 |
-
|
| 161 |
-
if strict:
|
| 162 |
-
g = ImageOps.grayscale(im)
|
| 163 |
-
bw = g.point(lambda p: 0 if p < 128 else 255, "L")
|
| 164 |
-
im = Image.merge("RGB", (bw, bw, bw))
|
| 165 |
-
|
| 166 |
-
# resize for ControlNet
|
| 167 |
-
im = im.resize((size, size), Image.NEAREST)
|
| 168 |
-
im.save("debug_qr_after_prep.png") # debug output
|
| 169 |
-
return im
|
| 170 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
|
|
|
|
|
|
|
| 174 |
|
| 175 |
# ----- Brightness map preprocessing & mixing -----
|
| 176 |
def prep_brightness_map(img: Image.Image, size: int, source: str,
|
|
@@ -301,7 +376,7 @@ def _qr_txt2img_core(model_id: str,
|
|
| 301 |
# --- Build base-size control images (s x s)
|
| 302 |
# qr_img = make_qr(url=url, size=s, border=int(border), back_color="#FFFFFF", blur_radius=0.0)
|
| 303 |
if qr_upload is not None:
|
| 304 |
-
qr_img =
|
| 305 |
else:
|
| 306 |
qr_img = make_qr(url=url, size=s, border=int(border), back_color="#FFFFFF")
|
| 307 |
|
|
@@ -457,7 +532,7 @@ def _qr_img2img_core(model_id: str,
|
|
| 457 |
# make_qr(url=url, size=s, border=int(border), back_color="#FFFFFF", blur_radius=0.0)
|
| 458 |
# )
|
| 459 |
if qr_upload is not None:
|
| 460 |
-
qr_img =
|
| 461 |
else:
|
| 462 |
qr_img = make_qr(url=url, size=s, border=int(border), back_color="#FFFFFF")
|
| 463 |
|
|
|
|
| 1 |
import os, gc, random, re, inspect
|
| 2 |
from contextlib import nullcontext
|
| 3 |
|
| 4 |
+
from PIL import Image, ImageOps, ImageFilter
|
| 5 |
|
| 6 |
import gradio as gr
|
| 7 |
import torch, spaces
|
|
|
|
| 127 |
return s
|
| 128 |
return "white"
|
| 129 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
def enforce_qr_contrast(stylized: Image.Image, qr_img: Image.Image, strength: float = 0.0, feather: float = 1.0) -> Image.Image:
|
| 131 |
if strength <= 0: return stylized
|
| 132 |
q = qr_img.convert("L")
|
|
|
|
| 139 |
s = np.clip(s, 0.0, 1.0)
|
| 140 |
return Image.fromarray((s * 255.0).astype(np.uint8), mode="RGB")
|
| 141 |
|
| 142 |
+
def make_qr(url="https://example.com", size=768, border=12, back_color="#FFFFFF", blur_radius=0.0):
|
| 143 |
+
qr = qrcode.QRCode(version=None, error_correction=ERROR_CORRECT_H, box_size=10, border=int(border))
|
| 144 |
+
qr.add_data(url.strip()); qr.make(fit=True)
|
| 145 |
+
img = qr.make_image(fill_color="black", back_color=normalize_color(back_color)).convert("RGB")
|
| 146 |
+
img = img.resize((int(size), int(size)), Image.NEAREST)
|
| 147 |
+
if blur_radius and blur_radius > 0:
|
| 148 |
+
img = img.filter(ImageFilter.GaussianBlur(radius=float(blur_radius)))
|
| 149 |
+
return img
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
|
| 151 |
+
def _binarize(img_gray, thr=128):
|
| 152 |
+
return img_gray.point(lambda p: 255 if p >= thr else 0, mode="1").convert("L")
|
| 153 |
+
|
| 154 |
+
def _estimate_module_px(bin_img):
|
| 155 |
+
"""Rough module width estimate via run-lengths of black segments."""
|
| 156 |
+
a = np.array(bin_img, dtype=np.uint8) # 0..255
|
| 157 |
+
row = a[a.shape[0]//2] # mid row
|
| 158 |
+
# find lengths of consecutive black runs
|
| 159 |
+
runs = []
|
| 160 |
+
cnt, prev = 0, None
|
| 161 |
+
for v in row:
|
| 162 |
+
if prev is None:
|
| 163 |
+
prev, cnt = v, 1
|
| 164 |
+
continue
|
| 165 |
+
if v == prev:
|
| 166 |
+
cnt += 1
|
| 167 |
+
else:
|
| 168 |
+
if prev == 0: runs.append(cnt) # black run
|
| 169 |
+
prev, cnt = v, 1
|
| 170 |
+
if prev == 0: runs.append(cnt)
|
| 171 |
+
if not runs: return None
|
| 172 |
+
# use a robust small quantile (modules are among the shorter black runs)
|
| 173 |
+
return max(1, int(np.percentile(runs, 25)))
|
| 174 |
+
|
| 175 |
+
def _enforce_quiet_zone(bin_img, module_px, border_modules=4):
|
| 176 |
+
# pad so that white border >= border_modules * module_px
|
| 177 |
+
arr = np.array(bin_img)
|
| 178 |
+
ys, xs = np.where(arr == 0)
|
| 179 |
+
if len(xs) == 0:
|
| 180 |
+
return ImageOps.expand(bin_img, border=border_modules*module_px, fill=255)
|
| 181 |
+
left = xs.min()
|
| 182 |
+
right = arr.shape[1]-1 - xs.max()
|
| 183 |
+
top = ys.min()
|
| 184 |
+
bottom = arr.shape[0]-1 - ys.max()
|
| 185 |
+
need = border_modules*module_px
|
| 186 |
+
pad = (
|
| 187 |
+
max(0, need - left),
|
| 188 |
+
max(0, need - top),
|
| 189 |
+
max(0, need - right),
|
| 190 |
+
max(0, need - bottom),
|
| 191 |
+
)
|
| 192 |
+
return ImageOps.expand(bin_img, border=pad, fill=255)
|
| 193 |
+
|
| 194 |
+
def prep_qr_uploaded_for_controlnet(
|
| 195 |
+
qr_upload: Image.Image,
|
| 196 |
+
size: int = 768,
|
| 197 |
+
border_modules: int = 4,
|
| 198 |
+
rounded: bool = True, # set True when input is circle/rounded style
|
| 199 |
+
inflate_ratio: float = 0.9, # target dot fill ≈ 90% of cell
|
| 200 |
+
threshold: int = 128
|
| 201 |
+
) -> Image.Image:
|
| 202 |
+
# 1) flatten & grayscale
|
| 203 |
+
if qr_upload.mode in ("RGBA", "LA"):
|
| 204 |
+
bg = Image.new("RGBA", qr_upload.size, (255, 255, 255, 255))
|
| 205 |
+
qr_upload = Image.alpha_composite(bg, qr_upload.convert("RGBA"))
|
| 206 |
+
gray = qr_upload.convert("L")
|
| 207 |
+
|
| 208 |
+
# 2) hard binarize (kill anti-aliasing)
|
| 209 |
+
bin_img = _binarize(gray, thr=threshold)
|
| 210 |
+
|
| 211 |
+
# 3) estimate module size (px)
|
| 212 |
+
module_px = _estimate_module_px(bin_img)
|
| 213 |
+
if module_px is None:
|
| 214 |
+
module_px = max(2, size // 120) # safe fallback
|
| 215 |
+
|
| 216 |
+
# 4) for rounded QRs, inflate dots a bit (morphological dilation)
|
| 217 |
+
if rounded:
|
| 218 |
+
# choose a small kernel relative to module size (≈ 1/5 of module)
|
| 219 |
+
k = max(3, int(round(module_px * 0.2)) | 1) # odd kernel size
|
| 220 |
+
bin_img = bin_img.filter(ImageFilter.MaxFilter(k))
|
| 221 |
+
|
| 222 |
+
# 5) enforce quiet zone ≥ border_modules
|
| 223 |
+
bin_img = _enforce_quiet_zone(bin_img, module_px, border_modules)
|
| 224 |
+
|
| 225 |
+
# 6) align-to-grid scaling (try to keep integer pixels per module)
|
| 226 |
+
w, h = bin_img.size
|
| 227 |
+
# content width (without the padded quiet zone we just enforced)
|
| 228 |
+
arr = np.array(bin_img)
|
| 229 |
+
ys, xs = np.where(arr == 0)
|
| 230 |
+
if len(xs) > 0:
|
| 231 |
+
content_w = (xs.max() - xs.min() + 1)
|
| 232 |
+
else:
|
| 233 |
+
content_w = w
|
| 234 |
|
| 235 |
+
# estimated number of modules across content
|
| 236 |
+
n_est = max(21, int(round(content_w / max(1, module_px)))) # QR v1 min=21
|
| 237 |
+
scale = max(1, size // (n_est + 2*border_modules))
|
| 238 |
+
final_dim = (n_est + 2*border_modules) * scale
|
| 239 |
|
| 240 |
+
# resize with NEAREST to the computed dim, then center-pad to target size
|
| 241 |
+
img_scaled = bin_img.resize((final_dim, final_dim), Image.NEAREST)
|
| 242 |
+
pad_each = max(0, (size - final_dim) // 2)
|
| 243 |
+
img_out = ImageOps.expand(img_scaled, border=pad_each, fill=255)
|
| 244 |
+
if img_out.size != (size, size):
|
| 245 |
+
img_out = img_out.resize((size, size), Image.NEAREST) # rare off-by-one
|
| 246 |
|
| 247 |
+
# 7) return RGB (3 channels)
|
| 248 |
+
return img_out.convert("RGB")
|
| 249 |
|
| 250 |
# ----- Brightness map preprocessing & mixing -----
|
| 251 |
def prep_brightness_map(img: Image.Image, size: int, source: str,
|
|
|
|
| 376 |
# --- Build base-size control images (s x s)
|
| 377 |
# qr_img = make_qr(url=url, size=s, border=int(border), back_color="#FFFFFF", blur_radius=0.0)
|
| 378 |
if qr_upload is not None:
|
| 379 |
+
qr_img = prep_qr_uploaded_for_controlnet(qr_upload, s, border=int(border))
|
| 380 |
else:
|
| 381 |
qr_img = make_qr(url=url, size=s, border=int(border), back_color="#FFFFFF")
|
| 382 |
|
|
|
|
| 532 |
# make_qr(url=url, size=s, border=int(border), back_color="#FFFFFF", blur_radius=0.0)
|
| 533 |
# )
|
| 534 |
if qr_upload is not None:
|
| 535 |
+
qr_img = prep_qr_uploaded_for_controlnet(qr_upload, s, border=int(border))
|
| 536 |
else:
|
| 537 |
qr_img = make_qr(url=url, size=s, border=int(border), back_color="#FFFFFF")
|
| 538 |
|