From 425f51bb2524914a5a8ed834d144b82cc168cc75 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 28 Nov 2024 13:28:46 -0500 Subject: [PATCH] simplify impaint/sketch canvas handling Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 5 ++- javascript/black-teal.css | 1 - javascript/imageMaskFix.js | 38 -------------------- javascript/sdnext.css | 20 +++++++---- javascript/ui.js | 2 +- modules/img2img.py | 14 ++++---- modules/ui_control.py | 4 +-- modules/ui_img2img.py | 71 ++++++++++++++++++-------------------- 8 files changed, 60 insertions(+), 95 deletions(-) delete mode 100644 javascript/imageMaskFix.js diff --git a/CHANGELOG.md b/CHANGELOG.md index f88c78302..919041bde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,7 +48,8 @@ - control: hide preview column by default - control: optionn to hide input column - control: add stats - - browser->server logging framework + - browser -> server logging framework + - add addtional themes: `black-reimagined` - **Sampler** improvements - Euler FlowMatch: add sigma methods (*karras/exponential/betas*) - DPM FlowMatch: update all and add sigma methods @@ -65,6 +66,8 @@ - allow xyz-grid with multi-axis s&r - fix xyz-grid with lora - fix api script callbacks +- fix gpu memory monitoring +- simplify img2img/inpaint/sketch canvas handling ## Update for 2024-11-21 diff --git a/javascript/black-teal.css b/javascript/black-teal.css index b73f9fdc7..2ebf32e96 100644 --- a/javascript/black-teal.css +++ b/javascript/black-teal.css @@ -108,7 +108,6 @@ fieldset .gr-block.gr-box, label.block span { padding: 0; margin-top: -4px; } .eta-bar { display: none !important } .gradio-slider { max-width: 200px; } .gradio-slider input[type="number"] { background: var(--neutral-950); margin-top: 2px; } -.gradio-image { height: unset !important; } svg.feather.feather-image, .feather .feather-image { display: none } .gap-2 { padding-top: 8px; } .gr-box > div > div > input.gr-text-input { right: 0; width: 4em; padding: 0; top: -12px; border: none; max-height: 20px; } diff --git a/javascript/imageMaskFix.js b/javascript/imageMaskFix.js deleted file mode 100644 index fd37caf90..000000000 --- a/javascript/imageMaskFix.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * temporary fix for https://github.com/AUTOMATIC1111/stable-diffusion-webui/issues/668 - * @see https://github.com/gradio-app/gradio/issues/1721 - */ -function imageMaskResize() { - const canvases = gradioApp().querySelectorAll('#img2maskimg .touch-none canvas'); - if (!canvases.length) { - window.removeEventListener('resize', imageMaskResize); - return; - } - const wrapper = canvases[0].closest('.touch-none'); - const previewImage = wrapper.previousElementSibling; - if (!previewImage.complete) { - previewImage.addEventListener('load', imageMaskResize); - return; - } - const w = previewImage.width; - const h = previewImage.height; - const nw = previewImage.naturalWidth; - const nh = previewImage.naturalHeight; - const portrait = nh > nw; - const wW = Math.min(w, portrait ? h / nh * nw : w / nw * nw); - const wH = Math.min(h, portrait ? h / nh * nh : w / nw * nh); - wrapper.style.width = `${wW}px`; - wrapper.style.height = `${wH}px`; - wrapper.style.left = '0px'; - wrapper.style.top = '0px'; - canvases.forEach((c) => { - c.style.width = ''; - c.style.height = ''; - c.style.maxWidth = '100%'; - c.style.maxHeight = '100%'; - c.style.objectFit = 'contain'; - }); -} - -onAfterUiUpdate(imageMaskResize); -window.addEventListener('resize', imageMaskResize); diff --git a/javascript/sdnext.css b/javascript/sdnext.css index 240b7492f..c5145c973 100644 --- a/javascript/sdnext.css +++ b/javascript/sdnext.css @@ -30,6 +30,19 @@ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button { margin-left .hidden { display: none; } .tabitem { padding: 0 !important; } +/* gradio image/canvas elements */ +.image-container { overflow: auto; } +/* +.gradio-image { min-height: fit-content; } +.gradio-image img { object-fit: contain; } +*/ +/* +.gradio-image { min-height: 200px !important; } +.image-container { height: unset !important; } +.control-image { height: unset !important; } +#img2img_sketch, #img2maskimg, #inpaint_sketch { overflow: overlay !important; resize: auto; background: var(--panel-background-fill); z-index: 5; } +*/ + /* color elements */ .gradio-dropdown, .block.gradio-slider, .block.gradio-checkbox, .block.gradio-textbox, .block.gradio-radio, .block.gradio-checkboxgroup, .block.gradio-number, .block.gradio-colorpicker { border-width: 0 !important; box-shadow: none !important;} .gradio-accordion { padding-top: var(--spacing-md) !important; padding-right: 0 !important; padding-bottom: 0 !important; color: var(--body-text-color); } @@ -87,8 +100,6 @@ button.custom-button { border-radius: var(--button-large-radius); padding: var(- .performance .time { margin-right: 0; } .thumbnails { background: var(--body-background-fill); } .prompt textarea { resize: vertical; } -.image-container { height: unset !important; } -.control-image { height: unset !important; } .grid-wrap { overflow-y: auto !important; } #control_results { margin: 0; padding: 0; } #txt2img_gallery, #img2img_gallery { height: 50vh; } @@ -106,7 +117,6 @@ button.custom-button { border-radius: var(--button-large-radius); padding: var(- #txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt, #control_prompt, #control_neg_prompt { display: contents; } #txt2img_actions_column, #img2img_actions_column, #control_actions { flex-flow: wrap; justify-content: space-between; } - .interrogate-clip { position: absolute; right: 6em; top: 8px; max-width: fit-content; background: none !important; z-index: 50; } .interrogate-blip { position: absolute; right: 4em; top: 8px; max-width: fit-content; background: none !important; z-index: 50; } .interrogate-col { min-width: 0 !important; max-width: fit-content; margin-right: var(--spacing-xxl); } @@ -119,8 +129,6 @@ div#extras_scale_to_tab div.form { flex-direction: row; } #img2img_unused_scale_by_slider { visibility: hidden; width: 0.5em; max-width: 0.5em; min-width: 0.5em; } .inactive{ opacity: 0.5; } div#extras_scale_to_tab div.form { flex-direction: row; } -#mode_img2img .gradio-image>div.fixed-height, #mode_img2img .gradio-image>div.fixed-height img{ height: 480px !important; max-height: 480px !important; min-height: 480px !important; } -#img2img_sketch, #img2maskimg, #inpaint_sketch { overflow: overlay !important; resize: auto; background: var(--panel-background-fill); z-index: 5; } .image-buttons button { min-width: auto; } .infotext { overflow-wrap: break-word; line-height: 1.5em; font-size: 0.95em !important; } .infotext > p { white-space: pre-wrap; color: var(--block-info-text-color) !important; } @@ -380,8 +388,6 @@ div:has(>#tab-gallery-folders) { flex-grow: 0 !important; background-color: var( #img2img_actions_column { display: flex; min-width: fit-content !important; flex-direction: row;justify-content: space-evenly; align-items: center;} #txt2img_generate_box, #img2img_generate_box, #txt2img_enqueue_wrapper,#img2img_enqueue_wrapper {display: flex;flex-direction: column;height: 4em !important;align-items: stretch;justify-content: space-evenly;} #img2img_interface, #img2img_results, #img2img_footer p { text-wrap: wrap; min-width: 100% !important; max-width: 100% !important;} /* maintain single column for from image operations on larger mobile devices */ - #img2img_sketch, #img2maskimg, #inpaint_sketch {display: flex; overflow: auto !important; resize: none !important; } /* fix inpaint image display being too large for mobile displays */ - #img2maskimg canvas { width: auto !important; max-height: 100% !important; height: auto !important; } #txt2img_sampler, #txt2img_batch, #txt2img_seed_group, #txt2img_advanced, #txt2img_second_pass, #img2img_sampling_group, #img2img_resize_group, #img2img_batch_group, #img2img_seed_group, #img2img_denoise_group, #img2img_advanced_group { width: 100% !important; } /* fix from text/image UI elements to prevent them from moving around within the UI */ #img2img_resize_group .gradio-radio>div { display: flex; flex-direction: column; width: unset !important; } #inpaint_controls div { display:flex;flex-direction: row;} diff --git a/javascript/ui.js b/javascript/ui.js index 81d1c67e4..3e3f14390 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -139,7 +139,7 @@ function switch_to_inpaint(...args) { return Array.from(arguments); } -function switch_to_inpaint_sketch(...args) { +function switch_to_composite(...args) { switchToTab('Image'); switch_to_img2img_tab(3); return Array.from(arguments); diff --git a/modules/img2img.py b/modules/img2img.py index 8274386cc..077df1259 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -164,12 +164,7 @@ def img2img(id_task: str, state: str, mode: int, return [], '', '', 'Error: init image not provided' image = init_img.convert("RGB") mask = None - elif mode == 1: # img2img sketch - if sketch is None: - return [], '', '', 'Error: sketch image not provided' - image = sketch.convert("RGB") - mask = None - elif mode == 2: # inpaint + elif mode == 1: # inpaint if init_img_with_mask is None: return [], '', '', 'Error: init image with mask not provided' image = init_img_with_mask["image"] @@ -177,7 +172,12 @@ def img2img(id_task: str, state: str, mode: int, alpha_mask = ImageOps.invert(image.split()[-1]).convert('L').point(lambda x: 255 if x > 0 else 0, mode='1') mask = ImageChops.lighter(alpha_mask, mask.convert('L')).convert('L') image = image.convert("RGB") - elif mode == 3: # inpaint sketch + elif mode == 2: # sketch + if sketch is None: + return [], '', '', 'Error: sketch image not provided' + image = sketch.convert("RGB") + mask = None + elif mode == 3: # composite if inpaint_color_sketch is None: return [], '', '', 'Error: color sketch image not provided' image = inpaint_color_sketch diff --git a/modules/ui_control.py b/modules/ui_control.py index f4329663a..072d9b9c9 100644 --- a/modules/ui_control.py +++ b/modules/ui_control.py @@ -13,7 +13,7 @@ from modules import errors, shared, progress, ui_components, ui_symbols, ui_comm from modules import ui_control_helpers as helpers -gr_height = None +gr_height = 512 max_units = shared.opts.control_max_units units: list[unit.Unit] = [] # main state variable controls: list[gr.component] = [] # list of gr controls @@ -135,7 +135,7 @@ def create_ui(_blocks: gr.Blocks=None): with gr.Row(): input_type = gr.Radio(label="Input type", choices=['Control only', 'Init image same as control', 'Separate init image'], value='Control only', type='index', elem_id='control_input_type') with gr.Row(): - denoising_strength = gr.Slider(minimum=0.01, maximum=1.0, step=0.01, label='Denoising strength', value=0.50, elem_id="control_input_denoising_strength") + denoising_strength = gr.Slider(minimum=0.01, maximum=1.0, step=0.01, label='Denoising strength', value=0.30, elem_id="control_input_denoising_strength") with gr.Accordion(open=False, label="Size", elem_id="control_size", elem_classes=["small-accordion"]): with gr.Tabs(): diff --git a/modules/ui_img2img.py b/modules/ui_img2img.py index 046c181ce..3c3d63656 100644 --- a/modules/ui_img2img.py +++ b/modules/ui_img2img.py @@ -1,7 +1,6 @@ import os from PIL import Image import gradio as gr -import numpy as np from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call from modules import timer, shared, ui_common, ui_sections, generation_parameters_copypaste, processing_vae @@ -56,7 +55,7 @@ def create_ui(): def add_copy_image_controls(tab_name, elem): with gr.Row(variant="compact", elem_id=f"img2img_copy_to_{tab_name}"): - for title, name in zip(['➠ Image', '➠ Sketch', '➠ Inpaint', '➠ Composite'], ['img2img', 'sketch', 'inpaint', 'inpaint_sketch']): + for title, name in zip(['➠ Image', '➠ Inpaint', '➠ Sketch', '➠ Composite'], ['img2img', 'sketch', 'inpaint', 'composite']): if name == tab_name: gr.Button(title, elem_id=f'copy_to_{name}', interactive=False) copy_image_destinations[name] = elem @@ -67,33 +66,36 @@ def create_ui(): with gr.Tabs(elem_id="mode_img2img"): img2img_selected_tab = gr.State(0) # pylint: disable=abstract-class-instantiated state = gr.Textbox(value='', visible=False) - with gr.TabItem('Image', id='img2img', elem_id="img2img_img2img_tab") as tab_img2img: - init_img = gr.Image(label="Image for img2img", elem_id="img2img_image", show_label=False, source="upload", interactive=True, type="pil", tool="editor", image_mode="RGBA") + with gr.TabItem('Image', id='img2img_image', elem_id="img2img_image_tab") as tab_img2img: + img_init = gr.Image(label="", elem_id="img2img_image", show_label=False, source="upload", interactive=True, type="pil", tool="editor", image_mode="RGBA", height=512) interrogate_clip, interrogate_booru = ui_sections.create_interrogate_buttons('img2img') - add_copy_image_controls('img2img', init_img) + add_copy_image_controls('img2img', img_init) - with gr.TabItem('Sketch', id='img2img_sketch', elem_id="img2img_img2img_sketch_tab") as tab_sketch: - sketch = gr.Image(label="Image for img2img", elem_id="img2img_sketch", show_label=False, source="upload", interactive=True, type="pil", tool="color-sketch", image_mode="RGBA") - add_copy_image_controls('sketch', sketch) + with gr.TabItem('Inpaint', id='img2img_inpaint', elem_id="img2img_inpaint_tab") as tab_inpaint: + img_inpaint = gr.Image(label="", elem_id="img2img_inpaint", show_label=False, source="upload", interactive=True, type="pil", tool="sketch", image_mode="RGBA", height=512) + add_copy_image_controls('inpaint', img_inpaint) - with gr.TabItem('Inpaint', id='inpaint', elem_id="img2img_inpaint_tab") as tab_inpaint: - init_img_with_mask = gr.Image(label="Image for inpainting with mask", show_label=False, elem_id="img2maskimg", source="upload", interactive=True, type="pil", tool="sketch", image_mode="RGBA") - add_copy_image_controls('inpaint', init_img_with_mask) + with gr.TabItem('Sketch', id='img2img_sketch', elem_id="img2img_sketch_tab") as tab_sketch: + img_sketch = gr.Image(label="", elem_id="img2img_sketch", show_label=False, source="upload", interactive=True, type="pil", tool="color-sketch", image_mode="RGBA", height=512) + add_copy_image_controls('sketch', img_sketch) - with gr.TabItem('Composite', id='inpaint_sketch', elem_id="img2img_inpaint_sketch_tab") as tab_inpaint_color: - inpaint_color_sketch = gr.Image(label="Color sketch inpainting", show_label=False, elem_id="inpaint_sketch", source="upload", interactive=True, type="pil", tool="color-sketch", image_mode="RGBA") - inpaint_color_sketch_orig = gr.State(None) # pylint: disable=abstract-class-instantiated - add_copy_image_controls('inpaint_sketch', inpaint_color_sketch) + with gr.TabItem('Composite', id='img2img_composite', elem_id="img2img_composite_tab") as tab_inpaint_color: + img_composite = gr.Image(label="", show_label=False, elem_id="img2img_composite", source="upload", interactive=True, type="pil", tool="color-sketch", image_mode="RGBA", height=512) + img_composite_orig = gr.State(None) # pylint: disable=abstract-class-instantiated + img_composite_orig_update = False - def update_orig(image, state): - if image is not None: - same_size = state is not None and state.size == image.size - has_exact_match = np.any(np.all(np.array(image) == np.array(state), axis=-1)) - edited = same_size and has_exact_match - return image if not edited or state is None else state - return state + def fn_img_composite_upload(): + nonlocal img_composite_orig_update + img_composite_orig_update = True + def fn_img_composite_change(img, img_composite): + nonlocal img_composite_orig_update + res = img if img_composite_orig_update else img_composite + img_composite_orig_update = False + return res - inpaint_color_sketch.change(update_orig, [inpaint_color_sketch, inpaint_color_sketch_orig], inpaint_color_sketch_orig) + img_composite.upload(fn=fn_img_composite_upload, inputs=[], outputs=[]) + img_composite.change(fn=fn_img_composite_change, inputs=[img_composite, img_composite_orig], outputs=[img_composite_orig]) + add_copy_image_controls('composite', img_composite) with gr.TabItem('Upload', id='inpaint_upload', elem_id="img2img_inpaint_upload_tab") as tab_inpaint_upload: init_img_inpaint = gr.Image(label="Image for img2img", show_label=False, source="upload", interactive=True, type="pil", elem_id="img_inpaint_base") @@ -120,13 +122,13 @@ def create_ui(): with gr.Accordion(open=False, label="Sampler", elem_classes=["small-accordion"], elem_id="img2img_sampler_group"): steps, sampler_index = ui_sections.create_sampler_and_steps_selection(None, "img2img") ui_sections.create_sampler_options('img2img') - resize_mode, resize_name, resize_context, width, height, scale_by, selected_scale_tab = ui_sections.create_resize_inputs('img2img', [init_img, sketch], latent=True, non_zero=False) + resize_mode, resize_name, resize_context, width, height, scale_by, selected_scale_tab = ui_sections.create_resize_inputs('img2img', [img_init, img_sketch], latent=True, non_zero=False) batch_count, batch_size = ui_sections.create_batch_inputs('img2img', accordion=True) seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w = ui_sections.create_seed_inputs('img2img') with gr.Accordion(open=False, label="Denoise", elem_classes=["small-accordion"], elem_id="img2img_denoise_group"): with gr.Row(): - denoising_strength = gr.Slider(minimum=0.0, maximum=0.99, step=0.01, label='Denoising strength', value=0.50, elem_id="img2img_denoising_strength") + denoising_strength = gr.Slider(minimum=0.0, maximum=0.99, step=0.01, label='Denoising strength', value=0.30, elem_id="img2img_denoising_strength") refiner_start = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Denoise start', value=0.0, elem_id="img2img_refiner_start") full_quality, tiling, hidiffusion, cfg_scale, clip_skip, image_cfg_scale, diffusers_guidance_rescale, pag_scale, pag_adaptive, cfg_end = ui_sections.create_advanced_inputs('img2img') @@ -167,13 +169,8 @@ def create_ui(): img2img_args = [ dummy_component1, state, dummy_component2, img2img_prompt, img2img_negative_prompt, img2img_prompt_styles, - init_img, - sketch, - init_img_with_mask, - inpaint_color_sketch, - inpaint_color_sketch_orig, - init_img_inpaint, - init_mask_inpaint, + img_init, img_sketch, img_inpaint, img_composite, img_composite_orig, + init_img_inpaint, init_mask_inpaint, steps, sampler_index, mask_blur, mask_alpha, @@ -225,10 +222,7 @@ def create_ui(): img2img_batch_files, img2img_batch_input_dir, img2img_batch_output_dir, - init_img, - sketch, - init_img_with_mask, - inpaint_color_sketch, + img_init, img_sketch, img_inpaint, img_composite, init_img_inpaint, ], outputs=[img2img_prompt, dummy_component], @@ -285,7 +279,8 @@ def create_ui(): (seed_resize_from_h, "Seed resize from-2"), *modules.scripts.scripts_img2img.infotext_fields ] - generation_parameters_copypaste.add_paste_fields("img2img", init_img, img2img_paste_fields, override_settings) - generation_parameters_copypaste.add_paste_fields("inpaint", init_img_with_mask, img2img_paste_fields, override_settings) + generation_parameters_copypaste.add_paste_fields("img2img", img_init, img2img_paste_fields, override_settings) + generation_parameters_copypaste.add_paste_fields("sketch", img_sketch, img2img_paste_fields, override_settings) + generation_parameters_copypaste.add_paste_fields("inpaint", img_inpaint, img2img_paste_fields, override_settings) img2img_bindings = generation_parameters_copypaste.ParamBinding(paste_button=img2img_paste, tabname="img2img", source_text_component=img2img_prompt, source_image_component=None) generation_parameters_copypaste.register_paste_params_button(img2img_bindings)