Tanut commited on
Commit
67e5d0e
·
1 Parent(s): 3dc7a6e
Files changed (2) hide show
  1. .DS_Store +0 -0
  2. app.py +106 -31
.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 prep_qr_upload_image(qr_upload: Image.Image, size: int, strict: bool = False) -> Image.Image:
152
- # If QR has alpha transparency → flatten onto white
153
- if qr_upload.mode in ("RGBA", "LA") or "transparency" in qr_upload.info:
154
- rgba = qr_upload.convert("RGBA")
155
- # white background (instead of default black!)
156
- white_bg = Image.new("RGBA", rgba.size, (255, 255, 255, 255))
157
- im = Image.alpha_composite(white_bg, rgba).convert("RGB")
158
- else:
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 = prep_qr_upload_image(qr_upload, s, strict=False)
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 = prep_qr_upload_image(qr_upload, s, strict=False)
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