diff --git a/CHANGELOG.md b/CHANGELOG.md index 03fe52caa..ad6888ac4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## TODO -- Tag multi-image pipes with `use_images_direct` +- Tag multi-image pipes with `use_images_direct` and allow UI override ## Update for 2026-04-30 @@ -10,16 +10,23 @@ - **Multi-image** workflows! for models that support multiple images as inputs, you can now add multiple stages in Kanvas prompts like "*place character from first image, add background from second image, render in style from third image*" are now possible + - **Anima** support for *img2img* and *inpaint* workflows + - option *inputs -> skip processing* to force images to passed to model as-is without any pre-processing - **UI** - add button to manually reorient input/output panels - all ui panels can be minimized/maximized by clicking on their header state is preserved across sessions and can be used to hide rarely used panels and declutter the workspace - **Kanvas** re-order stages by clicking on active stage + order of stages detemines order of images passed to model - **Control** - remove buttons: *input/control/process* - move params *control input type* to control menu section - remove "processed preview" from ui preprocessor output can still be generated by clicking preview button in in control unit and it will render into normal output area +- **Internal** + - refactor `pip` installer, thanks @awsr +- **Fixes** + - save handle already decoded images ## Update for 2026-04-28 diff --git a/modules/control/run.py b/modules/control/run.py index 3a8aa83a6..d3238675f 100644 --- a/modules/control/run.py +++ b/modules/control/run.py @@ -339,7 +339,8 @@ def control_process(p: StableDiffusionProcessingControl, def control_run(state: str = '', # pylint: disable=keyword-arg-before-vararg units: list[unit.Unit] | None = None, inputs: list[Image.Image] | None = None, inits: list[Image.Image] | None = None, mask: Image.Image = None, unit_type: str | None = None, is_generator: bool = True, input_type: int = 0, - prompt: str = '', negative_prompt: str = '', styles: list[str] | None = None, + prompt: str = '', negative_prompt: str = '', + styles: list[str] | None = None, steps: int = 20, sampler_index: int | None = None, seed: int = -1, subseed: int = -1, subseed_strength: float = 0, seed_resize_from_h: int = -1, seed_resize_from_w: int = -1, guidance_name: str = 'Default', guidance_scale: float = 6.0, guidance_rescale: float = 0.0, guidance_start: float = 0.0, guidance_stop: float = 1.0, @@ -358,7 +359,9 @@ def control_run(state: str = '', # pylint: disable=keyword-arg-before-vararg resize_mode_before: int = 0, resize_name_before: str = 'None', resize_context_before: str = 'None', width_before: int = 512, height_before: int = 512, scale_by_before: float = 1.0, selected_scale_tab_before: int = 0, resize_mode_after: int = 0, resize_name_after: str = 'None', resize_context_after: str = 'None', width_after: int = 0, height_after: int = 0, scale_by_after: float = 1.0, selected_scale_tab_after: int = 0, resize_mode_mask: int = 0, resize_name_mask: str = 'None', resize_context_mask: str = 'None', width_mask: int = 0, height_mask: int = 0, scale_by_mask: float = 1.0, selected_scale_tab_mask: int = 0, - denoising_strength: float = 0.3, batch_count: int = 1, batch_size: int = 1, + denoising_strength: float = 0.3, + skip_processing: bool = False, + batch_count: int = 1, batch_size: int = 1, enable_hr: bool = False, hr_sampler_index: int | None = None, hr_denoising_strength: float = 0.0, hr_resize_mode: int = 0, hr_resize_context: str = 'None', hr_upscaler: str | None = None, hr_force: bool = False, hr_second_pass_steps: int = 20, hr_scale: float = 1.0, hr_resize_x: int = 0, hr_resize_y: int = 0, refiner_steps: int = 5, refiner_start: float = 0.0, refiner_prompt: str = '', refiner_negative: str = '', video_skip_frames: int = 0, video_type: str = 'None', video_duration: float = 2.0, video_loop: bool = False, video_pad: int = 0, video_interpolate: int = 0, @@ -456,6 +459,7 @@ def control_run(state: str = '', # pylint: disable=keyword-arg-before-vararg seed_resize_from_h = seed_resize_from_h, seed_resize_from_w = seed_resize_from_w, denoising_strength = denoising_strength, + skip_processing = skip_processing, # modular guidance guidance_name = guidance_name, guidance_scale = guidance_scale, @@ -749,8 +753,9 @@ def control_run(state: str = '', # pylint: disable=keyword-arg-before-vararg continue index += 1 - if getattr(pipe, 'use_images_direct', False) or getattr(p, 'use_images_direct', False): + if getattr(pipe, 'skip_processing', False) or getattr(p, 'skip_processing', False): p.init_images = inputs + p.extra_generation_params['Process'] = False else: processed_image, blended_image = preprocess_image(p, pipe, diff --git a/modules/processing_class.py b/modules/processing_class.py index 9e1ed3e4f..ecbe47a92 100644 --- a/modules/processing_class.py +++ b/modules/processing_class.py @@ -276,6 +276,7 @@ class StableDiffusionProcessing: tome_ratio: float | None = None, todo_ratio: float | None = None, # overrides + skip_processing: bool = False, override_settings_restore_afterwards: bool = True, override_settings: dict[str, Any] | None = None, # metadata @@ -491,6 +492,7 @@ class StableDiffusionProcessing: self.scale_by_before = scale_by_before self.scale_by_after = scale_by_after self.scale_by_mask = scale_by_mask + self.skip_processing = skip_processing # special handled items if firstphase_width != 0 or firstphase_height != 0: diff --git a/modules/ui_control.py b/modules/ui_control.py index abdda0fec..12a4c9eb0 100644 --- a/modules/ui_control.py +++ b/modules/ui_control.py @@ -170,6 +170,7 @@ def create_ui(_blocks: gr.Blocks=None): input_type = gr.Radio(label="Use init image", choices=['No: Control only', '1st: Same as control', '2nd: Separate image'], value='No: Control only', type='index', elem_id='control_input_type') with gr.Row(): denoising_strength = gr.Slider(minimum=0.00, maximum=0.99, step=0.01, label='Denoising strength', value=0.30, elem_id="control_input_denoising_strength") + skip_processing = gr.Checkbox(label="Skip input processing", value=False, elem_id="control_input_skip_processing") with gr.Accordion(open=False, label="Size", elem_id="control_size", elem_classes=["small-accordion"]): with gr.Tabs(): @@ -318,7 +319,7 @@ def create_ui(_blocks: gr.Blocks=None): resize_mode_before, resize_name_before, resize_context_before, width_before, height_before, scale_by_before, selected_scale_tab_before, resize_mode_after, resize_name_after, resize_context_after, width_after, height_after, scale_by_after, selected_scale_tab_after, resize_mode_mask, resize_name_mask, resize_context_mask, width_mask, height_mask, scale_by_mask, selected_scale_tab_mask, - denoising_strength, batch_count, batch_size, + denoising_strength, skip_processing, batch_count, batch_size, enable_hr, hr_sampler_index, hr_denoising_strength, hr_resize_mode, hr_resize_context, hr_upscaler, hr_force, hr_second_pass_steps, hr_scale, hr_resize_x, hr_resize_y, refiner_steps, refiner_start, refiner_prompt, refiner_negative, video_skip_frames, video_type, video_duration, video_loop, video_pad, video_interpolate, diff --git a/pipelines/model_google.py b/pipelines/model_google.py index 9bb062811..40e52c5bd 100644 --- a/pipelines/model_google.py +++ b/pipelines/model_google.py @@ -40,7 +40,7 @@ def get_size_buckets(width: int, height: int) -> tuple[str, str]: class GoogleNanoBananaPipeline(): def __init__(self, model_name: str): - self.use_images_direct = True + self.skip_processing = True self.model = model_name self.client = None self.config = None