diff --git a/CHANGELOG.md b/CHANGELOG.md index e5456cdbc..359b2aa53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - add setting to control `cudnn` enable/disable - change `vlm` beams to 1 by default for faster response - update diffusers + - **controlnet** allow processor to keep aspect-ratio for override images based on i2i or t2i resolution - **Fixes** - `qwen` improve lora compatibility - `chrono` transformers handling diff --git a/extensions-builtin/sdnext-modernui b/extensions-builtin/sdnext-modernui index 9f0e3f336..388c22343 160000 --- a/extensions-builtin/sdnext-modernui +++ b/extensions-builtin/sdnext-modernui @@ -1 +1 @@ -Subproject commit 9f0e3f3360f5927d05ca8a2e728c80fbbda4c941 +Subproject commit 388c2234378fb0c45575d4e7993470377abfa790 diff --git a/javascript/sdnext.css b/javascript/sdnext.css index a099efa8d..2821fb164 100644 --- a/javascript/sdnext.css +++ b/javascript/sdnext.css @@ -2208,6 +2208,18 @@ div:has(>#tab-gallery-folders) { font-weight: normal; } +.controlnet-controls .styler { + display: flex; + flex-direction: row; + justify-self: flex-start; +} + +.controlnet-controls .styler .form { + display: flex; + flex-flow: nowrap; + flex: none; +} + #civitai_token textarea, #hf_token textarea, #setting_huggingface_token textarea { filter: blur(4px); } diff --git a/modules/control/processor.py b/modules/control/processor.py index 2022c7959..5a58c09d4 100644 --- a/modules/control/processor.py +++ b/modules/control/processor.py @@ -55,8 +55,8 @@ def preprocess_image( jobid = shared.state.begin('Preprocess') # run resize before - if p.resize_mode_before != 0 and p.resize_name_before != 'None': - if p.selected_scale_tab_before == 1 and input_image is not None: + if (p.resize_mode_before != 0) and (p.resize_name_before != 'None'): + if (p.selected_scale_tab_before == 1) and (input_image is not None): p.width_before, p.height_before = int(input_image.width * p.scale_by_before), int(input_image.height * p.scale_by_before) if input_image is not None: debug_log(f'Control resize: op=before image={input_image} width={p.width_before} height={p.height_before} mode={p.resize_mode_before} name={p.resize_name_before} context="{p.resize_context_before}"') @@ -64,10 +64,10 @@ def preprocess_image( p.init_img_width = getattr(p, 'init_img_width', input_image.width) # pylint: disable=attribute-defined-outside-init p.init_img_height = getattr(p, 'init_img_height', input_image.height) # pylint: disable=attribute-defined-outside-init input_image = images.resize_image(p.resize_mode_before, input_image, p.width_before, p.height_before, p.resize_name_before, context=p.resize_context_before) - if input_image is not None and init_image is not None and init_image.size != input_image.size: + if (input_image is not None) and (init_image is not None) and (init_image.size != input_image.size): debug_log(f'Control resize init: image={init_image} target={input_image}') init_image = images.resize_image(resize_mode=1, im=init_image, width=input_image.width, height=input_image.height) - if input_image is not None and p.override is not None and p.override.size != input_image.size: + if (input_image is not None) and (p.override is not None) and (p.override.size != input_image.size): debug_log(f'Control resize override: image={p.override} target={input_image}') p.override = images.resize_image(resize_mode=1, im=p.override, width=input_image.width, height=input_image.height) if input_image is not None: @@ -100,10 +100,16 @@ def preprocess_image( blended_image = None for i, process in enumerate(active_process): # list[image] debug_log(f'Control: i={i+1} process="{process.processor_id}" input={masked_image} override={process.override}') + if p.resize_mode_before != 0: + resize_mode = p.resize_mode_before + else: + resize_mode = 3 if shared.opts.control_aspect_ratio else 1 processed_image = process( image_input=masked_image, + width=p.width, + height=p.height, mode='RGB', - resize_mode=p.resize_mode_before, + resize_mode=resize_mode, resize_name=p.resize_name_before, scale_tab=p.selected_scale_tab_before, scale_by=p.scale_by_before, diff --git a/modules/control/processors.py b/modules/control/processors.py index 4d66bfac6..a5985639f 100644 --- a/modules/control/processors.py +++ b/modules/control/processors.py @@ -268,12 +268,14 @@ class Processor(): display(e, 'Control Processor load') return f'Processor load filed: {processor_id}' - def __call__(self, image_input: Image, mode: str = 'RGB', resize_mode: int = 0, resize_name: str = 'None', scale_tab: int = 1, scale_by: float = 1.0, local_config: dict = {}): + def __call__(self, image_input: Image, mode: str = 'RGB', width: int = 0, height: int = 0, resize_mode: int = 0, resize_name: str = 'None', scale_tab: int = 1, scale_by: float = 1.0, local_config: dict = {}): if self.override is not None: debug(f'Control Processor: id="{self.processor_id}" override={self.override}') - if image_input is not None and image_input.size != self.override.size: - debug(f'Control resize: op=override image={self.override} width={image_input.width} height={image_input.height} mode={resize_mode} name={resize_name}') - image_input = images.resize_image(resize_mode, self.override, image_input.width, image_input.height, resize_name) + width = image_input.width if image_input is not None else width + height = image_input.height if image_input is not None else height + if (width != self.override.width) or (height != self.override.height): + debug(f'Control resize: op=override image={self.override} width={width} height={height} mode={resize_mode} name={resize_name}') + image_input = images.resize_image(resize_mode, self.override, width, height, resize_name) else: image_input = self.override if resize_mode != 0 and resize_name != 'None': diff --git a/modules/control/units/controlnet.py b/modules/control/units/controlnet.py index ca7656f76..0108cbfa4 100644 --- a/modules/control/units/controlnet.py +++ b/modules/control/units/controlnet.py @@ -356,7 +356,7 @@ class ControlNet(): self.model.to_empty(device=self.device) # model could be sparse if "Control" in opts.sdnq_quantize_weights: try: - log.debug(f'Control {what} model SDNQ Compress: id="{model_id}"') + log.debug(f'Control {what} model SDNQ quantize: id="{model_id}"') from modules.model_quant import sdnq_quantize_model self.model = sdnq_quantize_model(self.model) except Exception as e: diff --git a/modules/shared.py b/modules/shared.py index 797681cc5..0ae7d2abb 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -745,7 +745,8 @@ options_templates.update(options_section(('hidden_options', "Hidden options"), { "tooltips": OptionInfo("UI Tooltips", "UI tooltips", gr.Radio, {"choices": ["None", "Browser default", "UI tooltips"], "visible": False}), # control settings are handled separately - "control_hires": OptionInfo(False, "Use control during hires", gr.Checkbox, {"visible": False}), + "control_hires": OptionInfo(False, "Hires use Control", gr.Checkbox, {"visible": False}), + "control_aspect_ratio": OptionInfo(False, "Aspect ratio resize", gr.Checkbox, {"visible": False}), "control_max_units": OptionInfo(4, "Maximum number of units", gr.Slider, {"minimum": 1, "maximum": 10, "step": 1, "visible": False}), "control_tiles": OptionInfo("1x1, 1x2, 1x3, 1x4, 2x1, 2x1, 2x2, 2x3, 2x4, 3x1, 3x2, 3x3, 3x4, 4x1, 4x2, 4x3, 4x4", "Tiling options", gr.Textbox, {"visible": False}), "control_move_processor": OptionInfo(False, "Processor move to CPU after use", gr.Checkbox, {"visible": False}), diff --git a/modules/ui_control_elements.py b/modules/ui_control_elements.py index e45bd640b..6c207087a 100644 --- a/modules/ui_control_elements.py +++ b/modules/ui_control_elements.py @@ -28,7 +28,12 @@ def create_ui_elements(units, result_txt, preview_process): enabled = True if i==0 else False with gr.Accordion(f'ControlNet unit {i+1}', visible= i < num_controlnet_units.value, elem_classes='control-unit') as unit_ui: with gr.Row(): - enabled_cb = gr.Checkbox(enabled, label='Active', container=False, show_label=True, elem_id=f'control_unit-{i}-enabled') + with gr.Group(elem_id=f'controlnet_unit-{i}-controls', elem_classes='controlnet-controls'): + enabled_cb = gr.Checkbox(enabled, label='Active', container=False, show_label=True, elem_id=f'control_unit-{i}-enabled') + image_upload = gr.UploadButton(label=ui_symbols.upload, file_types=['image'], elem_classes=['form', 'gradio-button', 'tool'], elem_id=f'controlnet_unit-{i}-upload') + image_reuse= ui_components.ToolButton(value=ui_symbols.reuse, elem_id=f'controlnet_unit-{i}-reuse') + reset_btn = ui_components.ToolButton(value=ui_symbols.reset, elem_id=f'controlnet_unit-{i}-reset') + preview_btn = ui_components.ToolButton(value=ui_symbols.preview, elem_id=f'controlnet_unit-{i}-preview') process_id = gr.Dropdown(label="Processor", choices=processors.list_models(), value='None', elem_id=f'control_unit-{i}-process_name') model_id = gr.Dropdown(label="ControlNet", choices=controlnet.list_models(), value='None', elem_id=f'control_unit-{i}-model_name') ui_common.create_refresh_button(model_id, controlnet.list_models, lambda: {"choices": controlnet.list_models(refresh=True)}, f'controlnet_models_{i}_refresh') @@ -37,10 +42,6 @@ def create_ui_elements(units, result_txt, preview_process): control_start = gr.Slider(label="CN Start", minimum=0.0, maximum=1.0, step=0.05, value=0, elem_id=f'control_unit-{i}-start') control_end = gr.Slider(label="CN End", minimum=0.0, maximum=1.0, step=0.05, value=1.0, elem_id=f'control_unit-{i}-end') control_tile = gr.Dropdown(label="CN Tiles", choices=[x.strip() for x in shared.opts.control_tiles.split(',') if 'x' in x], value='1x1', visible=False, elem_id=f'control_unit-{i}-tile') - reset_btn = ui_components.ToolButton(value=ui_symbols.reset, elem_id=f'controlnet_unit-{i}-reset') - image_upload = gr.UploadButton(label=ui_symbols.upload, file_types=['image'], elem_classes=['form', 'gradio-button', 'tool'], elem_id=f'controlnet_unit-{i}-upload') - image_reuse= ui_components.ToolButton(value=ui_symbols.reuse, elem_id=f'controlnet_unit-{i}-reuse') - btn_preview= ui_components.ToolButton(value=ui_symbols.preview, elem_id=f'controlnet_unit-{i}-preview') image_preview = gr.Image(label="Input", type="pil", height=128, width=128, visible=False, interactive=True, show_label=False, show_download_button=False, container=False, elem_id=f'controlnet_unit-{i}-override') controlnet_ui_units.append(unit_ui) units.append(unit.Unit( @@ -54,7 +55,7 @@ def create_ui_elements(units, result_txt, preview_process): model_id = model_id, model_strength = model_strength, preview_process = preview_process, - preview_btn = btn_preview, + preview_btn = preview_btn, image_upload = image_upload, image_reuse = image_reuse, image_preview = image_preview, @@ -253,10 +254,6 @@ def create_ui_elements(units, result_txt, preview_process): with gr.Group(elem_classes=['processor-group']): settings = [] with gr.Accordion('Global', open=True, elem_classes=['processor-settings']): - control_hires = gr.Checkbox(label="Use control during hires", value=shared.opts.control_hires, elem_id='control_hires') - def set_control_hires(value): - shared.opts.control_hires = value - control_hires.change(fn=set_control_hires, inputs=[control_hires], outputs=[]) control_max_units = gr.Slider(label="Maximum units", minimum=1, maximum=10, step=1, value=shared.opts.control_max_units, elem_id='control_max_units') def set_control_max_units(value): shared.opts.control_max_units = value @@ -265,11 +262,19 @@ def create_ui_elements(units, result_txt, preview_process): def set_control_tiles(value): shared.opts.control_tiles = value control_tiles.change(fn=set_control_tiles, inputs=[control_tiles], outputs=[]) - control_move_processor = gr.Checkbox(label="Move processor to CPU after use", value=shared.opts.control_move_processor, elem_id='control_move_processor') + control_hires = gr.Checkbox(label="Hires use control", value=shared.opts.control_hires, elem_id='control_hires') + def set_control_hires(value): + shared.opts.control_hires = value + control_hires.change(fn=set_control_hires, inputs=[control_hires], outputs=[]) + control_aspect_ratio = gr.Checkbox(label="Keep aspect ratio", value=shared.opts.control_aspect_ratio, elem_id='control_aspect_ratio') + def set_control_aspect_ratio(value): + shared.opts.control_aspect_ratio = value + control_aspect_ratio.change(fn=set_control_aspect_ratio, inputs=[control_aspect_ratio], outputs=[]) + control_move_processor = gr.Checkbox(label="Offload processor", value=shared.opts.control_move_processor, elem_id='control_move_processor') def set_control_move_processor(value): shared.opts.control_move_processor = value control_move_processor.change(fn=set_control_move_processor, inputs=[control_move_processor], outputs=[]) - control_unload_processor = gr.Checkbox(label="Unload processor after use", value=shared.opts.control_unload_processor, elem_id='control_unload_processor') + control_unload_processor = gr.Checkbox(label="Unload processor", value=shared.opts.control_unload_processor, elem_id='control_unload_processor') def set_control_unload_processor(value): shared.opts.control_unload_processor = value control_unload_processor.change(fn=set_control_unload_processor, inputs=[control_unload_processor], outputs=[])