Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f38ed1dc22 |
@@ -1913,7 +1913,7 @@
|
||||
"The painted area, grown a little, is filled with flat gray, so Krea 2 knows where to paint. The plain crop goes along too."
|
||||
],
|
||||
"widgets_values_named": {
|
||||
"text": "The painted area, grown a little, is filled with flat gray, so Krea 2 knows where to paint. The plain crop goes along too."
|
||||
"text": "The painted area, grown a little, is filled with flat gray. Krea 2 never sees what you are replacing."
|
||||
},
|
||||
"color": "#1f2c38",
|
||||
"bgcolor": "#2a3b4a"
|
||||
@@ -3052,7 +3052,7 @@
|
||||
"The gray-filled crop is the reference. Painting starts from the plain crop, inside the noise mask: the painted area, widened by 24 px."
|
||||
],
|
||||
"widgets_values_named": {
|
||||
"text": "The gray-filled crop is the reference. Painting starts from the plain crop, inside the noise mask: the painted area, widened by 24 px."
|
||||
"text": "The gray-filled crop is the reference. The noise mask is the grown painted area, widened by 24 px. Krea 2 paints only inside it."
|
||||
},
|
||||
"color": "#1f2c38",
|
||||
"bgcolor": "#2a3b4a"
|
||||
|
||||
@@ -2139,10 +2139,10 @@
|
||||
"title": "Note",
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"Blend in fades the model's picture into yours just inside its edge. Tone match is fully on, so the new area is adjusted to your picture's tone."
|
||||
"Blend in fades the model's picture into yours just inside its edge, with no tone shift. The new area stays as painted."
|
||||
],
|
||||
"widgets_values_named": {
|
||||
"text": "Blend in fades the model's picture into yours just inside its edge. Tone match is fully on, so the new area is adjusted to your picture's tone."
|
||||
"text": "Blend in fades the model's picture into yours just inside its edge, with no tone shift. The new area stays as painted."
|
||||
},
|
||||
"color": "#1f2c38",
|
||||
"bgcolor": "#2a3b4a"
|
||||
|
||||
@@ -37,10 +37,10 @@
|
||||
"Node name for S&R": "AUSBOSS_NODES_WorkflowNote"
|
||||
},
|
||||
"widgets_values": [
|
||||
"{\"v\": 1, \"title\": \"Qwen Image 2.1 Inpaint\", \"subtitle\": \"Click a thing in your picture and say what to change. Only that part is repainted.\", \"author\": \"AusBoss\", \"layout\": \"card\", \"accent\": \"\", \"body\": \"1. Load your image. You can crop it right there with the blue handles.\\n2. On **Mask by Touch**, click the thing you want to change. It lights up before you click.\\n3. In **What to change**, say it the way you would ask a person: \\\"Replace the cup with a glass of orange juice.\\\"\\n4. Press **Run**.\\n\\nOnly the picked part is repainted. Everything outside it stays your own picture.\\n\\n### Tips\\n- Wrong thing picked? The arrows under the picture swap it for the next thing it could mean.\\n- Name everything that should go: \\\"the iced coffee and its straw\\\". What you do not name can stay.\\n- To remove something, pick it and write \\\"Remove the cup.\\\" Pick its shadow too, or the shadow stays.\\n- To add something, switch to **Paint** and paint where it should go, with some room. Say how big: \\\"Add small black sunglasses on her eyes.\\\"\\n- Not what you wanted? Change **Seed** and run it again. It stays fixed between runs.\\n\\n### Switches\\n- **Fast** on: 8 steps with the Viggle Turbo LoRA from the list below. Off: 25 steps without it, about twice as long.\\n\\nNeeds ComfyUI 0.38 or newer and AusBoss nodes 2.12.0 or newer (update first).\", \"models\": [{\"name\": \"qwen_image_2.1_int8_convrot.safetensors\", \"dir\": \"diffusion_models\", \"size\": \"7.26 GB\", \"url\": \"https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/diffusion_models/qwen_image_2.1_int8_convrot.safetensors\", \"note\": \"\"}, {\"name\": \"qwen3vl_8b_int8_convrot.safetensors\", \"dir\": \"text_encoders\", \"size\": \"9.35 GB\", \"url\": \"https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/text_encoders/qwen3vl_8b_int8_convrot.safetensors\", \"note\": \"\"}, {\"name\": \"qwen_image_2.1_vae_bf16.safetensors\", \"dir\": \"vae\", \"size\": \"676 MB\", \"url\": \"https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/vae/qwen_image_2.1_vae_bf16.safetensors\", \"note\": \"\"}, {\"name\": \"sam3.1_multiplex_fp16.safetensors\", \"dir\": \"checkpoints\", \"size\": \"1.75 GB\", \"url\": \"https://huggingface.co/Comfy-Org/sam3.1/resolve/main/checkpoints/sam3.1_multiplex_fp16.safetensors\", \"note\": \"by Meta, packaged by Comfy-Org\"}, {\"name\": \"Qwen-Image-2.1-viggle-turbo-v0.3-6step-lora-r128.safetensors\", \"dir\": \"loras\", \"size\": \"680 MB\", \"url\": \"https://huggingface.co/Viggle/Qwen-Image-2.1-viggle-turbo/resolve/main/Qwen-Image-2.1-viggle-turbo-v0.3-6step-lora-r128.safetensors\", \"note\": \"optional, for Fast · Qwen Research License, research use\"}], \"packs\": [{\"name\": \"ComfyUI-AusBoss\", \"url\": \"https://github.com/ausboss/ComfyUI-AusBoss\", \"node\": \"AUSBOSS_NODES_MaskByTouch\"}], \"links\": [{\"label\": \"YouTube · this workflow\", \"url\": \"https://youtu.be/iMgaiXoB0v4\"}, {\"label\": \"GitHub · ausboss\", \"url\": \"https://github.com/ausboss\"}, {\"label\": \"Civitai · AusBoss\", \"url\": \"https://civitai.com/user/AusBoss\"}, {\"label\": \"X · Zanzibased\", \"url\": \"https://x.com/Zanzibased\"}]}"
|
||||
"{\"v\": 1, \"title\": \"Qwen Image 2.1 Inpaint\", \"subtitle\": \"Click a thing in your picture and say what to change. Only that part is repainted.\", \"author\": \"AusBoss\", \"layout\": \"card\", \"accent\": \"\", \"body\": \"1. Load your image. You can crop it right there with the blue handles.\\n2. On **Mask by Touch**, click the thing you want to change. It lights up before you click.\\n3. In **What to change**, say it the way you would ask a person: \\\"Replace the cup with a glass of orange juice.\\\"\\n4. Press **Run**.\\n\\nOnly the picked part is repainted. Everything outside it stays your own picture.\\n\\n### Tips\\n- Wrong thing picked? The arrows under the picture swap it for the next thing it could mean.\\n- Name everything that should go: \\\"the iced coffee and its straw\\\". What you do not name can stay.\\n- To remove something, pick it and write \\\"Remove the cup.\\\" Pick its shadow too, or the shadow stays.\\n- To add something, switch to **Paint** and paint where it should go, with some room. Say how big: \\\"Add small black sunglasses on her eyes.\\\"\\n- Not what you wanted? Run it again. The seed changes each run.\\n\\n### Switches\\n- **Fast** on: 8 steps with the Viggle Turbo LoRA from the list below. Off: 25 steps without it, about twice as long.\\n\\nNeeds ComfyUI 0.38 or newer and AusBoss nodes 2.12.0 or newer (update first).\", \"models\": [{\"name\": \"qwen_image_2.1_int8_convrot.safetensors\", \"dir\": \"diffusion_models\", \"size\": \"7.26 GB\", \"url\": \"https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/diffusion_models/qwen_image_2.1_int8_convrot.safetensors\", \"note\": \"\"}, {\"name\": \"qwen3vl_8b_int8_convrot.safetensors\", \"dir\": \"text_encoders\", \"size\": \"9.35 GB\", \"url\": \"https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/text_encoders/qwen3vl_8b_int8_convrot.safetensors\", \"note\": \"\"}, {\"name\": \"qwen_image_2.1_vae_bf16.safetensors\", \"dir\": \"vae\", \"size\": \"676 MB\", \"url\": \"https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/vae/qwen_image_2.1_vae_bf16.safetensors\", \"note\": \"\"}, {\"name\": \"sam3.1_multiplex_fp16.safetensors\", \"dir\": \"checkpoints\", \"size\": \"1.75 GB\", \"url\": \"https://huggingface.co/Comfy-Org/sam3.1/resolve/main/checkpoints/sam3.1_multiplex_fp16.safetensors\", \"note\": \"by Meta, packaged by Comfy-Org\"}, {\"name\": \"Qwen-Image-2.1-viggle-turbo-v0.3-6step-lora-r128.safetensors\", \"dir\": \"loras\", \"size\": \"680 MB\", \"url\": \"https://huggingface.co/Viggle/Qwen-Image-2.1-viggle-turbo/resolve/main/Qwen-Image-2.1-viggle-turbo-v0.3-6step-lora-r128.safetensors\", \"note\": \"optional, for Fast · Qwen Research License, research use\"}], \"packs\": [{\"name\": \"ComfyUI-AusBoss\", \"url\": \"https://github.com/ausboss/ComfyUI-AusBoss\", \"node\": \"AUSBOSS_NODES_MaskByTouch\"}], \"links\": [{\"label\": \"YouTube · this workflow\", \"url\": \"https://youtu.be/iMgaiXoB0v4\"}, {\"label\": \"GitHub · ausboss\", \"url\": \"https://github.com/ausboss\"}, {\"label\": \"Civitai · AusBoss\", \"url\": \"https://civitai.com/user/AusBoss\"}, {\"label\": \"X · Zanzibased\", \"url\": \"https://x.com/Zanzibased\"}]}"
|
||||
],
|
||||
"widgets_values_named": {
|
||||
"note": "{\"v\": 1, \"title\": \"Qwen Image 2.1 Inpaint\", \"subtitle\": \"Click a thing in your picture and say what to change. Only that part is repainted.\", \"author\": \"AusBoss\", \"layout\": \"card\", \"accent\": \"\", \"body\": \"1. Load your image. You can crop it right there with the blue handles.\\n2. On **Mask by Touch**, click the thing you want to change. It lights up before you click.\\n3. In **What to change**, say it the way you would ask a person: \\\"Replace the cup with a glass of orange juice.\\\"\\n4. Press **Run**.\\n\\nOnly the picked part is repainted. Everything outside it stays your own picture.\\n\\n### Tips\\n- Wrong thing picked? The arrows under the picture swap it for the next thing it could mean.\\n- Name everything that should go: \\\"the iced coffee and its straw\\\". What you do not name can stay.\\n- To remove something, pick it and write \\\"Remove the cup.\\\" Pick its shadow too, or the shadow stays.\\n- To add something, switch to **Paint** and paint where it should go, with some room. Say how big: \\\"Add small black sunglasses on her eyes.\\\"\\n- Not what you wanted? Change **Seed** and run it again. It stays fixed between runs.\\n\\n### Switches\\n- **Fast** on: 8 steps with the Viggle Turbo LoRA from the list below. Off: 25 steps without it, about twice as long.\\n\\nNeeds ComfyUI 0.38 or newer and AusBoss nodes 2.12.0 or newer (update first).\", \"models\": [{\"name\": \"qwen_image_2.1_int8_convrot.safetensors\", \"dir\": \"diffusion_models\", \"size\": \"7.26 GB\", \"url\": \"https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/diffusion_models/qwen_image_2.1_int8_convrot.safetensors\", \"note\": \"\"}, {\"name\": \"qwen3vl_8b_int8_convrot.safetensors\", \"dir\": \"text_encoders\", \"size\": \"9.35 GB\", \"url\": \"https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/text_encoders/qwen3vl_8b_int8_convrot.safetensors\", \"note\": \"\"}, {\"name\": \"qwen_image_2.1_vae_bf16.safetensors\", \"dir\": \"vae\", \"size\": \"676 MB\", \"url\": \"https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/vae/qwen_image_2.1_vae_bf16.safetensors\", \"note\": \"\"}, {\"name\": \"sam3.1_multiplex_fp16.safetensors\", \"dir\": \"checkpoints\", \"size\": \"1.75 GB\", \"url\": \"https://huggingface.co/Comfy-Org/sam3.1/resolve/main/checkpoints/sam3.1_multiplex_fp16.safetensors\", \"note\": \"by Meta, packaged by Comfy-Org\"}, {\"name\": \"Qwen-Image-2.1-viggle-turbo-v0.3-6step-lora-r128.safetensors\", \"dir\": \"loras\", \"size\": \"680 MB\", \"url\": \"https://huggingface.co/Viggle/Qwen-Image-2.1-viggle-turbo/resolve/main/Qwen-Image-2.1-viggle-turbo-v0.3-6step-lora-r128.safetensors\", \"note\": \"optional, for Fast · Qwen Research License, research use\"}], \"packs\": [{\"name\": \"ComfyUI-AusBoss\", \"url\": \"https://github.com/ausboss/ComfyUI-AusBoss\", \"node\": \"AUSBOSS_NODES_MaskByTouch\"}], \"links\": [{\"label\": \"YouTube · this workflow\", \"url\": \"https://youtu.be/iMgaiXoB0v4\"}, {\"label\": \"GitHub · ausboss\", \"url\": \"https://github.com/ausboss\"}, {\"label\": \"Civitai · AusBoss\", \"url\": \"https://civitai.com/user/AusBoss\"}, {\"label\": \"X · Zanzibased\", \"url\": \"https://x.com/Zanzibased\"}]}"
|
||||
"note": "{\"v\": 1, \"title\": \"Qwen Image 2.1 Inpaint\", \"subtitle\": \"Click a thing in your picture and say what to change. Only that part is repainted.\", \"author\": \"AusBoss\", \"layout\": \"card\", \"accent\": \"\", \"body\": \"1. Load your image. You can crop it right there with the blue handles.\\n2. On **Mask by Touch**, click the thing you want to change. It lights up before you click.\\n3. In **What to change**, say it the way you would ask a person: \\\"Replace the cup with a glass of orange juice.\\\"\\n4. Press **Run**.\\n\\nOnly the picked part is repainted. Everything outside it stays your own picture.\\n\\n### Tips\\n- Wrong thing picked? The arrows under the picture swap it for the next thing it could mean.\\n- Name everything that should go: \\\"the iced coffee and its straw\\\". What you do not name can stay.\\n- To remove something, pick it and write \\\"Remove the cup.\\\" Pick its shadow too, or the shadow stays.\\n- To add something, switch to **Paint** and paint where it should go, with some room. Say how big: \\\"Add small black sunglasses on her eyes.\\\"\\n- Not what you wanted? Run it again. The seed changes each run.\\n\\n### Switches\\n- **Fast** on: 8 steps with the Viggle Turbo LoRA from the list below. Off: 25 steps without it, about twice as long.\\n\\nNeeds ComfyUI 0.38 or newer and AusBoss nodes 2.12.0 or newer (update first).\", \"models\": [{\"name\": \"qwen_image_2.1_int8_convrot.safetensors\", \"dir\": \"diffusion_models\", \"size\": \"7.26 GB\", \"url\": \"https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/diffusion_models/qwen_image_2.1_int8_convrot.safetensors\", \"note\": \"\"}, {\"name\": \"qwen3vl_8b_int8_convrot.safetensors\", \"dir\": \"text_encoders\", \"size\": \"9.35 GB\", \"url\": \"https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/text_encoders/qwen3vl_8b_int8_convrot.safetensors\", \"note\": \"\"}, {\"name\": \"qwen_image_2.1_vae_bf16.safetensors\", \"dir\": \"vae\", \"size\": \"676 MB\", \"url\": \"https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/vae/qwen_image_2.1_vae_bf16.safetensors\", \"note\": \"\"}, {\"name\": \"sam3.1_multiplex_fp16.safetensors\", \"dir\": \"checkpoints\", \"size\": \"1.75 GB\", \"url\": \"https://huggingface.co/Comfy-Org/sam3.1/resolve/main/checkpoints/sam3.1_multiplex_fp16.safetensors\", \"note\": \"by Meta, packaged by Comfy-Org\"}, {\"name\": \"Qwen-Image-2.1-viggle-turbo-v0.3-6step-lora-r128.safetensors\", \"dir\": \"loras\", \"size\": \"680 MB\", \"url\": \"https://huggingface.co/Viggle/Qwen-Image-2.1-viggle-turbo/resolve/main/Qwen-Image-2.1-viggle-turbo-v0.3-6step-lora-r128.safetensors\", \"note\": \"optional, for Fast · Qwen Research License, research use\"}], \"packs\": [{\"name\": \"ComfyUI-AusBoss\", \"url\": \"https://github.com/ausboss/ComfyUI-AusBoss\", \"node\": \"AUSBOSS_NODES_MaskByTouch\"}], \"links\": [{\"label\": \"YouTube · this workflow\", \"url\": \"https://youtu.be/iMgaiXoB0v4\"}, {\"label\": \"GitHub · ausboss\", \"url\": \"https://github.com/ausboss\"}, {\"label\": \"Civitai · AusBoss\", \"url\": \"https://civitai.com/user/AusBoss\"}, {\"label\": \"X · Zanzibased\", \"url\": \"https://x.com/Zanzibased\"}]}"
|
||||
},
|
||||
"color": "#14424d",
|
||||
"bgcolor": "#161f21"
|
||||
|
||||
@@ -37,10 +37,10 @@
|
||||
"ver": "2.9.0"
|
||||
},
|
||||
"widgets_values": [
|
||||
"{\"v\": 1, \"title\": \"Qwen Image 2.1 Upscale + Restore\", \"subtitle\": \"Load a small, soft or worn picture. Qwen Image 2.1 redraws it sharp at the megapixels you set, in its own colors.\", \"author\": \"AusBoss\", \"layout\": \"card\", \"accent\": \"\", \"body\": \"[▶ AusBoss on YouTube](https://www.youtube.com/@ausbossdev) · videos on these nodes and workflows\\n\\n---\\n\\n1. Load your image. You can crop it right there with the blue handles.\\n2. Set **Megapixels**: the size of the result.\\n3. Press **Run**.\\n4. Slide **Before and after** to compare. The result is saved as a PNG.\\n\\n### What you get\\n\\n- Your picture redrawn sharp, at the size you asked for.\\n- Its own colors, also on a noisy or faded photo.\\n\\n### Megapixels\\n\\n- Up to about 2: one pass.\\n- Over that: redrawn in tiles of about 2 megapixels and joined. 4 takes two tiles, 8 takes four.\\n- The redraw limit for a small picture is 8 times its own megapixels or one roughly 2 megapixel tile, whichever is larger. The rest is enlarged the plain way.\\n- A picture of 1 megapixel or more only looks better when it grows about 1.6 times per side.\\n\\n### Switches\\n\\n- **Fast** on: 8 steps with the Viggle Turbo LoRA. Off: 25 steps without it, about three times as long.\\n\\n### Tips\\n\\n- It is a redraw. Fine detail is made up to fit, so a face stays the same person, not the same pixels.\\n- Small lettering on signs and labels can come back misspelled.\\n- Each tile is matched to your picture's colors. To let Qwen's own colors through, lower **Keep colors** inside the box.\\n- If the run stops at a red **LoRA Loader**, a LoRA that is switched on is missing. Download it from the list below.\\n- Not tested on blur from a moving camera or missed focus.\\n\\nNeeds ComfyUI 0.38 or newer and AusBoss nodes 2.9.0 or newer (update first).\", \"models\": [{\"name\": \"qwen_image_2.1_int8_convrot.safetensors\", \"dir\": \"diffusion_models\", \"size\": \"7.26 GB\", \"url\": \"https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/diffusion_models/qwen_image_2.1_int8_convrot.safetensors\", \"note\": \"\"}, {\"name\": \"qwen3vl_8b_int8_convrot.safetensors\", \"dir\": \"text_encoders\", \"size\": \"9.35 GB\", \"url\": \"https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/text_encoders/qwen3vl_8b_int8_convrot.safetensors\", \"note\": \"\"}, {\"name\": \"qwen_image_2.1_vae_bf16.safetensors\", \"dir\": \"vae\", \"size\": \"676 MB\", \"url\": \"https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/vae/qwen_image_2.1_vae_bf16.safetensors\", \"note\": \"\"}, {\"name\": \"qwen-image-2.1-restore.safetensors\", \"dir\": \"loras\", \"size\": \"159 MB\", \"url\": \"https://huggingface.co/ausboss/Qwen-Image-2.1-Restore-LoRA/resolve/main/qwen-image-2.1-restore.safetensors\", \"note\": \"keeps the result close to your picture\"}, {\"name\": \"Qwen-Image-2.1-viggle-turbo-v0.3-6step-lora-r128.safetensors\", \"dir\": \"loras\", \"size\": \"680 MB\", \"url\": \"https://huggingface.co/Viggle/Qwen-Image-2.1-viggle-turbo/resolve/main/Qwen-Image-2.1-viggle-turbo-v0.3-6step-lora-r128.safetensors\", \"note\": \"for Fast on · Qwen Research License, research use\"}], \"packs\": [{\"name\": \"ComfyUI-AusBoss\", \"url\": \"https://github.com/ausboss/ComfyUI-AusBoss\", \"node\": \"AUSBOSS_NODES_TiledUpscale\"}], \"links\": [{\"label\": \"YouTube · ausbossdev\", \"url\": \"https://www.youtube.com/@ausbossdev\"}, {\"label\": \"GitHub · ausboss\", \"url\": \"https://github.com/ausboss\"}, {\"label\": \"Civitai · AusBoss\", \"url\": \"https://civitai.com/user/AusBoss\"}, {\"label\": \"X · Zanzibased\", \"url\": \"https://x.com/Zanzibased\"}]}"
|
||||
"{\"v\": 1, \"title\": \"Qwen Image 2.1 Upscale + Restore\", \"subtitle\": \"Load a small, soft or worn picture. Qwen Image 2.1 redraws it sharp at the megapixels you set, in its own colors.\", \"author\": \"AusBoss\", \"layout\": \"card\", \"accent\": \"\", \"body\": \"[▶ AusBoss on YouTube](https://www.youtube.com/@ausbossdev) · videos on these nodes and workflows\\n\\n---\\n\\n1. Load your image. You can crop it right there with the blue handles.\\n2. Set **Megapixels**: the size of the result.\\n3. Press **Run**.\\n4. Slide **Before and after** to compare. The result is saved as a PNG.\\n\\n### What you get\\n\\n- Your picture redrawn sharp, at the size you asked for.\\n- Its own colors, also on a noisy or faded photo.\\n\\n### Megapixels\\n\\n- Up to about 2: one pass.\\n- Over that: redrawn in tiles of about 2 megapixels and joined. 4 takes two tiles, 8 takes four.\\n- A small picture is redrawn at up to 8 times its own megapixels, then enlarged the plain way.\\n- A picture of 1 megapixel or more only looks better when it grows about 1.6 times per side.\\n\\n### Switches\\n\\n- **Fast** on: 8 steps with the Viggle Turbo LoRA. Off: 25 steps without it, about three times as long.\\n\\n### Tips\\n\\n- It is a redraw. Fine detail is made up to fit, so a face stays the same person, not the same pixels.\\n- Small lettering on signs and labels can come back misspelled.\\n- Each tile is matched to your picture's colors. To let Qwen's own colors through, lower **Keep colors** inside the box.\\n- If the run stops at a red **LoRA Loader**, a LoRA that is switched on is missing. Download it from the list below.\\n- Not tested on blur from a moving camera or missed focus.\\n\\nNeeds ComfyUI 0.38 or newer and AusBoss nodes 2.9.0 or newer (update first).\", \"models\": [{\"name\": \"qwen_image_2.1_int8_convrot.safetensors\", \"dir\": \"diffusion_models\", \"size\": \"7.26 GB\", \"url\": \"https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/diffusion_models/qwen_image_2.1_int8_convrot.safetensors\", \"note\": \"\"}, {\"name\": \"qwen3vl_8b_int8_convrot.safetensors\", \"dir\": \"text_encoders\", \"size\": \"9.35 GB\", \"url\": \"https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/text_encoders/qwen3vl_8b_int8_convrot.safetensors\", \"note\": \"\"}, {\"name\": \"qwen_image_2.1_vae_bf16.safetensors\", \"dir\": \"vae\", \"size\": \"676 MB\", \"url\": \"https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/vae/qwen_image_2.1_vae_bf16.safetensors\", \"note\": \"\"}, {\"name\": \"qwen-image-2.1-restore.safetensors\", \"dir\": \"loras\", \"size\": \"159 MB\", \"url\": \"https://huggingface.co/ausboss/Qwen-Image-2.1-Restore-LoRA/resolve/main/qwen-image-2.1-restore.safetensors\", \"note\": \"keeps the result close to your picture\"}, {\"name\": \"Qwen-Image-2.1-viggle-turbo-v0.3-6step-lora-r128.safetensors\", \"dir\": \"loras\", \"size\": \"680 MB\", \"url\": \"https://huggingface.co/Viggle/Qwen-Image-2.1-viggle-turbo/resolve/main/Qwen-Image-2.1-viggle-turbo-v0.3-6step-lora-r128.safetensors\", \"note\": \"for Fast on · Qwen Research License, research use\"}], \"packs\": [{\"name\": \"ComfyUI-AusBoss\", \"url\": \"https://github.com/ausboss/ComfyUI-AusBoss\", \"node\": \"AUSBOSS_NODES_TiledUpscale\"}], \"links\": [{\"label\": \"YouTube · ausbossdev\", \"url\": \"https://www.youtube.com/@ausbossdev\"}, {\"label\": \"GitHub · ausboss\", \"url\": \"https://github.com/ausboss\"}, {\"label\": \"Civitai · AusBoss\", \"url\": \"https://civitai.com/user/AusBoss\"}, {\"label\": \"X · Zanzibased\", \"url\": \"https://x.com/Zanzibased\"}]}"
|
||||
],
|
||||
"widgets_values_named": {
|
||||
"note": "{\"v\": 1, \"title\": \"Qwen Image 2.1 Upscale + Restore\", \"subtitle\": \"Load a small, soft or worn picture. Qwen Image 2.1 redraws it sharp at the megapixels you set, in its own colors.\", \"author\": \"AusBoss\", \"layout\": \"card\", \"accent\": \"\", \"body\": \"[▶ AusBoss on YouTube](https://www.youtube.com/@ausbossdev) · videos on these nodes and workflows\\n\\n---\\n\\n1. Load your image. You can crop it right there with the blue handles.\\n2. Set **Megapixels**: the size of the result.\\n3. Press **Run**.\\n4. Slide **Before and after** to compare. The result is saved as a PNG.\\n\\n### What you get\\n\\n- Your picture redrawn sharp, at the size you asked for.\\n- Its own colors, also on a noisy or faded photo.\\n\\n### Megapixels\\n\\n- Up to about 2: one pass.\\n- Over that: redrawn in tiles of about 2 megapixels and joined. 4 takes two tiles, 8 takes four.\\n- The redraw limit for a small picture is 8 times its own megapixels or one roughly 2 megapixel tile, whichever is larger. The rest is enlarged the plain way.\\n- A picture of 1 megapixel or more only looks better when it grows about 1.6 times per side.\\n\\n### Switches\\n\\n- **Fast** on: 8 steps with the Viggle Turbo LoRA. Off: 25 steps without it, about three times as long.\\n\\n### Tips\\n\\n- It is a redraw. Fine detail is made up to fit, so a face stays the same person, not the same pixels.\\n- Small lettering on signs and labels can come back misspelled.\\n- Each tile is matched to your picture's colors. To let Qwen's own colors through, lower **Keep colors** inside the box.\\n- If the run stops at a red **LoRA Loader**, a LoRA that is switched on is missing. Download it from the list below.\\n- Not tested on blur from a moving camera or missed focus.\\n\\nNeeds ComfyUI 0.38 or newer and AusBoss nodes 2.9.0 or newer (update first).\", \"models\": [{\"name\": \"qwen_image_2.1_int8_convrot.safetensors\", \"dir\": \"diffusion_models\", \"size\": \"7.26 GB\", \"url\": \"https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/diffusion_models/qwen_image_2.1_int8_convrot.safetensors\", \"note\": \"\"}, {\"name\": \"qwen3vl_8b_int8_convrot.safetensors\", \"dir\": \"text_encoders\", \"size\": \"9.35 GB\", \"url\": \"https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/text_encoders/qwen3vl_8b_int8_convrot.safetensors\", \"note\": \"\"}, {\"name\": \"qwen_image_2.1_vae_bf16.safetensors\", \"dir\": \"vae\", \"size\": \"676 MB\", \"url\": \"https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/vae/qwen_image_2.1_vae_bf16.safetensors\", \"note\": \"\"}, {\"name\": \"qwen-image-2.1-restore.safetensors\", \"dir\": \"loras\", \"size\": \"159 MB\", \"url\": \"https://huggingface.co/ausboss/Qwen-Image-2.1-Restore-LoRA/resolve/main/qwen-image-2.1-restore.safetensors\", \"note\": \"keeps the result close to your picture\"}, {\"name\": \"Qwen-Image-2.1-viggle-turbo-v0.3-6step-lora-r128.safetensors\", \"dir\": \"loras\", \"size\": \"680 MB\", \"url\": \"https://huggingface.co/Viggle/Qwen-Image-2.1-viggle-turbo/resolve/main/Qwen-Image-2.1-viggle-turbo-v0.3-6step-lora-r128.safetensors\", \"note\": \"for Fast on · Qwen Research License, research use\"}], \"packs\": [{\"name\": \"ComfyUI-AusBoss\", \"url\": \"https://github.com/ausboss/ComfyUI-AusBoss\", \"node\": \"AUSBOSS_NODES_TiledUpscale\"}], \"links\": [{\"label\": \"YouTube · ausbossdev\", \"url\": \"https://www.youtube.com/@ausbossdev\"}, {\"label\": \"GitHub · ausboss\", \"url\": \"https://github.com/ausboss\"}, {\"label\": \"Civitai · AusBoss\", \"url\": \"https://civitai.com/user/AusBoss\"}, {\"label\": \"X · Zanzibased\", \"url\": \"https://x.com/Zanzibased\"}]}"
|
||||
"note": "{\"v\": 1, \"title\": \"Qwen Image 2.1 Upscale + Restore\", \"subtitle\": \"Load a small, soft or worn picture. Qwen Image 2.1 redraws it sharp at the megapixels you set, in its own colors.\", \"author\": \"AusBoss\", \"layout\": \"card\", \"accent\": \"\", \"body\": \"[▶ AusBoss on YouTube](https://www.youtube.com/@ausbossdev) · videos on these nodes and workflows\\n\\n---\\n\\n1. Load your image. You can crop it right there with the blue handles.\\n2. Set **Megapixels**: the size of the result.\\n3. Press **Run**.\\n4. Slide **Before and after** to compare. The result is saved as a PNG.\\n\\n### What you get\\n\\n- Your picture redrawn sharp, at the size you asked for.\\n- Its own colors, also on a noisy or faded photo.\\n\\n### Megapixels\\n\\n- Up to about 2: one pass.\\n- Over that: redrawn in tiles of about 2 megapixels and joined. 4 takes two tiles, 8 takes four.\\n- A small picture is redrawn at up to 8 times its own megapixels, then enlarged the plain way.\\n- A picture of 1 megapixel or more only looks better when it grows about 1.6 times per side.\\n\\n### Switches\\n\\n- **Fast** on: 8 steps with the Viggle Turbo LoRA. Off: 25 steps without it, about three times as long.\\n\\n### Tips\\n\\n- It is a redraw. Fine detail is made up to fit, so a face stays the same person, not the same pixels.\\n- Small lettering on signs and labels can come back misspelled.\\n- Each tile is matched to your picture's colors. To let Qwen's own colors through, lower **Keep colors** inside the box.\\n- If the run stops at a red **LoRA Loader**, a LoRA that is switched on is missing. Download it from the list below.\\n- Not tested on blur from a moving camera or missed focus.\\n\\nNeeds ComfyUI 0.38 or newer and AusBoss nodes 2.9.0 or newer (update first).\", \"models\": [{\"name\": \"qwen_image_2.1_int8_convrot.safetensors\", \"dir\": \"diffusion_models\", \"size\": \"7.26 GB\", \"url\": \"https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/diffusion_models/qwen_image_2.1_int8_convrot.safetensors\", \"note\": \"\"}, {\"name\": \"qwen3vl_8b_int8_convrot.safetensors\", \"dir\": \"text_encoders\", \"size\": \"9.35 GB\", \"url\": \"https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/text_encoders/qwen3vl_8b_int8_convrot.safetensors\", \"note\": \"\"}, {\"name\": \"qwen_image_2.1_vae_bf16.safetensors\", \"dir\": \"vae\", \"size\": \"676 MB\", \"url\": \"https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/vae/qwen_image_2.1_vae_bf16.safetensors\", \"note\": \"\"}, {\"name\": \"qwen-image-2.1-restore.safetensors\", \"dir\": \"loras\", \"size\": \"159 MB\", \"url\": \"https://huggingface.co/ausboss/Qwen-Image-2.1-Restore-LoRA/resolve/main/qwen-image-2.1-restore.safetensors\", \"note\": \"keeps the result close to your picture\"}, {\"name\": \"Qwen-Image-2.1-viggle-turbo-v0.3-6step-lora-r128.safetensors\", \"dir\": \"loras\", \"size\": \"680 MB\", \"url\": \"https://huggingface.co/Viggle/Qwen-Image-2.1-viggle-turbo/resolve/main/Qwen-Image-2.1-viggle-turbo-v0.3-6step-lora-r128.safetensors\", \"note\": \"for Fast on · Qwen Research License, research use\"}], \"packs\": [{\"name\": \"ComfyUI-AusBoss\", \"url\": \"https://github.com/ausboss/ComfyUI-AusBoss\", \"node\": \"AUSBOSS_NODES_TiledUpscale\"}], \"links\": [{\"label\": \"YouTube · ausbossdev\", \"url\": \"https://www.youtube.com/@ausbossdev\"}, {\"label\": \"GitHub · ausboss\", \"url\": \"https://github.com/ausboss\"}, {\"label\": \"Civitai · AusBoss\", \"url\": \"https://civitai.com/user/AusBoss\"}, {\"label\": \"X · Zanzibased\", \"url\": \"https://x.com/Zanzibased\"}]}"
|
||||
},
|
||||
"color": "#14424d",
|
||||
"bgcolor": "#161f21"
|
||||
|
||||
+4
-1
@@ -253,7 +253,7 @@ function run(tool) {
|
||||
// took the press. Every other selected node takes its size from where it
|
||||
// stood when the button went down, so nothing builds up across steps.
|
||||
|
||||
const together = { enabled: true, before: null, grabbed: null, handleOf: null, sides: null, frame: 0 };
|
||||
const together = { enabled: true, before: null, grabbed: null, handleOf: null, sides: null, frame: 0, resized: false };
|
||||
|
||||
function resizableSelection(canvas) {
|
||||
const { nodes } = graphSets(canvas?.graph);
|
||||
@@ -288,6 +288,8 @@ function syncSizes() {
|
||||
const grabbed = together.grabbed;
|
||||
if (!grabbed) return;
|
||||
const was = together.before.get(grabbed);
|
||||
together.resized ||= grabbed.size[0] !== was.size[0] || grabbed.size[1] !== was.size[1];
|
||||
if (!together.resized) return;
|
||||
together.sides ??= sharedSides(
|
||||
[...together.before.values()].map(({ pos, size }) => ({ x: pos[0], y: pos[1], w: size[0], h: size[1] })),
|
||||
);
|
||||
@@ -348,6 +350,7 @@ function endTogether() {
|
||||
together.grabbed = null;
|
||||
together.handleOf = null;
|
||||
together.sides = null;
|
||||
together.resized = false;
|
||||
}
|
||||
|
||||
function installTogether() {
|
||||
|
||||
@@ -15,7 +15,7 @@ function ensureCss() {
|
||||
const style = document.createElement("style");
|
||||
style.id = CSS_ID;
|
||||
style.textContent = `
|
||||
.ausboss-chips{display:flex;flex-wrap:wrap;align-content:flex-start;gap:5px;height:100%;min-width:0;font:600 10.5px/1 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
||||
.ausboss-chips{display:flex;flex-wrap:wrap;align-content:flex-start;gap:5px;min-width:0;font:600 10.5px/1 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
||||
.ausboss-chip{position:relative;display:flex;align-items:center;gap:4px;height:24px;max-width:100%;padding:0 8px;box-sizing:border-box;border:1px solid #2a3437;border-radius:12px;background:#0b0f10;color:#d8ecea;white-space:nowrap}
|
||||
.ausboss-chip:hover{border-color:${BRAND}}
|
||||
.ausboss-chip img{flex:none;width:18px;height:18px;margin-left:-5px;border-radius:9px;object-fit:cover}
|
||||
|
||||
@@ -31,16 +31,27 @@ export function stripRows(report) {
|
||||
}));
|
||||
}
|
||||
|
||||
// A time typed at the start of a line: 3s:, 3 s, at 3 seconds, [3s].
|
||||
const TYPED_TIME = /^\[?(?:at\s+)?\d+(?:\.\d+)?\s*(?:seconds?|secs?|s)\b\]?\s*[:,\-]?\s*/i;
|
||||
// Keep these in step with NUMBER and TIMED in nodes/_h3_script_helpers.py.
|
||||
const LIST_PREFIX = /^(?:\d+[.)]|[-*•]|(?:first|second|third|fourth|fifth|sixth)(?:ly)?\b[:,]?|step\s+\d+[:.]?)\s*/i;
|
||||
const UNIT = String.raw`(?:seconds?|secs?|s)\b`;
|
||||
const DASH = String.raw`[-–—]`;
|
||||
const TYPED_TIME = new RegExp(String.raw`^\[?(?:at\s+)?(?:\d+:\d\d(?:\.\d+)?|\d+(?:\.\d+)?\s*(?:${UNIT}|(?=${DASH}\s*\d)))(?:\s*${DASH}\s*\d+(?:\.\d+)?\s*(?:${UNIT})?)?\]?\s*[:,–—\-]?\s*`, "i");
|
||||
|
||||
function splitMoment(line) {
|
||||
const indent = line.match(/^\s*/)[0];
|
||||
const body = line.slice(indent.length);
|
||||
const prefix = TYPED_TIME.test(body) ? "" : body.match(LIST_PREFIX)?.[0] ?? "";
|
||||
const rest = body.slice(prefix.length);
|
||||
const time = rest.match(TYPED_TIME)?.[0] ?? "";
|
||||
return { lead: indent + prefix, time, words: rest.slice(time.length) };
|
||||
}
|
||||
|
||||
// The script with one line set to start at a second: its old time, if any,
|
||||
// gives way to the new one. line counts typed lines from 0.
|
||||
export function retime(text, line, second) {
|
||||
const lines = String(text ?? "").split("\n");
|
||||
if (!Number.isInteger(line) || line < 0 || line >= lines.length) return String(text ?? "");
|
||||
const lead = lines[line].match(/^\s*/)[0];
|
||||
const words = lines[line].slice(lead.length).replace(TYPED_TIME, "");
|
||||
const { lead, words } = splitMoment(lines[line]);
|
||||
lines[line] = `${lead}${trimmed(second)}s: ${words}`;
|
||||
return lines.join("\n");
|
||||
}
|
||||
@@ -88,13 +99,7 @@ export function rulerSeconds(seconds) {
|
||||
export function moveMoment(text, slots, from, to) {
|
||||
const lines = String(text ?? "").split("\n");
|
||||
if (from === to || !slots.every((line) => Number.isInteger(line) && line >= 0 && line < lines.length)) return String(text ?? "");
|
||||
const split = (line) => {
|
||||
const lead = lines[line].match(/^\s*/)[0];
|
||||
const body = lines[line].slice(lead.length);
|
||||
const time = body.match(TYPED_TIME)?.[0] ?? "";
|
||||
return { lead, time, words: body.slice(time.length) };
|
||||
};
|
||||
const parts = slots.map(split);
|
||||
const parts = slots.map((line) => splitMoment(lines[line]));
|
||||
const words = parts.map((part) => part.words);
|
||||
const [moved] = words.splice(from, 1);
|
||||
words.splice(Math.max(0, Math.min(to, words.length)), 0, moved);
|
||||
|
||||
@@ -22,6 +22,24 @@
|
||||
// stack's rounded bottom border once went missing.
|
||||
export const WIDGET_FRAME = 20;
|
||||
|
||||
// A wrapped row grows from its content, which must have a natural height.
|
||||
// The row's own height feeds the card total and the sockets below it.
|
||||
export function observePanelHeight(row, element, content, refresh) {
|
||||
const minimum = Number(row.height) || 26;
|
||||
const observer = new ResizeObserver(() => {
|
||||
// offsetHeight stays in layout pixels when the canvas is zoomed.
|
||||
const measured = content.offsetHeight;
|
||||
if (!measured) return;
|
||||
const height = Math.max(minimum, measured);
|
||||
if (height === row.height) return;
|
||||
row.height = height;
|
||||
element.style.height = `${height}px`;
|
||||
refresh();
|
||||
});
|
||||
observer.observe(content);
|
||||
return observer;
|
||||
}
|
||||
|
||||
// The frontend pads a DOM widget's minWidth before it becomes the node's
|
||||
// floor: LGraphNode.computeSize adds the room a number widget's value box
|
||||
// takes (BaseWidget.minValueWidth plus both arrows and margins - 104px on
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
import { BRAND, chainCallback, keepDomWidgetWidthAuto, notifyAusbossChange } from "./index.mjs";
|
||||
import { keepKeyInField } from "./canvas_passthrough.mjs";
|
||||
import { createMediaPicker } from "./media_picker.mjs";
|
||||
import { ensureNodeMinHeight, fillNodeHeight, holdNodeMinWidth, nodeHeightAfterCardChange, pinVuePanelHeight } from "./panel_layout.mjs";
|
||||
import { ensureNodeMinHeight, fillNodeHeight, holdNodeMinWidth, nodeHeightAfterCardChange, observePanelHeight, pinVuePanelHeight } from "./panel_layout.mjs";
|
||||
import { makeScrubInput, quantizeScrubValue, stepDecimals } from "./scrub_input.mjs";
|
||||
import { limitsLabel, narrowedRange } from "./wired_limits.mjs";
|
||||
import { hideWidget } from "./widget_visibility.mjs";
|
||||
@@ -221,7 +221,7 @@ function isFloatWidget(widget) {
|
||||
// - `top` lifts the widgets' sockets
|
||||
// into the node's slot column
|
||||
// { section: "Caption" }
|
||||
// { panel: (node) => ({ element, sync(values) }), height }
|
||||
// { panel: (node) => ({ element, sync(values) }), height, measure? }
|
||||
// - the node's own display, on a row
|
||||
// of the card
|
||||
// { group: "advanced", label: "More" } - a disclosure; later rows with
|
||||
@@ -526,11 +526,16 @@ export function mountWidgetCard(node, { rows, minWidth = 300, first = false, hid
|
||||
root.append(caption); continue;
|
||||
}
|
||||
if (row.panel) {
|
||||
// Measurements belong to this node, not the shared card definition.
|
||||
const panelRow = { ...row };
|
||||
const line = el("div", "ausboss-card-row panel");
|
||||
line.style.height = `${rowHeight(row)}px`;
|
||||
const panel = row.panel(node);
|
||||
line.append(panel.element);
|
||||
state.rows.push({ row, element: line, syncs: panel.sync ? [panel.sync] : [] });
|
||||
state.rows.push({ row: panelRow, element: line, syncs: panel.sync ? [panel.sync] : [] });
|
||||
if (row.measure && typeof ResizeObserver === "function") {
|
||||
state.observers.push(observePanelHeight(panelRow, line, panel.element, refresh));
|
||||
}
|
||||
root.append(line); continue;
|
||||
}
|
||||
if (row.group !== undefined && row.widget === undefined && row.pair === undefined) {
|
||||
|
||||
@@ -344,7 +344,7 @@ const CARDS = {
|
||||
{ widget: "sound", label: "Sound", placeholder: "rain on a window" },
|
||||
{ widget: "music", label: "Music", placeholder: "slow piano: music makes it louder" },
|
||||
{ section: "Pictures and settings" },
|
||||
{ panel: castChips, height: 30 },
|
||||
{ panel: castChips, height: 30, measure: true },
|
||||
{ widget: "seconds", label: "Length", suffix: "s" },
|
||||
{ widget: "camera", label: "Camera", kind: "select" },
|
||||
{ widget: "look", label: "Look", kind: "select" },
|
||||
|
||||
@@ -225,6 +225,20 @@ def _as_mask(
|
||||
|
||||
RESIZE_ALGORITHMS = ("bilinear", "bicubic", "area", "nearest")
|
||||
|
||||
# A crop keeps several float buffers alive together. Limit new picture
|
||||
# buffers to 256 MiB or the source's size, before padding or resizing can
|
||||
# multiply its footprint. A large source alone does not force a rejection.
|
||||
_MAX_CROP_ELEMENTS = 64 * 1024 * 1024
|
||||
|
||||
|
||||
def _check_crop_size(image: torch.Tensor, width: int, height: int) -> None:
|
||||
if image.shape[0] * width * height * image.shape[3] > max(_MAX_CROP_ELEMENTS, image.numel()):
|
||||
raise ValueError(
|
||||
f"Crop For Inpaint: {width}x{height} across {image.shape[0]} picture(s) "
|
||||
"is too large to process safely. Reduce the context, extension or "
|
||||
"target size, or choose target sides with closer proportions."
|
||||
)
|
||||
|
||||
|
||||
def _resize_image(
|
||||
image: torch.Tensor, width: int, height: int, algorithm: str = "bilinear"
|
||||
@@ -420,6 +434,7 @@ def build_crop(
|
||||
ext_u, ext_d = max(0, int(extend_up)), max(0, int(extend_down))
|
||||
if ext_l or ext_r or ext_u or ext_d:
|
||||
src_h, src_w = image.shape[1], image.shape[2]
|
||||
_check_crop_size(image, src_w + ext_l + ext_r, src_h + ext_u + ext_d)
|
||||
image = _replicate_pad_image(image, ext_l, ext_u, ext_r, ext_d)
|
||||
extended = torch.ones(
|
||||
(mask.shape[0], src_h + ext_u + ext_d, src_w + ext_l + ext_r),
|
||||
@@ -487,7 +502,10 @@ def build_crop(
|
||||
rect = expand_rect_to_multiple(rect, multiple)
|
||||
rect = fit_rect(rect, width, height)
|
||||
|
||||
if use_target:
|
||||
_check_crop_size(image, target_w, target_h)
|
||||
left, top, right, bottom = rect_margins(rect, width, height)
|
||||
_check_crop_size(image, width + left + right, height + top + bottom)
|
||||
if left or top or right or bottom:
|
||||
canvas = _replicate_pad_image(image, left, top, right, bottom)
|
||||
else:
|
||||
|
||||
@@ -1,11 +1,50 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { runInNewContext } from "node:vm";
|
||||
|
||||
import { MIN_GAP, enoughFor, lineUp, matchWidth, sameSize, sharedSides, spreadEvenly } from "../js/shared/line_up.mjs";
|
||||
|
||||
const box = (x, y, w, h) => ({ x, y, w, h });
|
||||
const rects = [box(10, 0, 100, 50), box(40, 100, 200, 80), box(0, 300, 60, 40)];
|
||||
|
||||
function resizeHandlers(layout = "column", classic = false) {
|
||||
const make = (id, pos, size) => ({ id, pos, size, flags: {}, setSize(size) { this.size = size; }, computeSize: () => [100, 60] });
|
||||
const first = make(1, [0, 0], [300, 100]);
|
||||
const second = make(2, layout === "row" ? [500, 0] : [0, 200], [400, 150]);
|
||||
const canvas = { graph: { _nodes: [first, second], _groups: [] }, selectedItems: new Set([first, second]), setDirty() {} };
|
||||
const source = readFileSync(new URL("../js/line_up/index.js", import.meta.url), "utf8").replace(/^import .*;\n/gm, "");
|
||||
const handlers = runInNewContext(`${source}\n({ onTogetherDown, syncSizes, onTogetherUp })`, {
|
||||
app: { canvas, registerExtension() {} }, sharedSides, matchWidth,
|
||||
cancelAnimationFrame() {},
|
||||
});
|
||||
const handle = { closest: () => ({ dataset: { nodeId: "1" } }) };
|
||||
handlers.onTogetherDown({ button: 0, target: { closest: () => classic ? null : handle } });
|
||||
if (classic) canvas.resizing_node = first;
|
||||
return { ...handlers, first, second };
|
||||
}
|
||||
|
||||
test("pressing and releasing a resize handle leaves other selected nodes unchanged", () => {
|
||||
for (const classic of [false, true]) for (const layout of ["row", "column"]) {
|
||||
const { syncSizes, onTogetherUp, second } = resizeHandlers(layout, classic);
|
||||
syncSizes(); // A pointer move can arrive without a size change.
|
||||
onTogetherUp();
|
||||
assert.deepEqual(second.size, [400, 150]);
|
||||
}
|
||||
});
|
||||
|
||||
test("an actual resize still shares dimensions and can return to its starting size", () => {
|
||||
for (const classic of [false, true]) {
|
||||
const { syncSizes, onTogetherUp, first, second } = resizeHandlers("column", classic);
|
||||
first.size = [350, 100];
|
||||
syncSizes();
|
||||
assert.deepEqual(Array.from(second.size), [350, 150]);
|
||||
first.size = [300, 100];
|
||||
onTogetherUp();
|
||||
assert.deepEqual(Array.from(second.size), [300, 150]);
|
||||
}
|
||||
});
|
||||
|
||||
test("edges line up on the outermost edge of the selection", () => {
|
||||
assert.deepEqual(lineUp(rects, "left").map((p) => p.x), [0, 0, 0]);
|
||||
assert.deepEqual(lineUp(rects, "right").map((p) => p.x), [140, 40, 180]); // right edge 240 for all
|
||||
|
||||
@@ -1,10 +1,28 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { stripCaption, stripRows, wiredJobs } from "../js/shared/moment_strip_math.mjs";
|
||||
import { moveMoment, retime, stripCaption, stripRows, wiredJobs } from "../js/shared/moment_strip_math.mjs";
|
||||
|
||||
const row = (start, end, extra = {}) => ({ start, end, text: "She runs", cut: false, speech: false, ...extra });
|
||||
|
||||
test("retiming keeps the list prefix ahead of the new timestamp", () => {
|
||||
for (const prefix of ["2. ", "2) ", "- ", "* ", "• ", "First, ", "secondly: ", "Step 2: "]) {
|
||||
assert.equal(retime(` ${prefix}@1 sits.`, 0, 5), ` ${prefix}5s: @1 sits.`);
|
||||
}
|
||||
});
|
||||
|
||||
test("retiming replaces all supported timestamps and ranges", () => {
|
||||
for (const prefix of ["", "2. "]) for (const time of ["3s:", "3 s,", "at 3 seconds,", "[3s]", "3.5s:", "at 3.5 secs,", "01:03.5:", "[at 1:03]", "3-6s:", "3s - 6 seconds,", "[3–6s]", "at 3—6 secs:"]) {
|
||||
assert.equal(retime(`${prefix}${time} @1 sits.`, 0, 5), `${prefix}5s: @1 sits.`, time);
|
||||
}
|
||||
});
|
||||
|
||||
test("reordering timed list items leaves times and list prefixes in their slots", () => {
|
||||
const text = "1. [0-3s] @1 walks.\n2) 00:03-6s: @1 sits.\n\nStep 3: at 6 seconds, @1 runs.";
|
||||
assert.equal(moveMoment(text, [0, 1, 3], 2, 0),
|
||||
"1. [0-3s] @1 runs.\n2) 00:03-6s: @1 walks.\n\nStep 3: at 6 seconds, @1 sits.");
|
||||
});
|
||||
|
||||
test("a row says its second and its words, and its bar sits where it plays", () => {
|
||||
const rows = stripRows({ seconds: 10, moments: [row(0, 2.5), row(2.5, 10)] });
|
||||
assert.deepEqual(rows.map((moment) => moment.second), ["0", "2.5"]);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { runInNewContext } from "node:vm";
|
||||
import * as cardMath from "../js/shared/widget_card_math.mjs";
|
||||
import * as panelLayout from "../js/shared/panel_layout.mjs";
|
||||
|
||||
import {
|
||||
ensureNodeMinHeight,
|
||||
@@ -11,7 +15,113 @@ import {
|
||||
measureLayoutWidthPadding,
|
||||
nodeHeightAfterCardChange,
|
||||
pinVuePanelHeight,
|
||||
observePanelHeight,
|
||||
} from "../js/shared/panel_layout.mjs";
|
||||
import { cardHeight, rowTops } from "../js/shared/widget_card_math.mjs";
|
||||
|
||||
test("a measured card row updates its node and sockets without changing another node", () => {
|
||||
const observed = [];
|
||||
class PanelObserver {
|
||||
constructor(callback) { this.callback = callback; this.disconnected = false; }
|
||||
observe(content) { this.content = content; observed.push(this); }
|
||||
disconnect() { this.disconnected = true; }
|
||||
}
|
||||
const element = () => ({
|
||||
style: {}, classList: { add() {}, remove() {}, toggle() {} }, children: [],
|
||||
append(...children) { this.children.push(...children); }, addEventListener() {},
|
||||
});
|
||||
const source = readFileSync(new URL("../js/shared/widget_card.mjs", import.meta.url), "utf8")
|
||||
.replace(/^import [\s\S]*?;\n/gm, "").replace(/^export \* from .*;\n/gm, "").replace(/^export /gm, "");
|
||||
const mount = runInNewContext(`${source}\nmountWidgetCard`, {
|
||||
...cardMath, ...panelLayout, BRAND: "#00b4aa",
|
||||
document: { getElementById: () => true, createElement: element },
|
||||
ResizeObserver: PanelObserver,
|
||||
observePanelHeight(row, line, content, refresh) {
|
||||
// Run the real observer helper in the same test realm as the mock.
|
||||
const saved = globalThis.ResizeObserver;
|
||||
globalThis.ResizeObserver = PanelObserver;
|
||||
try { return panelLayout.observePanelHeight(row, line, content, refresh); }
|
||||
finally { globalThis.ResizeObserver = saved; }
|
||||
},
|
||||
chainCallback(target, name, callback) {
|
||||
const previous = target[name];
|
||||
target[name] = function (...args) { previous?.apply(this, args); callback.apply(this, args); };
|
||||
},
|
||||
keepDomWidgetWidthAuto() {}, pinVuePanelHeight() {}, holdNodeMinWidth() {},
|
||||
hideWidget(widget) { widget.hidden = true; },
|
||||
});
|
||||
const definition = { panel: () => ({ element: { offsetHeight: 24 } }), height: 30, measure: true };
|
||||
const make = () => {
|
||||
const node = {
|
||||
size: [420, 100], pos: [0, 0], widgets: [{ name: "seconds", type: "text", value: "8" }],
|
||||
inputs: [{ name: "seconds", widget: { name: "seconds" }, link: null }],
|
||||
addDOMWidget(name, type, root, options) {
|
||||
const widget = { name, type, element: root, options, y: 0 };
|
||||
this.widgets.push(widget);
|
||||
return widget;
|
||||
},
|
||||
computeSize() { return this.__ausbossCard.widget.computeSize(420); },
|
||||
setSize(size) { this.size = Array.from(size); },
|
||||
};
|
||||
return { node, state: mount(node, { rows: [definition, { widget: "seconds", kind: "text" }], minWidth: 420 }) };
|
||||
};
|
||||
const first = make();
|
||||
const second = make();
|
||||
const height = first.node.size[1];
|
||||
const socketY = first.node.widgets[0].y;
|
||||
if (observed[0]) {
|
||||
observed[0].content.offsetHeight = 53;
|
||||
observed[0].callback();
|
||||
}
|
||||
assert.equal(first.node.size[1], height + 23);
|
||||
assert.equal(first.node.widgets[0].y, socketY + 23);
|
||||
assert.equal(second.node.size[1], height);
|
||||
assert.equal(definition.height, 30);
|
||||
first.node.onRemoved();
|
||||
assert.equal(observed[0].disconnected, true);
|
||||
});
|
||||
|
||||
test("wrapped panel content moves the next row and grows the card, then shrinks again", () => {
|
||||
const saved = globalThis.ResizeObserver;
|
||||
let changed;
|
||||
let disconnected = false;
|
||||
globalThis.ResizeObserver = class {
|
||||
constructor(callback) { changed = callback; }
|
||||
observe(element) { assert.equal(element, content); }
|
||||
disconnect() { disconnected = true; }
|
||||
};
|
||||
const row = { panel: () => {}, height: 30 };
|
||||
const next = { widget: "seconds" };
|
||||
const element = { style: {} };
|
||||
let height = 24;
|
||||
const content = { get offsetHeight() { return height; }, getBoundingClientRect: () => ({ height: height / 2 }) };
|
||||
const initialHeight = cardHeight([row, next]);
|
||||
const initialTop = rowTops([row, next])[1];
|
||||
let refreshed = 0;
|
||||
try {
|
||||
const observer = observePanelHeight(row, element, content, () => { refreshed += 1; });
|
||||
changed();
|
||||
assert.equal(row.height, 30);
|
||||
height = 53; // Two 24px chip rows and the 5px gap.
|
||||
changed();
|
||||
assert.equal(element.style.height, "53px");
|
||||
assert.equal(cardHeight([row, next]), initialHeight + 23);
|
||||
assert.equal(rowTops([row, next])[1], initialTop + 23);
|
||||
changed();
|
||||
assert.equal(refreshed, 1, "unchanged measurements do not resize again");
|
||||
height = 0; // A hidden panel does not erase its minimum.
|
||||
changed();
|
||||
assert.equal(row.height, 53);
|
||||
height = 24;
|
||||
changed();
|
||||
assert.equal(row.height, 30);
|
||||
assert.equal(cardHeight([row, next]), initialHeight);
|
||||
observer.disconnect();
|
||||
assert.equal(disconnected, true);
|
||||
} finally {
|
||||
globalThis.ResizeObserver = saved;
|
||||
}
|
||||
});
|
||||
|
||||
test("fillNodeHeight drops computeSize and declares a floor with no ceiling", () => {
|
||||
const widget = { computeSize: () => [0, 200], options: {} };
|
||||
|
||||
@@ -8,6 +8,7 @@ from pathlib import Path
|
||||
import sys
|
||||
import types
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
import torch
|
||||
|
||||
@@ -145,6 +146,39 @@ class GeometryTests(unittest.TestCase):
|
||||
|
||||
|
||||
class CropContractTests(unittest.TestCase):
|
||||
def test_size_guard_allows_a_large_source_without_amplifying_it(self):
|
||||
image = torch.zeros((2, 32, 32, 4))
|
||||
mask = torch.ones((1, 32, 32))
|
||||
with patch.object(inpaint_helpers, "_MAX_CROP_ELEMENTS", 1024):
|
||||
cropped, _, _ = build_crop(image, mask, 1.0, 0, 32)
|
||||
self.assertEqual(cropped.shape, image.shape)
|
||||
with self.assertRaisesRegex(ValueError, "too large"):
|
||||
build_crop(image, mask, 1.0, 0, 32, target_width=64, target_height=64)
|
||||
|
||||
def test_extreme_target_aspects_fail_before_allocating_padding(self):
|
||||
image = torch.zeros((1, 2048, 2048, 3))
|
||||
mask = torch.ones((1, 2048, 2048))
|
||||
for keep_inside in (False, True):
|
||||
for width, height in ((16384, 32), (32, 16384)):
|
||||
with self.subTest(keep_inside=keep_inside, target=(width, height)):
|
||||
with patch.object(inpaint_helpers, "_replicate_pad_image", side_effect=AssertionError("unsafe allocation")) as pad:
|
||||
with self.assertRaisesRegex(ValueError, "Crop For Inpaint.*too large"):
|
||||
build_crop(image, mask, 1.0, 0, 32, target_width=width, target_height=height, keep_inside=keep_inside)
|
||||
pad.assert_not_called()
|
||||
|
||||
def test_size_guard_covers_extension_native_context_and_sampler_output(self):
|
||||
image = torch.zeros((1, 32, 32, 3))
|
||||
mask = torch.ones((1, 32, 32))
|
||||
for options in ({"extend_right": 100_000_000}, {"context_factor": 10000},
|
||||
{"target_width": 16384, "target_height": 16384}):
|
||||
with self.subTest(options=options):
|
||||
with (
|
||||
patch.object(inpaint_helpers, "_replicate_pad_image", side_effect=AssertionError("unsafe allocation")),
|
||||
patch.object(inpaint_helpers, "_resize_image", side_effect=AssertionError("unsafe resize")),
|
||||
):
|
||||
with self.assertRaisesRegex(ValueError, "Crop For Inpaint.*too large"):
|
||||
build_crop(image, mask, blend_pixels=0, output_multiple=32, **{"context_factor": 1.0, **options})
|
||||
|
||||
def test_stitcher_schema(self):
|
||||
image = rand_image(1, 64, 96)
|
||||
mask = box_mask(64, 96, 24, 40, 40, 56)
|
||||
|
||||
Reference in New Issue
Block a user