Oysiyl Claude Sonnet 4.5 commited on
Commit
08c63d3
·
1 Parent(s): d965bc8

Add animation feature and remove FreeU from standard pipeline

Browse files

- Add KSampler animation with intermediate frame preview every 5 steps
- Fix VAE latent scaling (0.18215) for proper preview image quality
- Remove FreeU from standard pipeline (kept in artistic only)
- Increase ZeroGPU duration from 120 to 150 seconds
- Update seed range to 2^32-1 for JSON compatibility
- Update UI descriptions for animation, color quantization, and upscale
- Set artistic pipeline defaults to Japanese temple example
- Add "Change Settings Manually" section descriptions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +364 -76
app.py CHANGED
@@ -1,5 +1,8 @@
1
  import os
2
  import sys
 
 
 
3
 
4
  # Force unbuffered output for real-time logging
5
  sys.stdout.reconfigure(line_buffering=True)
@@ -570,7 +573,7 @@ def compile_models_with_aoti():
570
  return False
571
 
572
 
573
- @spaces.GPU(duration=90) # Reduced from 720s - AOTI compilation speeds up inference
574
  def generate_qr_code_unified(
575
  prompt: str,
576
  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",
@@ -605,6 +608,7 @@ def generate_qr_code_unified(
605
  apply_gradient_filter: bool = False,
606
  gradient_strength: float = 0.3,
607
  variation_steps: int = 5,
 
608
  progress=gr.Progress(),
609
  ):
610
  # Only manipulate the text if it's a URL input type
@@ -616,7 +620,7 @@ def generate_qr_code_unified(
616
  qr_text = qr_text.replace("http://", "")
617
 
618
  # Use custom seed or random
619
- actual_seed = seed if use_custom_seed else random.randint(1, 2**64)
620
 
621
  with torch.inference_mode():
622
  if pipeline == "standard":
@@ -643,6 +647,7 @@ def generate_qr_code_unified(
643
  apply_gradient_filter=apply_gradient_filter,
644
  gradient_strength=gradient_strength,
645
  variation_steps=variation_steps,
 
646
  gr_progress=progress,
647
  )
648
  else: # artistic
@@ -676,10 +681,124 @@ def generate_qr_code_unified(
676
  apply_gradient_filter=apply_gradient_filter,
677
  gradient_strength=gradient_strength,
678
  variation_steps=variation_steps,
 
679
  gr_progress=progress,
680
  )
681
 
682
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
683
  def apply_color_quantization(
684
  image: Image.Image,
685
  colors: list[str],
@@ -882,7 +1001,7 @@ def generate_standard_qr(
882
  use_custom_seed: bool = False,
883
  seed: int = 0,
884
  enable_upscale: bool = False,
885
- enable_freeu: bool = False,
886
  controlnet_strength_standard_first: float = 0.45,
887
  controlnet_strength_standard_final: float = 1.0,
888
  enable_color_quantization: bool = False,
@@ -898,7 +1017,7 @@ def generate_standard_qr(
898
  ):
899
  """Wrapper function for standard QR generation"""
900
  # Get actual seed used (custom or random)
901
- actual_seed = seed if use_custom_seed else random.randint(1, 2**64)
902
 
903
  # Create settings JSON once
904
  settings_dict = {
@@ -915,7 +1034,7 @@ def generate_standard_qr(
915
  "seed": actual_seed,
916
  "use_custom_seed": True,
917
  "enable_upscale": enable_upscale,
918
- "enable_freeu": enable_freeu,
919
  "controlnet_strength_standard_first": controlnet_strength_standard_first,
920
  "controlnet_strength_standard_final": controlnet_strength_standard_final,
921
  "enable_color_quantization": enable_color_quantization,
@@ -945,6 +1064,7 @@ def generate_standard_qr(
945
  seed,
946
  pipeline="standard",
947
  enable_upscale=enable_upscale,
 
948
  controlnet_strength_standard_first=controlnet_strength_standard_first,
949
  controlnet_strength_standard_final=controlnet_strength_standard_final,
950
  enable_color_quantization=enable_color_quantization,
@@ -991,6 +1111,7 @@ def generate_artistic_qr(
991
  use_custom_seed: bool = False,
992
  seed: int = 0,
993
  enable_upscale: bool = True,
 
994
  enable_freeu: bool = True,
995
  freeu_b1: float = 1.4,
996
  freeu_b2: float = 1.3,
@@ -1014,7 +1135,7 @@ def generate_artistic_qr(
1014
  ):
1015
  """Wrapper function for artistic QR generation with FreeU and SAG parameters"""
1016
  # Get actual seed used (custom or random)
1017
- actual_seed = seed if use_custom_seed else random.randint(1, 2**64)
1018
 
1019
  # Create settings JSON once
1020
  settings_dict = {
@@ -1031,6 +1152,7 @@ def generate_artistic_qr(
1031
  "seed": actual_seed,
1032
  "use_custom_seed": True,
1033
  "enable_upscale": enable_upscale,
 
1034
  "enable_freeu": enable_freeu,
1035
  "freeu_b1": freeu_b1,
1036
  "freeu_b2": freeu_b2,
@@ -1086,6 +1208,7 @@ def generate_artistic_qr(
1086
  apply_gradient_filter=apply_gradient_filter,
1087
  gradient_strength=gradient_strength,
1088
  variation_steps=variation_steps,
 
1089
  progress=progress,
1090
  )
1091
 
@@ -1192,7 +1315,7 @@ def load_settings_from_json_standard(json_string: str):
1192
  use_custom_seed = params.get("use_custom_seed", True)
1193
  seed = params.get("seed", 718313)
1194
  enable_upscale = params.get("enable_upscale", False)
1195
- enable_freeu = params.get("enable_freeu", False)
1196
  controlnet_strength_standard_first = params.get(
1197
  "controlnet_strength_standard_first", 0.45
1198
  )
@@ -1223,7 +1346,7 @@ def load_settings_from_json_standard(json_string: str):
1223
  use_custom_seed,
1224
  seed,
1225
  enable_upscale,
1226
- enable_freeu,
1227
  controlnet_strength_standard_first,
1228
  controlnet_strength_standard_final,
1229
  enable_color_quantization,
@@ -1604,8 +1727,14 @@ def _pipeline_standard(
1604
  apply_gradient_filter: bool = False,
1605
  gradient_strength: float = 0.3,
1606
  variation_steps: int = 5,
 
1607
  gr_progress=None,
1608
  ):
 
 
 
 
 
1609
  emptylatentimage_5 = emptylatentimage.generate(
1610
  width=image_size, height=image_size, batch_size=1
1611
  )
@@ -1711,18 +1840,52 @@ def _pipeline_standard(
1711
  vae=get_value_at_index(checkpointloadersimple_4, 2),
1712
  )
1713
 
1714
- ksampler_3 = ksampler.sample(
1715
- seed=seed,
1716
- steps=20,
1717
- cfg=7,
1718
- sampler_name="dpmpp_2m",
1719
- scheduler="karras",
1720
- denoise=1,
1721
- model=get_value_at_index(checkpointloadersimple_4, 0),
1722
- positive=get_value_at_index(controlnetapplyadvanced_13, 0),
1723
- negative=get_value_at_index(controlnetapplyadvanced_13, 1),
1724
- latent_image=get_value_at_index(emptylatentimage_5, 0),
1725
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1726
 
1727
  # Yield progress update after first sampling completes
1728
  msg = "First pass sampling complete... decoding image"
@@ -1764,18 +1927,52 @@ def _pipeline_standard(
1764
  vae=get_value_at_index(checkpointloadersimple_4, 2),
1765
  )
1766
 
1767
- ksampler_18 = ksampler.sample(
1768
- seed=seed + 1,
1769
- steps=20,
1770
- cfg=7,
1771
- sampler_name="dpmpp_2m",
1772
- scheduler="karras",
1773
- denoise=1,
1774
- model=get_value_at_index(checkpointloadersimple_4, 0),
1775
- positive=get_value_at_index(controlnetapplyadvanced_20, 0),
1776
- negative=get_value_at_index(controlnetapplyadvanced_20, 1),
1777
- latent_image=get_value_at_index(emptylatentimage_17, 0),
1778
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1779
 
1780
  # Yield progress update after second sampling completes
1781
  msg = "Second pass sampling complete... decoding final image"
@@ -1903,8 +2100,14 @@ def _pipeline_artistic(
1903
  apply_gradient_filter: bool = False,
1904
  gradient_strength: float = 0.3,
1905
  variation_steps: int = 5,
 
1906
  gr_progress=None,
1907
  ):
 
 
 
 
 
1908
  # Generate QR code
1909
  qr_protocol = "None" if input_type == "Plain Text" else "Https"
1910
 
@@ -2060,18 +2263,52 @@ def _pipeline_artistic(
2060
  # First sampling pass
2061
  log_progress("First pass - artistic sampling...", gr_progress, 0.2)
2062
 
2063
- samples = ksampler.sample(
2064
- seed=seed,
2065
- steps=30,
2066
- cfg=7,
2067
- sampler_name="dpmpp_3m_sde",
2068
- scheduler="karras",
2069
- denoise=1,
2070
- model=enhanced_model, # Using FreeU + SAG enhanced model
2071
- positive=get_value_at_index(controlnet_apply, 0),
2072
- negative=get_value_at_index(controlnet_apply, 1),
2073
- latent_image=get_value_at_index(latent_image, 0),
2074
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2075
 
2076
  # Yield progress update after first sampling completes
2077
  msg = f"First pass sampling complete... decoding image (step {current_step}/{total_steps})"
@@ -2121,18 +2358,52 @@ def _pipeline_artistic(
2121
  # Final sampling pass
2122
  log_progress("Second pass (refinement)...", gr_progress, 0.6)
2123
 
2124
- final_samples = ksampler.sample(
2125
- seed=seed + 1,
2126
- steps=30,
2127
- cfg=7,
2128
- sampler_name="dpmpp_3m_sde",
2129
- scheduler="karras",
2130
- denoise=0.8,
2131
- model=enhanced_model, # Using FreeU + SAG enhanced model
2132
- positive=get_value_at_index(controlnet_apply_final, 0),
2133
- negative=get_value_at_index(controlnet_apply_final, 1),
2134
- latent_image=get_value_at_index(upscaled_latent, 0),
2135
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2136
 
2137
  # Yield progress update after second sampling completes
2138
  msg = f"Second pass sampling complete... decoding final image (step {current_step}/{total_steps})"
@@ -2244,6 +2515,9 @@ if __name__ == "__main__" and not os.environ.get("QR_TESTING_MODE"):
2244
  - Include style keywords like 'photorealistic', 'detailed', '8k'
2245
  - Choose **URL** mode for web links or **Plain Text** mode for VCARD, WiFi credentials, calendar events, etc.
2246
  - Try the examples below for inspiration
 
 
 
2247
  - **Copy/paste settings**: After generation, copy the JSON settings string that appears below the image and paste it into "Import Settings from JSON" to reproduce exact results or share with others
2248
 
2249
  ### Two Modes:
@@ -2274,13 +2548,13 @@ if __name__ == "__main__" and not os.environ.get("QR_TESTING_MODE"):
2274
  artistic_prompt_input = gr.Textbox(
2275
  label="Prompt",
2276
  placeholder="Describe the image you want to generate (check examples below for inspiration)",
2277
- value="Enter your prompt here... For example: 'a beautiful sunset over mountains, photorealistic, detailed landscape'",
2278
  lines=3,
2279
  )
2280
  artistic_text_input = gr.Textbox(
2281
  label="QR Code Content",
2282
  placeholder="Enter URL or plain text",
2283
- value="Enter your URL or text here... For example: https://github.com",
2284
  lines=3,
2285
  )
2286
 
@@ -2310,6 +2584,7 @@ if __name__ == "__main__" and not os.environ.get("QR_TESTING_MODE"):
2310
 
2311
  # Change Settings Manually - separate accordion
2312
  with gr.Accordion("Change Settings Manually", open=False):
 
2313
  # Negative Prompt
2314
  negative_prompt_artistic = gr.Textbox(
2315
  label="Negative Prompt",
@@ -2324,9 +2599,9 @@ if __name__ == "__main__" and not os.environ.get("QR_TESTING_MODE"):
2324
  minimum=512,
2325
  maximum=1024,
2326
  step=64,
2327
- value=704,
2328
  label="Image Size",
2329
- info="Base size of the generated image. Final output will be 2x this size (e.g., 7041408) due to the two-step enhancement process. Higher values use more VRAM and take longer to process.",
2330
  )
2331
 
2332
  # Add border size slider for artistic QR
@@ -2347,7 +2622,7 @@ if __name__ == "__main__" and not os.environ.get("QR_TESTING_MODE"):
2347
  "Quartile (25%)",
2348
  "High (30%)",
2349
  ],
2350
- value="High (30%)",
2351
  label="Error Correction Level",
2352
  info="Higher error correction makes the QR code more scannable when damaged or obscured, but increases its size and complexity. High (30%) is recommended for artistic QR codes.",
2353
  )
@@ -2357,16 +2632,16 @@ if __name__ == "__main__" and not os.environ.get("QR_TESTING_MODE"):
2357
  minimum=4,
2358
  maximum=16,
2359
  step=1,
2360
- value=16,
2361
  label="QR Module Size",
2362
- info="Pixel width of the smallest QR code unit. Larger values improve readability but require a larger image size. 16 is a good starting point.",
2363
  )
2364
 
2365
  # Add module drawer dropdown with style examples for artistic QR
2366
  artistic_module_drawer = gr.Dropdown(
2367
  choices=[
2368
  "Square",
2369
- "Gapped Square",
2370
  "Circle",
2371
  "Rounded",
2372
  "Vertical bars",
@@ -2441,8 +2716,16 @@ if __name__ == "__main__" and not os.environ.get("QR_TESTING_MODE"):
2441
  info="Enable upscaling with RealESRGAN for higher quality output (enabled by default for artistic pipeline)",
2442
  )
2443
 
 
 
 
 
 
 
 
2444
  # Color Quantization Section
2445
  gr.Markdown("### Color Quantization (Optional)")
 
2446
  artistic_enable_color_quantization = gr.Checkbox(
2447
  label="Enable Color Quantization",
2448
  value=False,
@@ -2556,7 +2839,7 @@ if __name__ == "__main__" and not os.environ.get("QR_TESTING_MODE"):
2556
  )
2557
  artistic_seed = gr.Slider(
2558
  minimum=0,
2559
- maximum=2000000,
2560
  step=1,
2561
  value=718313,
2562
  label="Seed",
@@ -2694,6 +2977,7 @@ if __name__ == "__main__" and not os.environ.get("QR_TESTING_MODE"):
2694
  artistic_use_custom_seed,
2695
  artistic_seed,
2696
  artistic_enable_upscale,
 
2697
  enable_freeu_artistic,
2698
  freeu_b1,
2699
  freeu_b2,
@@ -2739,6 +3023,7 @@ if __name__ == "__main__" and not os.environ.get("QR_TESTING_MODE"):
2739
  artistic_use_custom_seed,
2740
  artistic_seed,
2741
  artistic_enable_upscale,
 
2742
  enable_freeu_artistic,
2743
  freeu_b1,
2744
  freeu_b2,
@@ -3193,13 +3478,13 @@ if __name__ == "__main__" and not os.environ.get("QR_TESTING_MODE"):
3193
  prompt_input = gr.Textbox(
3194
  label="Prompt",
3195
  placeholder="Describe the image you want to generate (check examples below for inspiration)",
3196
- value="Enter your prompt here... For example: 'a beautiful sunset over mountains, photorealistic, detailed landscape'",
3197
  lines=3,
3198
  )
3199
  text_input = gr.Textbox(
3200
  label="QR Code Content",
3201
  placeholder="Enter URL or plain text",
3202
- value="Enter your URL or text here... For example: https://github.com",
3203
  lines=3,
3204
  )
3205
 
@@ -3229,6 +3514,7 @@ if __name__ == "__main__" and not os.environ.get("QR_TESTING_MODE"):
3229
 
3230
  # Change Settings Manually - separate accordion
3231
  with gr.Accordion("Change Settings Manually", open=False):
 
3232
  # Negative Prompt
3233
  negative_prompt_standard = gr.Textbox(
3234
  label="Negative Prompt",
@@ -3285,7 +3571,7 @@ if __name__ == "__main__" and not os.environ.get("QR_TESTING_MODE"):
3285
  module_drawer = gr.Dropdown(
3286
  choices=[
3287
  "Square",
3288
- "Gapped Square",
3289
  "Circle",
3290
  "Rounded",
3291
  "Vertical bars",
@@ -3360,15 +3646,16 @@ if __name__ == "__main__" and not os.environ.get("QR_TESTING_MODE"):
3360
  info="Enable upscaling with RealESRGAN for higher quality output (disabled by default for standard pipeline)",
3361
  )
3362
 
3363
- # Add FreeU checkbox
3364
- enable_freeu_standard = gr.Checkbox(
3365
- label="Enable FreeU",
3366
- value=False,
3367
- info="Enable FreeU quality enhancement (disabled by default for standard pipeline)",
3368
  )
3369
 
3370
  # Color Quantization Section
3371
  gr.Markdown("### Color Quantization (Optional)")
 
3372
  enable_color_quantization = gr.Checkbox(
3373
  label="Enable Color Quantization",
3374
  value=False,
@@ -3478,7 +3765,7 @@ if __name__ == "__main__" and not os.environ.get("QR_TESTING_MODE"):
3478
  )
3479
  seed = gr.Slider(
3480
  minimum=0,
3481
- maximum=2000000,
3482
  step=1,
3483
  value=718313,
3484
  label="Seed",
@@ -3550,7 +3837,7 @@ if __name__ == "__main__" and not os.environ.get("QR_TESTING_MODE"):
3550
  use_custom_seed,
3551
  seed,
3552
  enable_upscale,
3553
- enable_freeu_standard,
3554
  controlnet_strength_standard_first,
3555
  controlnet_strength_standard_final,
3556
  enable_color_quantization,
@@ -3569,6 +3856,7 @@ if __name__ == "__main__" and not os.environ.get("QR_TESTING_MODE"):
3569
  settings_output_standard,
3570
  settings_accordion_standard,
3571
  ],
 
3572
  )
3573
 
3574
  # Load Settings button event handler
@@ -3588,7 +3876,7 @@ if __name__ == "__main__" and not os.environ.get("QR_TESTING_MODE"):
3588
  use_custom_seed,
3589
  seed,
3590
  enable_upscale,
3591
- enable_freeu_standard,
3592
  controlnet_strength_standard_first,
3593
  controlnet_strength_standard_final,
3594
  enable_color_quantization,
 
1
  import os
2
  import sys
3
+ import time
4
+ import threading
5
+ import queue
6
 
7
  # Force unbuffered output for real-time logging
8
  sys.stdout.reconfigure(line_buffering=True)
 
573
  return False
574
 
575
 
576
+ @spaces.GPU(duration=150) # Allows time for animation frames during generation
577
  def generate_qr_code_unified(
578
  prompt: str,
579
  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",
 
608
  apply_gradient_filter: bool = False,
609
  gradient_strength: float = 0.3,
610
  variation_steps: int = 5,
611
+ enable_animation: bool = True,
612
  progress=gr.Progress(),
613
  ):
614
  # Only manipulate the text if it's a URL input type
 
620
  qr_text = qr_text.replace("http://", "")
621
 
622
  # Use custom seed or random
623
+ actual_seed = seed if use_custom_seed else random.randint(1, 2**32 - 1)
624
 
625
  with torch.inference_mode():
626
  if pipeline == "standard":
 
647
  apply_gradient_filter=apply_gradient_filter,
648
  gradient_strength=gradient_strength,
649
  variation_steps=variation_steps,
650
+ enable_animation=enable_animation,
651
  gr_progress=progress,
652
  )
653
  else: # artistic
 
681
  apply_gradient_filter=apply_gradient_filter,
682
  gradient_strength=gradient_strength,
683
  variation_steps=variation_steps,
684
+ enable_animation=enable_animation,
685
  gr_progress=progress,
686
  )
687
 
688
 
689
+ class AnimationHandler:
690
+ """Handler for managing KSampler animation callbacks"""
691
+ def __init__(self, preview_size=512):
692
+ self.intermediate_images = []
693
+ self.image_queue = queue.Queue()
694
+ self.enabled = False
695
+ self.preview_size = preview_size # Consistent preview size for all intermediate images
696
+
697
+ def create_callback(self, vae, interval=5):
698
+ """Create a callback that stores intermediate decoded images"""
699
+ last_step = [0]
700
+
701
+ def callback(step, x0, x, total_steps):
702
+ if not self.enabled:
703
+ return
704
+
705
+ # Only decode every 'interval' steps, but skip the very last step to avoid contaminating main pipeline
706
+ if (step - last_step[0]) >= interval and step < total_steps:
707
+ last_step[0] = step
708
+ try:
709
+ # Use torch.no_grad() instead of inference_mode to avoid tensor contamination
710
+ # Key insight: inference_mode tensors cannot be used in backward pass
711
+ # Source: https://pytorch.org/docs/stable/generated/torch.autograd.grad_mode.inference_mode.html
712
+ import torch
713
+ with torch.no_grad():
714
+ # Create a detached clone and ensure contiguous memory layout
715
+ # .contiguous() ensures proper memory layout for VAE decoder
716
+ x0_copy = x0.detach().clone().contiguous()
717
+
718
+ # CRITICAL: Scale the latent for VAE decoding
719
+ # SD1.5 uses scale_factor = 0.18215, so we divide by it
720
+ # This converts from model sampling space to VAE decoding space
721
+ x0_scaled = x0_copy / 0.18215
722
+
723
+ # Decode - create full latent dict like KSampler output
724
+ latent_dict = {"samples": x0_scaled}
725
+ decoded = vaedecode.decode(samples=latent_dict, vae=vae)
726
+ image_tensor = get_value_at_index(decoded, 0)
727
+
728
+ # Convert EXACTLY like final images (lines 1915-1918) - no transpose, no mode
729
+ image_np = (image_tensor.detach().cpu().numpy() * 255).astype(np.uint8)
730
+ image_np = image_np[0]
731
+ pil_image = Image.fromarray(image_np)
732
+
733
+ # Resize to consistent preview size to avoid size inconsistencies in UI
734
+ if pil_image.size[0] > self.preview_size or pil_image.size[1] > self.preview_size:
735
+ pil_image.thumbnail((self.preview_size, self.preview_size), Image.LANCZOS)
736
+
737
+ # Store with message (step is already the correct value at interval points)
738
+ msg = f"Sampling progress: step {step}/{total_steps}"
739
+ self.intermediate_images.append((pil_image, msg))
740
+ self.image_queue.put((pil_image, msg))
741
+ except Exception as e:
742
+ print(f"Animation decode error: {e}")
743
+
744
+ return callback
745
+
746
+ def get_and_clear_images(self):
747
+ """Get all intermediate images and clear the buffer"""
748
+ images = self.intermediate_images.copy()
749
+ self.intermediate_images.clear()
750
+ return images
751
+
752
+
753
+ def ksampler_with_animation(model, seed, steps, cfg, sampler_name, scheduler,
754
+ positive, negative, latent_image, denoise=1.0,
755
+ animation_handler=None, vae=None):
756
+ """
757
+ Custom KSampler that supports animation callbacks.
758
+ Based on ComfyUI's common_ksampler but with animation support.
759
+ """
760
+ import comfy.sample
761
+ import comfy.utils
762
+
763
+ # Prepare noise
764
+ latent = latent_image
765
+ latent_image_data = latent["samples"]
766
+ latent_image_data = comfy.sample.fix_empty_latent_channels(model, latent_image_data)
767
+
768
+ batch_inds = latent["batch_index"] if "batch_index" in latent else None
769
+ noise = comfy.sample.prepare_noise(latent_image_data, seed, batch_inds)
770
+
771
+ noise_mask = None
772
+ if "noise_mask" in latent:
773
+ noise_mask = latent["noise_mask"]
774
+
775
+ # Create animation callback once before sampling (not on every step!)
776
+ callback_fn = None
777
+ if animation_handler and animation_handler.enabled and vae:
778
+ callback_fn = animation_handler.create_callback(vae)
779
+
780
+ def animation_callback(step, x0, x, total_steps):
781
+ # Call animation callback if enabled
782
+ if callback_fn:
783
+ callback_fn(step, x0, x, total_steps)
784
+
785
+ disable_pbar = not comfy.utils.PROGRESS_BAR_ENABLED
786
+
787
+ # Sample
788
+ samples = comfy.sample.sample(
789
+ model, noise, steps, cfg, sampler_name, scheduler,
790
+ positive, negative, latent_image_data,
791
+ denoise=denoise, disable_noise=False, start_step=None,
792
+ last_step=None, force_full_denoise=False,
793
+ noise_mask=noise_mask, callback=animation_callback,
794
+ disable_pbar=disable_pbar, seed=seed
795
+ )
796
+
797
+ out = latent.copy()
798
+ out["samples"] = samples
799
+ return (out,)
800
+
801
+
802
  def apply_color_quantization(
803
  image: Image.Image,
804
  colors: list[str],
 
1001
  use_custom_seed: bool = False,
1002
  seed: int = 0,
1003
  enable_upscale: bool = False,
1004
+ enable_animation: bool = True,
1005
  controlnet_strength_standard_first: float = 0.45,
1006
  controlnet_strength_standard_final: float = 1.0,
1007
  enable_color_quantization: bool = False,
 
1017
  ):
1018
  """Wrapper function for standard QR generation"""
1019
  # Get actual seed used (custom or random)
1020
+ actual_seed = seed if use_custom_seed else random.randint(1, 2**32 - 1)
1021
 
1022
  # Create settings JSON once
1023
  settings_dict = {
 
1034
  "seed": actual_seed,
1035
  "use_custom_seed": True,
1036
  "enable_upscale": enable_upscale,
1037
+ "enable_animation": enable_animation,
1038
  "controlnet_strength_standard_first": controlnet_strength_standard_first,
1039
  "controlnet_strength_standard_final": controlnet_strength_standard_final,
1040
  "enable_color_quantization": enable_color_quantization,
 
1064
  seed,
1065
  pipeline="standard",
1066
  enable_upscale=enable_upscale,
1067
+ enable_animation=enable_animation,
1068
  controlnet_strength_standard_first=controlnet_strength_standard_first,
1069
  controlnet_strength_standard_final=controlnet_strength_standard_final,
1070
  enable_color_quantization=enable_color_quantization,
 
1111
  use_custom_seed: bool = False,
1112
  seed: int = 0,
1113
  enable_upscale: bool = True,
1114
+ enable_animation: bool = True,
1115
  enable_freeu: bool = True,
1116
  freeu_b1: float = 1.4,
1117
  freeu_b2: float = 1.3,
 
1135
  ):
1136
  """Wrapper function for artistic QR generation with FreeU and SAG parameters"""
1137
  # Get actual seed used (custom or random)
1138
+ actual_seed = seed if use_custom_seed else random.randint(1, 2**32 - 1)
1139
 
1140
  # Create settings JSON once
1141
  settings_dict = {
 
1152
  "seed": actual_seed,
1153
  "use_custom_seed": True,
1154
  "enable_upscale": enable_upscale,
1155
+ "enable_animation": enable_animation,
1156
  "enable_freeu": enable_freeu,
1157
  "freeu_b1": freeu_b1,
1158
  "freeu_b2": freeu_b2,
 
1208
  apply_gradient_filter=apply_gradient_filter,
1209
  gradient_strength=gradient_strength,
1210
  variation_steps=variation_steps,
1211
+ enable_animation=enable_animation,
1212
  progress=progress,
1213
  )
1214
 
 
1315
  use_custom_seed = params.get("use_custom_seed", True)
1316
  seed = params.get("seed", 718313)
1317
  enable_upscale = params.get("enable_upscale", False)
1318
+ enable_animation = params.get("enable_animation", True)
1319
  controlnet_strength_standard_first = params.get(
1320
  "controlnet_strength_standard_first", 0.45
1321
  )
 
1346
  use_custom_seed,
1347
  seed,
1348
  enable_upscale,
1349
+ enable_animation,
1350
  controlnet_strength_standard_first,
1351
  controlnet_strength_standard_final,
1352
  enable_color_quantization,
 
1727
  apply_gradient_filter: bool = False,
1728
  gradient_strength: float = 0.3,
1729
  variation_steps: int = 5,
1730
+ enable_animation: bool = True,
1731
  gr_progress=None,
1732
  ):
1733
+ # Initialize animation handler if enabled
1734
+ animation_handler = AnimationHandler(preview_size=image_size) if enable_animation else None
1735
+ if animation_handler:
1736
+ animation_handler.enabled = True
1737
+
1738
  emptylatentimage_5 = emptylatentimage.generate(
1739
  width=image_size, height=image_size, batch_size=1
1740
  )
 
1840
  vae=get_value_at_index(checkpointloadersimple_4, 2),
1841
  )
1842
 
1843
+ # Use animation-enabled sampler if requested
1844
+ if animation_handler and enable_animation:
1845
+ # Run ksampler in thread to allow real-time image yielding
1846
+ result_container = [None]
1847
+ def run_ksampler():
1848
+ result_container[0] = ksampler_with_animation(
1849
+ model=get_value_at_index(checkpointloadersimple_4, 0),
1850
+ seed=seed,
1851
+ steps=20,
1852
+ cfg=7,
1853
+ sampler_name="dpmpp_2m",
1854
+ scheduler="karras",
1855
+ positive=get_value_at_index(controlnetapplyadvanced_13, 0),
1856
+ negative=get_value_at_index(controlnetapplyadvanced_13, 1),
1857
+ latent_image=get_value_at_index(emptylatentimage_5, 0),
1858
+ denoise=1,
1859
+ animation_handler=animation_handler,
1860
+ vae=get_value_at_index(checkpointloadersimple_4, 2),
1861
+ )
1862
+
1863
+ ksampler_thread = threading.Thread(target=run_ksampler)
1864
+ ksampler_thread.start()
1865
+
1866
+ # Yield intermediate images as they're captured
1867
+ while ksampler_thread.is_alive() or not animation_handler.image_queue.empty():
1868
+ try:
1869
+ img, msg = animation_handler.image_queue.get(timeout=0.1)
1870
+ yield img, msg
1871
+ except queue.Empty:
1872
+ pass
1873
+
1874
+ ksampler_thread.join()
1875
+ ksampler_3 = result_container[0]
1876
+ else:
1877
+ ksampler_3 = ksampler.sample(
1878
+ seed=seed,
1879
+ steps=20,
1880
+ cfg=7,
1881
+ sampler_name="dpmpp_2m",
1882
+ scheduler="karras",
1883
+ denoise=1,
1884
+ model=get_value_at_index(checkpointloadersimple_4, 0),
1885
+ positive=get_value_at_index(controlnetapplyadvanced_13, 0),
1886
+ negative=get_value_at_index(controlnetapplyadvanced_13, 1),
1887
+ latent_image=get_value_at_index(emptylatentimage_5, 0),
1888
+ )
1889
 
1890
  # Yield progress update after first sampling completes
1891
  msg = "First pass sampling complete... decoding image"
 
1927
  vae=get_value_at_index(checkpointloadersimple_4, 2),
1928
  )
1929
 
1930
+ # Use animation-enabled sampler if requested
1931
+ if animation_handler and enable_animation:
1932
+ # Run ksampler in thread to allow real-time image yielding
1933
+ result_container = [None]
1934
+ def run_ksampler():
1935
+ result_container[0] = ksampler_with_animation(
1936
+ model=get_value_at_index(checkpointloadersimple_4, 0),
1937
+ seed=seed + 1,
1938
+ steps=20,
1939
+ cfg=7,
1940
+ sampler_name="dpmpp_2m",
1941
+ scheduler="karras",
1942
+ positive=get_value_at_index(controlnetapplyadvanced_20, 0),
1943
+ negative=get_value_at_index(controlnetapplyadvanced_20, 1),
1944
+ latent_image=get_value_at_index(emptylatentimage_17, 0),
1945
+ denoise=1,
1946
+ animation_handler=animation_handler,
1947
+ vae=get_value_at_index(checkpointloadersimple_4, 2),
1948
+ )
1949
+
1950
+ ksampler_thread = threading.Thread(target=run_ksampler)
1951
+ ksampler_thread.start()
1952
+
1953
+ # Yield intermediate images as they're captured
1954
+ while ksampler_thread.is_alive() or not animation_handler.image_queue.empty():
1955
+ try:
1956
+ img, msg = animation_handler.image_queue.get(timeout=0.1)
1957
+ yield img, msg
1958
+ except queue.Empty:
1959
+ pass
1960
+
1961
+ ksampler_thread.join()
1962
+ ksampler_18 = result_container[0]
1963
+ else:
1964
+ ksampler_18 = ksampler.sample(
1965
+ seed=seed + 1,
1966
+ steps=20,
1967
+ cfg=7,
1968
+ sampler_name="dpmpp_2m",
1969
+ scheduler="karras",
1970
+ denoise=1,
1971
+ model=get_value_at_index(checkpointloadersimple_4, 0),
1972
+ positive=get_value_at_index(controlnetapplyadvanced_20, 0),
1973
+ negative=get_value_at_index(controlnetapplyadvanced_20, 1),
1974
+ latent_image=get_value_at_index(emptylatentimage_17, 0),
1975
+ )
1976
 
1977
  # Yield progress update after second sampling completes
1978
  msg = "Second pass sampling complete... decoding final image"
 
2100
  apply_gradient_filter: bool = False,
2101
  gradient_strength: float = 0.3,
2102
  variation_steps: int = 5,
2103
+ enable_animation: bool = True,
2104
  gr_progress=None,
2105
  ):
2106
+ # Initialize animation handler if enabled
2107
+ animation_handler = AnimationHandler(preview_size=image_size) if enable_animation else None
2108
+ if animation_handler:
2109
+ animation_handler.enabled = True
2110
+
2111
  # Generate QR code
2112
  qr_protocol = "None" if input_type == "Plain Text" else "Https"
2113
 
 
2263
  # First sampling pass
2264
  log_progress("First pass - artistic sampling...", gr_progress, 0.2)
2265
 
2266
+ # Use animation-enabled sampler if requested
2267
+ if animation_handler and enable_animation:
2268
+ # Run ksampler in thread to allow real-time image yielding
2269
+ result_container = [None]
2270
+ def run_ksampler():
2271
+ result_container[0] = ksampler_with_animation(
2272
+ model=enhanced_model, # Using FreeU + SAG enhanced model
2273
+ seed=seed,
2274
+ steps=30,
2275
+ cfg=7,
2276
+ sampler_name="dpmpp_3m_sde",
2277
+ scheduler="karras",
2278
+ positive=get_value_at_index(controlnet_apply, 0),
2279
+ negative=get_value_at_index(controlnet_apply, 1),
2280
+ latent_image=get_value_at_index(latent_image, 0),
2281
+ denoise=1,
2282
+ animation_handler=animation_handler,
2283
+ vae=get_value_at_index(checkpointloadersimple_artistic, 2),
2284
+ )
2285
+
2286
+ ksampler_thread = threading.Thread(target=run_ksampler)
2287
+ ksampler_thread.start()
2288
+
2289
+ # Yield intermediate images as they're captured
2290
+ while ksampler_thread.is_alive() or not animation_handler.image_queue.empty():
2291
+ try:
2292
+ img, msg = animation_handler.image_queue.get(timeout=0.1)
2293
+ yield (img, msg)
2294
+ except queue.Empty:
2295
+ pass
2296
+
2297
+ ksampler_thread.join()
2298
+ samples = result_container[0]
2299
+ else:
2300
+ samples = ksampler.sample(
2301
+ seed=seed,
2302
+ steps=30,
2303
+ cfg=7,
2304
+ sampler_name="dpmpp_3m_sde",
2305
+ scheduler="karras",
2306
+ denoise=1,
2307
+ model=enhanced_model, # Using FreeU + SAG enhanced model
2308
+ positive=get_value_at_index(controlnet_apply, 0),
2309
+ negative=get_value_at_index(controlnet_apply, 1),
2310
+ latent_image=get_value_at_index(latent_image, 0),
2311
+ )
2312
 
2313
  # Yield progress update after first sampling completes
2314
  msg = f"First pass sampling complete... decoding image (step {current_step}/{total_steps})"
 
2358
  # Final sampling pass
2359
  log_progress("Second pass (refinement)...", gr_progress, 0.6)
2360
 
2361
+ # Use animation-enabled sampler if requested
2362
+ if animation_handler and enable_animation:
2363
+ # Run ksampler in thread to allow real-time image yielding
2364
+ result_container = [None]
2365
+ def run_ksampler():
2366
+ result_container[0] = ksampler_with_animation(
2367
+ model=enhanced_model, # Using FreeU + SAG enhanced model
2368
+ seed=seed + 1,
2369
+ steps=30,
2370
+ cfg=7,
2371
+ sampler_name="dpmpp_3m_sde",
2372
+ scheduler="karras",
2373
+ positive=get_value_at_index(controlnet_apply_final, 0),
2374
+ negative=get_value_at_index(controlnet_apply_final, 1),
2375
+ latent_image=get_value_at_index(upscaled_latent, 0),
2376
+ denoise=0.8,
2377
+ animation_handler=animation_handler,
2378
+ vae=get_value_at_index(checkpointloadersimple_artistic, 2),
2379
+ )
2380
+
2381
+ ksampler_thread = threading.Thread(target=run_ksampler)
2382
+ ksampler_thread.start()
2383
+
2384
+ # Yield intermediate images as they're captured
2385
+ while ksampler_thread.is_alive() or not animation_handler.image_queue.empty():
2386
+ try:
2387
+ img, msg = animation_handler.image_queue.get(timeout=0.1)
2388
+ yield (img, msg)
2389
+ except queue.Empty:
2390
+ pass
2391
+
2392
+ ksampler_thread.join()
2393
+ final_samples = result_container[0]
2394
+ else:
2395
+ final_samples = ksampler.sample(
2396
+ seed=seed + 1,
2397
+ steps=30,
2398
+ cfg=7,
2399
+ sampler_name="dpmpp_3m_sde",
2400
+ scheduler="karras",
2401
+ denoise=0.8,
2402
+ model=enhanced_model, # Using FreeU + SAG enhanced model
2403
+ positive=get_value_at_index(controlnet_apply_final, 0),
2404
+ negative=get_value_at_index(controlnet_apply_final, 1),
2405
+ latent_image=get_value_at_index(upscaled_latent, 0),
2406
+ )
2407
 
2408
  # Yield progress update after second sampling completes
2409
  msg = f"Second pass sampling complete... decoding final image (step {current_step}/{total_steps})"
 
2515
  - Include style keywords like 'photorealistic', 'detailed', '8k'
2516
  - Choose **URL** mode for web links or **Plain Text** mode for VCARD, WiFi credentials, calendar events, etc.
2517
  - Try the examples below for inspiration
2518
+ - **Animation** (enabled by default): Shows intermediate generation steps every 5 steps. Disable for faster generation. (Available in "Change Settings Manually")
2519
+ - **Color Quantization** (disabled by default): Optional feature to specify a custom color scheme (2-4 colors) for your QR code. Perfect for matching brand colors or creating themed designs with gradient variations. (Available in "Change Settings Manually")
2520
+ - **Upscale Image**: Enhances output quality with RealESRGAN (enabled by default in Artistic, disabled in Standard). (Available in "Change Settings Manually")
2521
  - **Copy/paste settings**: After generation, copy the JSON settings string that appears below the image and paste it into "Import Settings from JSON" to reproduce exact results or share with others
2522
 
2523
  ### Two Modes:
 
2548
  artistic_prompt_input = gr.Textbox(
2549
  label="Prompt",
2550
  placeholder="Describe the image you want to generate (check examples below for inspiration)",
2551
+ value="some clothes spread on ropes, Japanese girl sits inside in the middle of the image, few sakura flowers, realistic, great details, out in the open air sunny day realistic, great details, absence of people, Detailed and Intricate, CGI, Photoshoot, rim light, 8k, 16k, ultra detail",
2552
  lines=3,
2553
  )
2554
  artistic_text_input = gr.Textbox(
2555
  label="QR Code Content",
2556
  placeholder="Enter URL or plain text",
2557
+ value="https://www.google.com",
2558
  lines=3,
2559
  )
2560
 
 
2584
 
2585
  # Change Settings Manually - separate accordion
2586
  with gr.Accordion("Change Settings Manually", open=False):
2587
+ gr.Markdown("**Advanced controls including:** Animation toggle, Color Quantization, FreeU/SAG parameters, ControlNet strength, QR settings, and more.")
2588
  # Negative Prompt
2589
  negative_prompt_artistic = gr.Textbox(
2590
  label="Negative Prompt",
 
2599
  minimum=512,
2600
  maximum=1024,
2601
  step=64,
2602
+ value=640,
2603
  label="Image Size",
2604
+ info="Base size of the generated image. Final output will be 2x this size (e.g., 6401280) due to the two-step enhancement process. Higher values use more VRAM and take longer to process.",
2605
  )
2606
 
2607
  # Add border size slider for artistic QR
 
2622
  "Quartile (25%)",
2623
  "High (30%)",
2624
  ],
2625
+ value="Medium (15%)",
2626
  label="Error Correction Level",
2627
  info="Higher error correction makes the QR code more scannable when damaged or obscured, but increases its size and complexity. High (30%) is recommended for artistic QR codes.",
2628
  )
 
2632
  minimum=4,
2633
  maximum=16,
2634
  step=1,
2635
+ value=14,
2636
  label="QR Module Size",
2637
+ info="Pixel width of the smallest QR code unit. Larger values improve readability but require a larger image size. 14 is a good starting point.",
2638
  )
2639
 
2640
  # Add module drawer dropdown with style examples for artistic QR
2641
  artistic_module_drawer = gr.Dropdown(
2642
  choices=[
2643
  "Square",
2644
+ "Gapped square",
2645
  "Circle",
2646
  "Rounded",
2647
  "Vertical bars",
 
2716
  info="Enable upscaling with RealESRGAN for higher quality output (enabled by default for artistic pipeline)",
2717
  )
2718
 
2719
+ # Animation toggle
2720
+ artistic_enable_animation = gr.Checkbox(
2721
+ label="Enable Animation (Show KSampler Progress)",
2722
+ value=True,
2723
+ info="Shows intermediate images every 5 steps during generation. Disable for faster generation.",
2724
+ )
2725
+
2726
  # Color Quantization Section
2727
  gr.Markdown("### Color Quantization (Optional)")
2728
+ gr.Markdown("Use this option to specify a custom color scheme for your QR code. Perfect for matching brand colors or creating themed designs.")
2729
  artistic_enable_color_quantization = gr.Checkbox(
2730
  label="Enable Color Quantization",
2731
  value=False,
 
2839
  )
2840
  artistic_seed = gr.Slider(
2841
  minimum=0,
2842
+ maximum=2**32 - 1,
2843
  step=1,
2844
  value=718313,
2845
  label="Seed",
 
2977
  artistic_use_custom_seed,
2978
  artistic_seed,
2979
  artistic_enable_upscale,
2980
+ artistic_enable_animation,
2981
  enable_freeu_artistic,
2982
  freeu_b1,
2983
  freeu_b2,
 
3023
  artistic_use_custom_seed,
3024
  artistic_seed,
3025
  artistic_enable_upscale,
3026
+ artistic_enable_animation,
3027
  enable_freeu_artistic,
3028
  freeu_b1,
3029
  freeu_b2,
 
3478
  prompt_input = gr.Textbox(
3479
  label="Prompt",
3480
  placeholder="Describe the image you want to generate (check examples below for inspiration)",
3481
+ value="some clothes spread on ropes, realistic, great details, out in the open air sunny day realistic, great details,absence of people, Detailed and Intricate, CGI, Photoshoot,rim light, 8k, 16k, ultra detail",
3482
  lines=3,
3483
  )
3484
  text_input = gr.Textbox(
3485
  label="QR Code Content",
3486
  placeholder="Enter URL or plain text",
3487
+ value="https://www.google.com",
3488
  lines=3,
3489
  )
3490
 
 
3514
 
3515
  # Change Settings Manually - separate accordion
3516
  with gr.Accordion("Change Settings Manually", open=False):
3517
+ gr.Markdown("**Advanced controls including:** Animation toggle, Color Quantization, ControlNet strength, QR settings, and more.")
3518
  # Negative Prompt
3519
  negative_prompt_standard = gr.Textbox(
3520
  label="Negative Prompt",
 
3571
  module_drawer = gr.Dropdown(
3572
  choices=[
3573
  "Square",
3574
+ "Gapped square",
3575
  "Circle",
3576
  "Rounded",
3577
  "Vertical bars",
 
3646
  info="Enable upscaling with RealESRGAN for higher quality output (disabled by default for standard pipeline)",
3647
  )
3648
 
3649
+ # Animation toggle
3650
+ enable_animation = gr.Checkbox(
3651
+ label="Enable Animation (Show KSampler Progress)",
3652
+ value=True,
3653
+ info="Shows intermediate images every 5 steps during generation. Disable for faster generation.",
3654
  )
3655
 
3656
  # Color Quantization Section
3657
  gr.Markdown("### Color Quantization (Optional)")
3658
+ gr.Markdown("Use this option to specify a custom color scheme for your QR code. Perfect for matching brand colors or creating themed designs.")
3659
  enable_color_quantization = gr.Checkbox(
3660
  label="Enable Color Quantization",
3661
  value=False,
 
3765
  )
3766
  seed = gr.Slider(
3767
  minimum=0,
3768
+ maximum=2**32 - 1,
3769
  step=1,
3770
  value=718313,
3771
  label="Seed",
 
3837
  use_custom_seed,
3838
  seed,
3839
  enable_upscale,
3840
+ enable_animation,
3841
  controlnet_strength_standard_first,
3842
  controlnet_strength_standard_final,
3843
  enable_color_quantization,
 
3856
  settings_output_standard,
3857
  settings_accordion_standard,
3858
  ],
3859
+ show_progress="full",
3860
  )
3861
 
3862
  # Load Settings button event handler
 
3876
  use_custom_seed,
3877
  seed,
3878
  enable_upscale,
3879
+ enable_animation,
3880
  controlnet_strength_standard_first,
3881
  controlnet_strength_standard_final,
3882
  enable_color_quantization,