diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1725901c2..9700fe346 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,6 +28,9 @@ For full details, see [ChangeLog](https://github.com/vladmandic/automatic/blob/m
- **Prompt enhance** add info to image metadata
- custom **VAE** loader for all pipelines
*note*: vae still needs to be compatible with the model
+ - [SD Ultimate Upscale](https://github.com/Coyote-A/ultimate-upscale-for-automatic1111)
+ still a popular method for upscaling, but has not been updated nor maintained for a while
+ so now its modernized and fully integrated as a built-in script!
- **Models**
- [StepFun Step1X-Edit v1.1](https://huggingface.co/stepfun-ai/Step1X-Edit-v1p1-diffusers) image edit model support
step1x is a large dedicated image edit model combining qwen-2.5 8B encoder with custom 12.4B transformer
@@ -73,6 +76,7 @@ For full details, see [ChangeLog](https://github.com/vladmandic/automatic/blob/m
- detailer `segmentation`, thanks @awsr
- `ipex` invalid device type
- cache network thumbnails
+ - `scripts` corrupting control ui state
## Update for 2026-04-28
diff --git a/extensions-builtin/sdnext-modernui b/extensions-builtin/sdnext-modernui
index bd0fcf817..74fd68c5f 160000
--- a/extensions-builtin/sdnext-modernui
+++ b/extensions-builtin/sdnext-modernui
@@ -1 +1 @@
-Subproject commit bd0fcf817af9337e8d0cb443baee18ffe5abf858
+Subproject commit 74fd68c5f09716a6a3d0df5ed40780262774fa93
diff --git a/installer.py b/installer.py
index 273b034d7..ca816f585 100644
--- a/installer.py
+++ b/installer.py
@@ -75,7 +75,7 @@ extensions_commit = { # force specific commit for extensions
'adetailer': 'a89c01d'
# 'stable-diffusion-webui-images-browser': '27fe4a7',
}
-control_extensions = [ # 3rd party extensions marked as safe for control ui
+control_extensions = [ # extensions marked as safe for control ui
'NudeNet',
'IP Adapters',
'Remove background',
diff --git a/modules/images.py b/modules/images.py
index d294e02b7..05c33d802 100644
--- a/modules/images.py
+++ b/modules/images.py
@@ -3,6 +3,7 @@ from modules.image.save import save_image, sanitize_filename_part
from modules.image.resize import resize_image
from modules.image.namegen import FilenameGenerator, get_next_sequence_number
from modules.image.grid import Grid, image_grid, check_grid_size, get_grid_size, draw_grid_annotations, draw_prompt_matrix, combine_grid, get_font
+from modules.image.util import draw_text, flatten
__all__ = [
'check_grid_size',
@@ -20,4 +21,6 @@ __all__ = [
'save_image',
'get_font',
'get_next_sequence_number',
+ 'draw_text',
+ 'flatten',
]
diff --git a/modules/scripts_manager.py b/modules/scripts_manager.py
index 25bf6cf2e..57824b159 100644
--- a/modules/scripts_manager.py
+++ b/modules/scripts_manager.py
@@ -380,6 +380,11 @@ class ScriptRunner:
script.is_img2img = is_img2img
if path.startswith(paths.extensions_dir) and not path.startswith(paths.extensions_builtin_dir):
script.external = True
+ if is_control and script.external:
+ title = script.title()
+ if title not in control_extensions:
+ log.debug(f'Script: fn="{script.filename}" type=control title="{title}" skip')
+ return
if is_control: # this is messy but show is a legacy function that is not aware of control tab
v1 = script.show(script.is_txt2img)
v2 = script.show(script.is_img2img)
diff --git a/scripts/style_aligned_ext.py b/scripts/style_aligned_ext.py
index c64d811a8..6eac8d6ed 100644
--- a/scripts/style_aligned_ext.py
+++ b/scripts/style_aligned_ext.py
@@ -35,7 +35,7 @@ class StyleAlignedScript(scripts_manager.Script):
def ui(self, _is_img2img): # ui elements
with gr.Row():
- gr.HTML('  Style Aligned Image Generation
')
+ gr.HTML('  Style Aligned Image Generation
')
with gr.Row():
preset = gr.Dropdown(label="Preset", choices=['text', 'image', 'all'], value='text')
scheduler = gr.Checkbox(label="Override scheduler", value=False)
diff --git a/scripts/ultimate-upscale.py b/scripts/ultimate-upscale.py
new file mode 100644
index 000000000..d92a8f660
--- /dev/null
+++ b/scripts/ultimate-upscale.py
@@ -0,0 +1,515 @@
+from enum import Enum
+import math
+import gradio as gr
+from PIL import Image, ImageDraw, ImageOps
+from modules import processing, shared, images, scripts_manager
+from modules.processing import StableDiffusionProcessing
+from modules.processing import Processed
+from modules.shared import opts, state
+from modules.logger import log
+
+elem_id = "ultimateupscale"
+
+class USDUMode(Enum):
+ LINEAR = 0
+ CHESS = 1
+ NONE = 2
+
+class USDUSFMode(Enum):
+ NONE = 0
+ BAND_PASS = 1
+ HALF_TILE = 2
+ HALF_TILE_PLUS_INTERSECTIONS = 3
+
+class USDUpscaler():
+
+ def __init__(self, p, image, upscaler_index:int, tile_width, tile_height) -> None:
+ self.p:StableDiffusionProcessing = p
+ self.image:Image = image
+ self.scales = []
+ self.result_images = []
+ self.scale_factor = math.ceil(max(p.width, p.height) / max(image.width, image.height))
+ self.upscaler = shared.sd_upscalers[upscaler_index]
+ self.redraw = USDURedraw()
+ self.redraw.tile_width = tile_width if tile_width > 0 else tile_height
+ self.redraw.tile_height = tile_height if tile_height > 0 else tile_width
+ self.seams_fix = USDUSeamsFix()
+ self.seams_fix.tile_width = tile_width if tile_width > 0 else tile_height
+ self.seams_fix.tile_height = tile_height if tile_height > 0 else tile_width
+ self.initial_info = None
+ self.rows = math.ceil(self.p.height / self.redraw.tile_height)
+ self.cols = math.ceil(self.p.width / self.redraw.tile_width)
+
+ def get_factor(self, num):
+ # Its just return, don't need elif
+ if num == 1:
+ return 2
+ if num % 4 == 0:
+ return 4
+ if num % 3 == 0:
+ return 3
+ if num % 2 == 0:
+ return 2
+ return 0
+
+ def get_factors(self):
+ scales = []
+ current_scale = 1
+ current_scale_factor = self.get_factor(self.scale_factor)
+ while current_scale_factor == 0:
+ self.scale_factor += 1
+ current_scale_factor = self.get_factor(self.scale_factor)
+ while current_scale < self.scale_factor:
+ current_scale_factor = self.get_factor(self.scale_factor // current_scale)
+ scales.append(current_scale_factor)
+ current_scale = current_scale * current_scale_factor
+ if current_scale_factor == 0:
+ break
+ self.scales = enumerate(scales)
+
+ def upscale(self):
+ # Log info
+ log.info(f'USDUpscaler: canvas={self.p.width}x{self.p.height} image={self.image.width}x{self.image.height} scale={self.scale_factor} scales={list(self.scales)} upscaler={self.upscaler.name}')
+ # Check upscaler is not empty
+ if self.upscaler.name == "None":
+ self.image = self.image.resize((self.p.width, self.p.height), resample=Image.Resampling.LANCZOS)
+ return
+ # Get list with scale factors
+ self.get_factors()
+ # Upscaling image over all factors
+ for _index, value in self.scales:
+ self.image = self.upscaler.scaler.upscale(self.image, value, self.upscaler.data_path)
+ # Resize image to set values
+ self.image = self.image.resize((self.p.width, self.p.height), resample=Image.Resampling.LANCZOS)
+
+ def setup_redraw(self, redraw_mode, padding, mask_blur):
+ self.redraw.mode = USDUMode(redraw_mode)
+ self.redraw.enabled = self.redraw.mode != USDUMode.NONE
+ self.redraw.padding = padding
+ self.p.mask_blur = mask_blur
+
+ def setup_seams_fix(self, padding, denoise, mask_blur, width, mode):
+ self.seams_fix.padding = padding
+ self.seams_fix.denoise = denoise
+ self.seams_fix.mask_blur = mask_blur
+ self.seams_fix.width = width
+ self.seams_fix.mode = USDUSFMode(mode)
+ self.seams_fix.enabled = self.seams_fix.mode != USDUSFMode.NONE
+
+ def save_image(self):
+ if type(self.p.prompt) != list:
+ images.save_image(self.image, self.p.outpath_samples, "", self.p.seed, self.p.prompt, opts.samples_format, info=self.initial_info, p=self.p)
+ else:
+ images.save_image(self.image, self.p.outpath_samples, "", self.p.seed, self.p.prompt[0], opts.samples_format, info=self.initial_info, p=self.p)
+
+ def calc_jobs_count(self):
+ redraw_job_count = (self.rows * self.cols) if self.redraw.enabled else 0
+ seams_job_count = 0
+ if self.seams_fix.mode == USDUSFMode.BAND_PASS:
+ seams_job_count = self.rows + self.cols - 2
+ elif self.seams_fix.mode == USDUSFMode.HALF_TILE:
+ seams_job_count = self.rows * (self.cols - 1) + (self.rows - 1) * self.cols
+ elif self.seams_fix.mode == USDUSFMode.HALF_TILE_PLUS_INTERSECTIONS:
+ seams_job_count = self.rows * (self.cols - 1) + (self.rows - 1) * self.cols + (self.rows - 1) * (self.cols - 1)
+ state.job_count = redraw_job_count + seams_job_count
+
+ def add_extra_info(self):
+ log.info(f"USDUpscaler: tile={self.redraw.tile_width}x{self.redraw.tile_height} rows={self.rows} cols={self.cols} redraw={self.redraw.enabled} seams={self.seams_fix.mode.name}")
+ self.p.extra_generation_params["USDUpscaler upscaler"] = self.upscaler.name
+ self.p.extra_generation_params["USDUpscaler tile"] = f'{self.redraw.tile_width}x{self.redraw.tile_height}'
+ self.p.extra_generation_params["USDUpscaler blur"] = self.p.mask_blur
+ self.p.extra_generation_params["USDUpscaler padding"] = self.redraw.padding
+
+ def process(self):
+ state.begin()
+ self.calc_jobs_count()
+ self.result_images = []
+ if self.redraw.enabled:
+ self.image = self.redraw.start(self.p, self.image, self.rows, self.cols)
+ self.initial_info = self.redraw.initial_info
+ self.result_images.append(self.image)
+ if self.seams_fix.enabled:
+ self.image = self.seams_fix.start(self.p, self.image, self.rows, self.cols)
+ self.initial_info = self.seams_fix.initial_info
+ self.result_images.append(self.image)
+ state.end()
+
+class USDURedraw():
+ def __init__(self) -> None:
+ self.enabled = False
+ self.mode = USDUMode.NONE
+ self.padding = 0
+ self.tile_width = 0
+ self.tile_height = 0
+ self.initial_info = None
+
+ def init_draw(self, p, width, height):
+ p.inpaint_full_res = True
+ p.inpaint_full_res_padding = self.padding
+ p.width = math.ceil((self.tile_width+self.padding) / 64) * 64
+ p.height = math.ceil((self.tile_height+self.padding) / 64) * 64
+ mask = Image.new("L", (width, height), "black")
+ draw = ImageDraw.Draw(mask)
+ return mask, draw
+
+ def calc_rectangle(self, xi, yi):
+ x1 = xi * self.tile_width
+ y1 = yi * self.tile_height
+ x2 = xi * self.tile_width + self.tile_width
+ y2 = yi * self.tile_height + self.tile_height
+ return x1, y1, x2, y2
+
+ def linear_process(self, p, image, rows, cols):
+ mask, draw = self.init_draw(p, image.width, image.height)
+ processed = None
+ for yi in range(rows):
+ for xi in range(cols):
+ if state.interrupted:
+ break
+ draw.rectangle(self.calc_rectangle(xi, yi), fill="white")
+ p.init_images = [image]
+ p.image_mask = mask
+ processed = processing.process_images(p)
+ draw.rectangle(self.calc_rectangle(xi, yi), fill="black")
+ if len(processed.images) > 0:
+ image = processed.images[0]
+ p.width = image.width
+ p.height = image.height
+ if processed is not None:
+ self.initial_info = processed.infotext(p, 0)
+ return image
+
+ def chess_process(self, p, image, rows, cols):
+ mask, draw = self.init_draw(p, image.width, image.height)
+ tiles = []
+ processed = None
+ # calc tiles colors
+ for yi in range(rows):
+ for xi in range(cols):
+ if state.interrupted:
+ break
+ if xi == 0:
+ tiles.append([])
+ color = xi % 2 == 0
+ if yi > 0 and yi % 2 != 0:
+ color = not color
+ tiles[yi].append(color)
+
+ for yi in range(len(tiles)):
+ for xi in range(len(tiles[yi])):
+ if state.interrupted:
+ break
+ if not tiles[yi][xi]:
+ tiles[yi][xi] = not tiles[yi][xi]
+ continue
+ tiles[yi][xi] = not tiles[yi][xi]
+ draw.rectangle(self.calc_rectangle(xi, yi), fill="white")
+ p.init_images = [image]
+ p.image_mask = mask
+ processed = processing.process_images(p)
+ draw.rectangle(self.calc_rectangle(xi, yi), fill="black")
+ if len(processed.images) > 0:
+ image = processed.images[0]
+
+ for yi in range(len(tiles)):
+ for xi in range(len(tiles[yi])):
+ if state.interrupted:
+ break
+ if not tiles[yi][xi]:
+ continue
+ draw.rectangle(self.calc_rectangle(xi, yi), fill="white")
+ p.init_images = [image]
+ p.image_mask = mask
+ processed = processing.process_images(p)
+ draw.rectangle(self.calc_rectangle(xi, yi), fill="black")
+ if len(processed.images) > 0:
+ image = processed.images[0]
+
+ p.width = image.width
+ p.height = image.height
+ if processed is not None:
+ self.initial_info = processed.infotext(p, 0)
+
+ return image
+
+ def start(self, p, image, rows, cols):
+ self.initial_info = None
+ if self.mode == USDUMode.LINEAR:
+ return self.linear_process(p, image, rows, cols)
+ if self.mode == USDUMode.CHESS:
+ return self.chess_process(p, image, rows, cols)
+ return image
+
+class USDUSeamsFix():
+ def __init__(self) -> None:
+ self.enabled = False
+ self.padding = 0
+ self.denoise = 0
+ self.mask_blur = 0
+ self.width = 0
+ self.mode = USDUSFMode.NONE
+ self.tile_width = 0
+ self.tile_height = 0
+ self.initial_info = None
+
+ def init_draw(self, p):
+ self.initial_info = None
+ p.width = math.ceil((self.tile_width+self.padding) / 64) * 64
+ p.height = math.ceil((self.tile_height+self.padding) / 64) * 64
+
+ def half_tile_process(self, p, image, rows, cols):
+ self.init_draw(p)
+ processed = None
+ gradient = Image.linear_gradient("L")
+ row_gradient = Image.new("L", (self.tile_width, self.tile_height), "black")
+ row_gradient.paste(gradient.resize(
+ (self.tile_width, self.tile_height//2), resample=Image.Resampling.BICUBIC), (0, 0))
+ row_gradient.paste(gradient.rotate(180).resize(
+ (self.tile_width, self.tile_height//2), resample=Image.Resampling.BICUBIC),
+ (0, self.tile_height//2))
+ col_gradient = Image.new("L", (self.tile_width, self.tile_height), "black")
+ col_gradient.paste(gradient.rotate(90).resize(
+ (self.tile_width//2, self.tile_height), resample=Image.Resampling.BICUBIC), (0, 0))
+ col_gradient.paste(gradient.rotate(270).resize(
+ (self.tile_width//2, self.tile_height), resample=Image.Resampling.BICUBIC), (self.tile_width//2, 0))
+
+ p.denoising_strength = self.denoise
+ p.mask_blur = self.mask_blur
+
+ for yi in range(rows-1):
+ for xi in range(cols):
+ if state.interrupted:
+ break
+ p.width = self.tile_width
+ p.height = self.tile_height
+ p.inpaint_full_res = True
+ p.inpaint_full_res_padding = self.padding
+ mask = Image.new("L", (image.width, image.height), "black")
+ mask.paste(row_gradient, (xi*self.tile_width, yi*self.tile_height + self.tile_height//2))
+ p.init_images = [image]
+ p.image_mask = mask
+ processed = processing.process_images(p)
+ if len(processed.images) > 0:
+ image = processed.images[0]
+
+ for yi in range(rows):
+ for xi in range(cols-1):
+ if state.interrupted:
+ break
+ p.width = self.tile_width
+ p.height = self.tile_height
+ p.inpaint_full_res = True
+ p.inpaint_full_res_padding = self.padding
+ mask = Image.new("L", (image.width, image.height), "black")
+ mask.paste(col_gradient, (xi*self.tile_width+self.tile_width//2, yi*self.tile_height))
+ p.init_images = [image]
+ p.image_mask = mask
+ processed = processing.process_images(p)
+ if len(processed.images) > 0:
+ image = processed.images[0]
+
+ p.width = image.width
+ p.height = image.height
+ if processed is not None:
+ self.initial_info = processed.infotext(p, 0)
+ return image
+
+ def half_tile_process_corners(self, p, image, rows, cols):
+ fixed_image = self.half_tile_process(p, image, rows, cols)
+ processed = None
+ self.init_draw(p)
+ gradient = Image.radial_gradient("L").resize(
+ (self.tile_width, self.tile_height), resample=Image.Resampling.BICUBIC)
+ gradient = ImageOps.invert(gradient)
+ p.denoising_strength = self.denoise
+ #p.mask_blur = 0
+ p.mask_blur = self.mask_blur
+
+ for yi in range(rows-1):
+ for xi in range(cols-1):
+ if state.interrupted:
+ break
+ p.width = self.tile_width
+ p.height = self.tile_height
+ p.inpaint_full_res = True
+ p.inpaint_full_res_padding = 0
+ mask = Image.new("L", (fixed_image.width, fixed_image.height), "black")
+ mask.paste(gradient, (xi*self.tile_width + self.tile_width//2, yi*self.tile_height + self.tile_height//2))
+ p.init_images = [fixed_image]
+ p.image_mask = mask
+ processed = processing.process_images(p)
+ if len(processed.images) > 0:
+ fixed_image = processed.images[0]
+
+ p.width = fixed_image.width
+ p.height = fixed_image.height
+ if processed is not None:
+ self.initial_info = processed.infotext(p, 0)
+
+ return fixed_image
+
+ def band_pass_process(self, p, image, rows, cols):
+ self.init_draw(p)
+ processed = None
+ p.denoising_strength = self.denoise
+ p.mask_blur = 0
+ gradient = Image.linear_gradient("L")
+ mirror_gradient = Image.new("L", (256, 256), "black")
+ mirror_gradient.paste(gradient.resize((256, 128), resample=Image.Resampling.BICUBIC), (0, 0))
+ mirror_gradient.paste(gradient.rotate(180).resize((256, 128), resample=Image.Resampling.BICUBIC), (0, 128))
+ row_gradient = mirror_gradient.resize((image.width, self.width), resample=Image.Resampling.BICUBIC)
+ col_gradient = mirror_gradient.rotate(90).resize((self.width, image.height), resample=Image.Resampling.BICUBIC)
+
+ for xi in range(1, cols):
+ if state.interrupted:
+ break
+ p.width = self.width + self.padding * 2
+ p.height = image.height
+ p.inpaint_full_res = True
+ p.inpaint_full_res_padding = self.padding
+ mask = Image.new("L", (image.width, image.height), "black")
+ mask.paste(col_gradient, (xi * self.tile_width - self.width // 2, 0))
+ p.init_images = [image]
+ p.image_mask = mask
+ processed = processing.process_images(p)
+ if len(processed.images) > 0:
+ image = processed.images[0]
+ for yi in range(1, rows):
+ if state.interrupted:
+ break
+ p.width = image.width
+ p.height = self.width + self.padding * 2
+ p.inpaint_full_res = True
+ p.inpaint_full_res_padding = self.padding
+ mask = Image.new("L", (image.width, image.height), "black")
+ mask.paste(row_gradient, (0, yi * self.tile_height - self.width // 2))
+ p.init_images = [image]
+ p.image_mask = mask
+ processed = processing.process_images(p)
+ if len(processed.images) > 0:
+ image = processed.images[0]
+
+ p.width = image.width
+ p.height = image.height
+ if processed is not None:
+ self.initial_info = processed.infotext(p, 0)
+ return image
+
+ def start(self, p, image, rows, cols):
+ if USDUSFMode(self.mode) == USDUSFMode.BAND_PASS:
+ return self.band_pass_process(p, image, rows, cols)
+ elif USDUSFMode(self.mode) == USDUSFMode.HALF_TILE:
+ return self.half_tile_process(p, image, rows, cols)
+ elif USDUSFMode(self.mode) == USDUSFMode.HALF_TILE_PLUS_INTERSECTIONS:
+ return self.half_tile_process_corners(p, image, rows, cols)
+ else:
+ return image
+
+class UltimateSDUpscaleScript(scripts_manager.Script):
+ def title(self):
+ return "SD Ultimate Upscale"
+
+ def show(self, is_img2img):
+ return True
+
+ def ui(self, is_img2img): # pylint: disable=unused-argument
+ elem_id_prefix = f"{self.parent}_{elem_id}"
+ target_size_types = ["Default", "Custom", "Rescale"]
+ seams_fix_types = ["None", "Band pass", "Half tile offset pass", "Half tile with intersections"]
+ redraw_modes = ["Linear", "Chess", "None"]
+
+ with gr.Row():
+ gr.HTML('  SD Ultimate Upscale
')
+ with gr.Row():
+ target_size_type = gr.Dropdown(label="Target size", elem_id=f"{elem_id_prefix}_target_size_type", choices=target_size_types, type="index", value=next(iter(target_size_types)))
+ custom_width = gr.Slider(label='Custom width', elem_id=f"{elem_id_prefix}_custom_width", minimum=64, maximum=8192, step=64, value=2048, visible=False, interactive=True)
+ custom_height = gr.Slider(label='Custom height', elem_id=f"{elem_id_prefix}_custom_height", minimum=64, maximum=8192, step=64, value=2048, visible=False, interactive=True)
+ custom_scale = gr.Slider(label='Target scale', elem_id=f"{elem_id_prefix}_custom_scale", minimum=1, maximum=16, step=0.2, value=2, visible=False, interactive=True)
+ with gr.Row():
+ upscaler_index = gr.Dropdown(label='Redraw upscaler', elem_id=f"{elem_id_prefix}_upscaler_index", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name, type="index")
+ redraw_mode = gr.Dropdown(label="Redraw mode", elem_id=f"{elem_id_prefix}_redraw_mode", choices=redraw_modes, type="index", value=next(iter(redraw_modes)))
+ with gr.Row():
+ tile_width = gr.Slider(elem_id=f"{elem_id_prefix}_tile_width", minimum=0, maximum=2048, step=64, label='Tile width', value=1024)
+ tile_height = gr.Slider(elem_id=f"{elem_id_prefix}_tile_height", minimum=0, maximum=2048, step=64, label='Tile height', value=0)
+ with gr.Row():
+ mask_blur = gr.Slider(elem_id=f"{elem_id_prefix}_mask_blur", label='Tile blur', minimum=0, maximum=64, step=1, value=8)
+ padding = gr.Slider(elem_id=f"{elem_id_prefix}_padding", label='Tile padding', minimum=0, maximum=512, step=1, value=32)
+ with gr.Row():
+ seams_fix_type = gr.Dropdown(label="Seams fix", elem_id=f"{elem_id_prefix}_seams_fix_type", choices=seams_fix_types, type="index", value=next(iter(seams_fix_types)))
+ with gr.Row():
+ seams_fix_denoise = gr.Slider(label='Seams fix denoise', elem_id=f"{elem_id_prefix}_seams_fix_denoise", minimum=0, maximum=1, step=0.01, value=0.35, visible=False, interactive=True)
+ seams_fix_width = gr.Slider(label='Seams fix width', elem_id=f"{elem_id_prefix}_seams_fix_width", minimum=0, maximum=128, step=1, value=64, visible=False, interactive=True)
+ seams_fix_mask_blur = gr.Slider(label='Seams fix mask blur', elem_id=f"{elem_id_prefix}_seams_fix_mask_blur", minimum=0, maximum=64, step=1, value=4, visible=False, interactive=True)
+ seams_fix_padding = gr.Slider(label='Seams fix padding', elem_id=f"{elem_id_prefix}_seams_fix_padding", minimum=0, maximum=128, step=1, value=16, visible=False, interactive=True)
+
+ def select_fix_type(fix_index):
+ all_visible = fix_index != 0
+ mask_blur_visible = fix_index == 2 or fix_index == 3
+ width_visible = fix_index == 1
+ return [gr.update(visible=all_visible), gr.update(visible=width_visible), gr.update(visible=mask_blur_visible), gr.update(visible=all_visible)]
+
+ seams_fix_type.change(fn=select_fix_type, inputs=seams_fix_type, outputs=[seams_fix_denoise, seams_fix_width, seams_fix_mask_blur, seams_fix_padding])
+
+ def select_scale_type(scale_index):
+ is_custom_size = scale_index == 1
+ is_custom_scale = scale_index == 2
+ return [gr.update(visible=is_custom_size), gr.update(visible=is_custom_size), gr.update(visible=is_custom_scale)]
+
+ target_size_type.change(fn=select_scale_type, inputs=target_size_type, outputs=[custom_width, custom_height, custom_scale])
+
+ def init_field(scale_name):
+ try:
+ scale_index = target_size_types.index(scale_name)
+ custom_width.visible = custom_height.visible = scale_index == 1
+ custom_scale.visible = scale_index == 2
+ except Exception:
+ pass
+
+ target_size_type.init_field = init_field
+
+ return [tile_width, tile_height, mask_blur, padding, seams_fix_width, seams_fix_denoise, seams_fix_padding,
+ upscaler_index, redraw_mode, seams_fix_mask_blur,
+ seams_fix_type, target_size_type, custom_width, custom_height, custom_scale]
+
+ def run(self, p: StableDiffusionProcessing, *args):
+ tile_width, tile_height, mask_blur, padding, seams_fix_width, seams_fix_denoise, seams_fix_padding, upscaler_index, redraw_mode, seams_fix_mask_blur, seams_fix_type, target_size_type, custom_width, custom_height, custom_scale = args
+
+ if p.init_images is None or len(p.init_images) == 0: # Dont run on t2i
+ return None
+
+ # Init
+ processing.fix_seed(p)
+
+ p.do_not_save_grid = True
+ p.do_not_save_samples = True
+ p.inpaint_full_res = False
+ p.inpainting_fill = 1
+ p.n_iter = 1
+ p.batch_size = 1
+
+ # Init image
+ init_img = p.init_images[0]
+ if init_img is None:
+ return Processed(p, [], p.seed, "Empty image")
+ init_img = images.flatten(init_img, opts.img2img_background_color)
+
+ #override size
+ if target_size_type == 1:
+ p.width = custom_width
+ p.height = custom_height
+ if target_size_type == 2:
+ p.width = math.ceil((init_img.width * custom_scale) / 64) * 64
+ p.height = math.ceil((init_img.height * custom_scale) / 64) * 64
+
+ # Upscaling
+ upscaler = USDUpscaler(p, init_img, upscaler_index, tile_width, tile_height)
+ upscaler.upscale()
+
+ # Drawing
+ upscaler.setup_redraw(redraw_mode, padding, mask_blur)
+ upscaler.setup_seams_fix(seams_fix_padding, seams_fix_denoise, seams_fix_mask_blur, seams_fix_width, seams_fix_type)
+ upscaler.add_extra_info()
+ upscaler.process()
+ result_images = upscaler.result_images
+
+ return Processed(p, result_images, p.seed, upscaler.initial_info if upscaler.initial_info is not None else "")