From b1f55afdf10a05613240aa8c5d6a44d5a9fb2cef Mon Sep 17 00:00:00 2001 From: Haoming Date: Tue, 26 May 2026 23:06:43 +0800 Subject: [PATCH] round --- javascript/ui.js | 2 +- modules/img2img.py | 14 +++++++------- modules/infotext_utils.py | 6 ++++-- modules/processing.py | 13 +++++++------ modules/shared_options.py | 1 + modules/ui.py | 31 +++++++++++++++++-------------- modules/ui_loadsave.py | 12 ++++++++++-- 7 files changed, 47 insertions(+), 32 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index 6670dd51..c17ef331 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -471,7 +471,7 @@ function currentImg2imgSourceResolution(w, h, r) { if (!img) return [0, 0, r]; const width = img.naturalWidth || img.width; const height = img.naturalHeight || img.height; - return [Math.round(width / 64.0) * 64, Math.round(height / 64.0) * 64, r]; + return [Math.round(width / opts.res_step) * opts.res_step, Math.round(height / opts.res_step) * opts.res_step, r]; } function updateImg2imgResizeToTextAfterChangingImage() { diff --git a/modules/img2img.py b/modules/img2img.py index ea28a034..06b2d5f3 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -20,7 +20,7 @@ from modules.processing import ( ) from modules.sd_models import get_closet_checkpoint_match from modules.shared import opts, state -from modules.ui import plaintext_to_html +from modules.ui import _STEP, plaintext_to_html, sRound from modules_forge import main_thread @@ -73,12 +73,12 @@ def process_batch(p, input, output_dir, inpaint_mask_dir, args, to_scale=False, img = ImageOps.exif_transpose(img) if to_scale: - p.width = round(img.width * scale_by / 64) * 64 - p.height = round(img.height * scale_by / 64) * 64 + p.width = sRound(img.width * scale_by) + p.height = sRound(img.height * scale_by) _w, _h = img.size - if not (_w % 64 == 0 and _h % 64 == 0): - img = images.resize_image(1, img, round(_w / 64) * 64, round(_h / 64) * 64) + if not (_w % _STEP == 0 and _h % _STEP == 0): + img = images.resize_image(1, img, sRound(_w), sRound(_h)) p.init_images = [img] * p.batch_size @@ -209,8 +209,8 @@ def img2img_function(id_task: str, request: gr.Request, mode: int, prompt: str, if selected_scale_tab == 1 and not is_batch: assert image, "Can't scale by because no image is selected" - width = round(image.width * scale_by / 64) * 64 - height = round(image.height * scale_by / 64) * 64 + width = sRound(image.width * scale_by) + height = sRound(image.height * scale_by) assert 0.0 <= denoising_strength <= 1.0, "can only work with strength in [0.0, 1.0]" diff --git a/modules/infotext_utils.py b/modules/infotext_utils.py index 3703f5a8..4d0122b8 100644 --- a/modules/infotext_utils.py +++ b/modules/infotext_utils.py @@ -254,9 +254,11 @@ def send_image_and_dimensions(x) -> tuple[Image.Image, int, int]: if img.mode != "RGB": img = img.convert("RGB") + from modules.ui import sRound + if shared.opts.send_size and isinstance(img, Image.Image): - w = round(img.width / 64.0) * 64 - h = round(img.height / 64.0) * 64 + w = sRound(img.width) + h = sRound(img.height) else: w = gr.skip() h = gr.skip() diff --git a/modules/processing.py b/modules/processing.py index b089b0db..8ea118ab 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -32,6 +32,7 @@ from modules.sd_models import apply_token_merging, forge_model_reload from modules.sd_samplers_common import approximation_indexes, decode_first_stage, images_tensor_to_samples from modules.shared import cmd_opts, opts, state from modules.sysinfo import set_config +from modules.ui import sRound from modules_forge import main_entry from modules_forge.utils import apply_circular_forge @@ -1185,8 +1186,8 @@ def old_hires_fix_first_pass_dimensions(width: int, height: int) -> tuple[int, i desired_pixel_count = 512 * 512 actual_pixel_count = width * height scale = math.sqrt(desired_pixel_count / actual_pixel_count) - width = round(scale * width / 64.0) * 64 - height = round(scale * height / 64.0) * 64 + width = sRound(scale * width) + height = sRound(scale * height) return width, height @@ -1254,8 +1255,8 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): if self.hr_resize_x == 0 and self.hr_resize_y == 0: self.extra_generation_params["Hires upscale"] = self.hr_scale - self.hr_upscale_to_x = round(self.width * self.hr_scale / 64.0) * 64 - self.hr_upscale_to_y = round(self.height * self.hr_scale / 64.0) * 64 + self.hr_upscale_to_x = sRound(self.width * self.hr_scale) + self.hr_upscale_to_y = sRound(self.height * self.hr_scale) else: if self.hr_resize_y == 0: self.hr_upscale_to_x = self.hr_resize_x @@ -1267,8 +1268,8 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): self.hr_upscale_to_x = self.hr_resize_x self.hr_upscale_to_y = self.hr_resize_y - self.hr_upscale_to_x = round(self.hr_upscale_to_x / 64.0) * 64 - self.hr_upscale_to_y = round(self.hr_upscale_to_y / 64.0) * 64 + self.hr_upscale_to_x = sRound(self.hr_upscale_to_x) + self.hr_upscale_to_y = sRound(self.hr_upscale_to_y) self.extra_generation_params["Hires resize"] = f"{self.hr_upscale_to_x}x{self.hr_upscale_to_y}" diff --git a/modules/shared_options.py b/modules/shared_options.py index cddc1863..40f6ffd6 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -163,6 +163,7 @@ options_templates.update( ("system", "System", "system"), { "setting_allocated_vram": OptionInfo(1.0, "GPU Weights", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.05}).info("amount of VRAM that Forge can access").info("in % of total vram"), + "res_step": OptionInfo(64, "Resolution Step", gr.Radio, {"choices": (8, 16, 32, 64, 128, 256)}).info('"64" is recommended to prevent compatibility issues').needs_restart(), "auto_launch_browser": OptionInfo("Local", "Launch the webui in browser on startup", gr.Radio, {"choices": ("Disable", "Local", "Remote")}).info("Remote = always automatically start; Local = only when not sharing the server, such as --share"), "enable_console_prompts": OptionInfo(False, "Print the generation prompts to console"), "samples_log_stdout": OptionInfo(False, "Print the generation infotxt to console"), diff --git a/modules/ui.py b/modules/ui.py index 0f2c36aa..dc3834d7 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -15,7 +15,7 @@ import modules.processing_scripts.comments as comments import modules.shared as shared from modules import extra_networks, gradio_extensions, launch_utils, paths_internal, processing, progress, prompt_parser, script_callbacks, scripts, sd_models, sd_samplers, sd_schedulers, shared_items, sysinfo, timer, ui_checkpoint_merger, ui_common, ui_extensions, ui_extra_networks, ui_loadsave, ui_postprocessing, ui_settings, ui_toprow # noqa: F401 from modules.call_queue import wrap_gradio_call, wrap_gradio_call_no_job, wrap_gradio_gpu_call, wrap_queued_call # noqa: F401 -from modules.infotext_utils import PasteField, image_from_url_text +from modules.infotext_utils import PasteField from modules.paths import script_path from modules.shared import cmd_opts, opts from modules.ui_common import create_refresh_button # noqa: F401 @@ -80,8 +80,11 @@ detect_image_size_symbol = "\U0001f4d0" # 📐 plaintext_to_html = ui_common.plaintext_to_html -def _round(v: float) -> int: - return round(v / 64.0) * 64 +_STEP = int(opts.res_step) + + +def sRound(val: int) -> int: + return round(val / _STEP) * _STEP def calc_resolution_hires(enable, width, height, hr_scale, hr_resize_x, hr_resize_y): @@ -91,15 +94,15 @@ def calc_resolution_hires(enable, width, height, hr_scale, hr_resize_x, hr_resiz p = processing.StableDiffusionProcessingTxt2Img(width=width, height=height, enable_hr=True, hr_scale=hr_scale, hr_resize_x=hr_resize_x, hr_resize_y=hr_resize_y) p.calculate_target_resolution() - new_width = _round(p.hr_resize_x or p.hr_upscale_to_x) - new_height = _round(p.hr_resize_y or p.hr_upscale_to_y) + new_width = sRound(p.hr_resize_x or p.hr_upscale_to_x) + new_height = sRound(p.hr_resize_y or p.hr_upscale_to_y) return f"from {p.width}x{p.height} to {new_width}x{new_height}" def resize_from_to_html(width, height, scale_by): - target_width = _round(int(width) * scale_by) - target_height = _round(int(height) * scale_by) + target_width = sRound(int(width) * scale_by) + target_height = sRound(int(height) * scale_by) if not target_width or not target_height: return "no image selected" @@ -211,8 +214,8 @@ def create_ui(): elif category == "dimensions": with FormRow(): with gr.Column(elem_id="txt2img_column_size", scale=4): - width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=1024, elem_id="txt2img_width") - height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=1024, elem_id="txt2img_height") + width = gr.Slider(minimum=64, maximum=2048, step=_STEP, label="Width", value=1024, elem_id="txt2img_width") + height = gr.Slider(minimum=64, maximum=2048, step=_STEP, label="Height", value=1024, elem_id="txt2img_height") with gr.Column(elem_id="txt2img_dimensions_row", scale=1, elem_classes="dimensions-tools"): res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="txt2img_res_switch_btn", tooltip="Switch width/height") @@ -242,8 +245,8 @@ def create_ui(): with FormRow(elem_id="txt2img_hires_fix_row2", variant="compact"): hr_scale = gr.Slider(minimum=1.0, maximum=4.0, step=0.05, label="Upscale by", value=2.0, elem_id="txt2img_hr_scale") - hr_resize_x = gr.Slider(minimum=0, maximum=4096, step=64, label="Resize width to", value=0, elem_id="txt2img_hr_resize_x") - hr_resize_y = gr.Slider(minimum=0, maximum=4096, step=64, label="Resize height to", value=0, elem_id="txt2img_hr_resize_y") + hr_resize_x = gr.Slider(minimum=0, maximum=4096, step=_STEP, label="Resize width to", value=0, elem_id="txt2img_hr_resize_x") + hr_resize_y = gr.Slider(minimum=0, maximum=4096, step=_STEP, label="Resize height to", value=0, elem_id="txt2img_hr_resize_y") with FormRow(elem_id="txt2img_hires_fix_row_cfg", variant="compact"): hr_distilled_cfg = gr.Slider(minimum=1.0, maximum=24.0, step=0.5, label="Hires Distilled CFG Scale", value=3.0, elem_id="txt2img_hr_distilled_cfg") @@ -575,8 +578,8 @@ def create_ui(): with gr.Tab(label="Resize to", id="to", elem_id="img2img_tab_resize_to") as tab_scale_to: with FormRow(): with gr.Column(elem_id="img2img_column_size", scale=4): - width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=1024, elem_id="img2img_width") - height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=1024, elem_id="img2img_height") + width = gr.Slider(minimum=64, maximum=2048, step=_STEP, label="Width", value=1024, elem_id="img2img_width") + height = gr.Slider(minimum=64, maximum=2048, step=_STEP, label="Height", value=1024, elem_id="img2img_height") with gr.Column(elem_id="img2img_dimensions_row", scale=1, elem_classes="dimensions-tools"): res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="img2img_res_switch_btn", tooltip="Switch width/height") detect_image_size_btn = ToolButton(value=detect_image_size_symbol, elem_id="img2img_detect_image_size_btn", tooltip="Auto detect size from img2img") @@ -602,7 +605,7 @@ def create_ui(): def updateWH(img): if img and shared.opts.img2img_autosize is True: - return _round(img.size[0]), _round(img.size[1]) + return sRound(img.size[0]), sRound(img.size[1]) else: return gr.skip(), gr.skip() diff --git a/modules/ui_loadsave.py b/modules/ui_loadsave.py index 9b3414bf..d2f4db1c 100644 --- a/modules/ui_loadsave.py +++ b/modules/ui_loadsave.py @@ -1,4 +1,5 @@ import json +import math import os import gradio as gr @@ -47,8 +48,9 @@ class UiLoadsave: if getattr(obj, "do_not_save_to_config", False): return - if field == "value" and getattr(obj, "_internal_preset_param", False): - return + if getattr(obj, "_internal_preset_param", False): + if field in ("value", "step"): + return saved_value = self.ui_settings.get(key, None) @@ -73,6 +75,12 @@ class UiLoadsave: except ValueError: return + if getattr(obj, "elem_id", None) in ("txt2img_width", "txt2img_height", "img2img_width", "img2img_height"): + if field == "minimum": + from modules.ui import _STEP + + saved_value = math.ceil(saved_value / _STEP) * _STEP + setattr(obj, field, saved_value) if init_field is not None: init_field(saved_value)