diff --git a/extensions-builtin/sd_forge_image_stitch/scripts/image_stitch.py b/extensions-builtin/sd_forge_image_stitch/scripts/image_stitch.py index c7151465..89867c98 100644 --- a/extensions-builtin/sd_forge_image_stitch/scripts/image_stitch.py +++ b/extensions-builtin/sd_forge_image_stitch/scripts/image_stitch.py @@ -12,15 +12,13 @@ from modules.shared import device, opts from modules.ui_components import FormRow, InputAccordion t2i_info = """ -For Flux-Kontext / Flux.2-Klein / Qwen-Image-Edit
-Use in txt2img to achieve the effect of empty latent with custom resolution
-NOTE: This doesn't actually stitch the images +For Flux.1-Kontext / Flux.2-Klein / Qwen-Image-Edit ; Use in txt2img to achieve the effect of empty latent with custom resolution
+Note: This doesn't actually stitch the images ; Tip: Use the "Image to Upload" to paste images """ i2i_info = """ -For Flux-Kontext / Flux.2-Klein / Qwen-Image-Edit
-Use in img2img to achieve the effect of multiple input images
-NOTE: This doesn't actually stitch the images +For Flux.1-Kontext / Flux.2-Klein / Qwen-Image-Edit ; Use in img2img to achieve the effect of multiple input images
+Note: This doesn't actually stitch the images ; Tip: Use the "Image to Upload" to paste images """ @@ -47,8 +45,8 @@ class ImageStitch(scripts.Script): container=False, show_download_button=False, show_share_button=False, - label="Reference Images", - min_width=384, + label="Reference Image(s)", + min_width=512, height=384, columns=3, rows=1, @@ -57,39 +55,94 @@ class ImageStitch(scripts.Script): elem_id=self.elem_id("ref_latent"), ) + select_index = gr.State(-1) + + def on_select(evt: gr.SelectData) -> int: + return evt.index + + references.select( + fn=on_select, + outputs=[select_index], + queue=False, + show_progress=False, + ) + with FormRow(): upload = gr.Image( - height=256, - width=256, + height=225, + width=225, sources="upload", type="pil", - label="Paste Image", + label="Image to Upload", show_download_button=False, show_share_button=False, ) with gr.Column(): - btn_upload = gr.Button("Upload Pasted Image") - btn_clear = gr.Button("Clear All References") - max_dim = gr.Slider( - minimum=0, - maximum=2048, - value=1024, - step=256, - label="Maximum Side Length", - info="reduce VRAM usage during encoding ; set to 0 for no limit", - ) + btn_upload = gr.Button("Append Pasted Image") + btn_replace = gr.Button("Replace Selected Image") + btn_delete = gr.Button("Delete Selected Image", variant="stop") + btn_clear = gr.Button("Clear All References", variant="stop") + + max_dim = gr.Slider( + minimum=0, + maximum=2048, + value=1024, + step=256, + label="Maximum Side Length", + info="reduce VRAM usage during encoding ; apply to all reference images ; set to 0 for no limit", + ) def _upload(gallery: list[tuple[Image.Image, str]], image: Image.Image): - if image is None: + if not image: return [gr.skip(), gr.skip()] - elif gallery is None: + elif not gallery: gallery = [(image, None)] else: gallery.append((image, None)) return [gr.update(value=gallery), gr.update(value=None)] - btn_upload.click(fn=_upload, inputs=[references, upload], outputs=[references, upload], queue=False, show_progress=False) - btn_clear.click(fn=lambda: [], outputs=[references], queue=False, show_progress=False) + def _replace(index: int, gallery: list[tuple[Image.Image, str]], image: Image.Image): + if not image or not gallery or index < 0 or index >= len(gallery): + return [-1, gr.skip(), gr.skip()] + gallery[index] = (image, None) + return [-1, gr.update(value=gallery), gr.update(value=None)] + + def _delete(index: int, gallery: list[tuple[Image.Image, str]]): + if not gallery or index < 0 or index >= len(gallery): + return [-1, gr.skip()] + gallery.pop(index) + return [-1, gr.update(value=gallery)] + + btn_upload.click( + fn=_upload, + inputs=[references, upload], + outputs=[references, upload], + queue=False, + show_progress=False, + ) + + btn_replace.click( + fn=_replace, + inputs=[select_index, references, upload], + outputs=[select_index, references, upload], + queue=False, + show_progress=False, + ) + + btn_delete.click( + fn=_delete, + inputs=[select_index, references], + outputs=[select_index, references], + queue=False, + show_progress=False, + ) + + btn_clear.click( + fn=lambda: [-1, gr.update(value=[])], + outputs=[select_index, references], + queue=False, + show_progress=False, + ) return [enable, references, max_dim]