diff --git a/README.md b/README.md index 3fab76b..378d367 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ # Asymmetric Tiling for stable-diffusion-webui -A custom script for [stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui/) to configure seamless image tiling independently for the X and Y axes. +An always visible script extension for [stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui/) to configure seamless image tiling independently for the X and Y axes. -To use, add [asymmetric_tiling.py](asymmetric_tiling.py) to your `scripts` directory in stable-diffusion-webui, restart your server, select `Asymmetric tiling` in the script dropdown, and check `Tile X` or `Tile Y` as desired. While this script is active, the `Tiling` checkbox in the main UI will be ignored. +To use, install this repository from the "Extensions" tab in stable-diffusion-webui, restart your server, open the `Asymmetric tiling` foldout on txt2img or img2img, and make it active, and check `Tile X` or `Tile Y` as desired. While this script is active, the `Tiling` checkbox in the main UI will be ignored. Like existing tiling options this won't guarantee seamless tiling 100% of the time, but it should manage it for most prompts. You can check that images tile seamlessly using online tools like [Seamless Texture Check](https://www.pycheung.com/checker/). +For the old, non-extension version of this script, use the "classic_script" branch. + ## X axis tiling examples ![00817-3274117678-midnight cityscape, stunning environment, wide-angle, massive scale, landscape, panoramic, lush vegetation, idyllic](https://user-images.githubusercontent.com/19196175/195132862-8c050327-92f3-44a4-9c02-0f11cce0b609.png) ![01064-1316547214-(((domino run))), domino toppling, line of standing dominoes, domino cascade, domino effect, black dominos](https://user-images.githubusercontent.com/19196175/195137782-e72fc69a-14f1-4ae7-bac2-219734509aea.png) diff --git a/asymmetric_tiling.py b/scripts/asymmetric_tiling.py similarity index 57% rename from asymmetric_tiling.py rename to scripts/asymmetric_tiling.py index a5f798f..31dd77a 100644 --- a/asymmetric_tiling.py +++ b/scripts/asymmetric_tiling.py @@ -1,5 +1,6 @@ import modules.scripts import modules.sd_hijack +import modules.shared import gradio from modules.processing import process_images @@ -18,40 +19,53 @@ class Script(modules.scripts.Script): def title(self): return "Asymmetric tiling" + # Override from modules.scripts.Script + def show(self, is_img2img): + return modules.scripts.AlwaysVisible + # Override from modules.scripts.Script def ui(self, is_img2img): - tileX = gradio.Checkbox(True, label="Tile X") - tileY = gradio.Checkbox(False, label="Tile Y") + with gradio.Accordion("Asymmetric tiling", open=False): + active = gradio.Checkbox(False, label="Active") + tileX = gradio.Checkbox(True, label="Tile X") + tileY = gradio.Checkbox(False, label="Tile Y") + startStep = gradio.Number(0, label="Start tiling from step N", precision=0) + stopStep = gradio.Number(-1, label="Stop tiling after step N (-1: Don't stop)", precision=0) - return [tileX, tileY] + return [active, tileX, tileY, startStep, stopStep] # Override from modules.scripts.Script - def run(self, p, tileX, tileY): - # Record tiling options chosen for each axis. - p.extra_generation_params = { - "Tile X": tileX, - "Tile Y": tileY, - } + def process(self, p, active, tileX, tileY, startStep, stopStep): + if (active): + # Record tiling options chosen for each axis. + p.extra_generation_params = { + "Tile X": tileX, + "Tile Y": tileY, + "Start Tiling From Step": startStep, + "Stop Tiling After Step": stopStep, + } - try: # Modify the model's Conv2D layers to perform our chosen tiling. - self.__hijackConv2DMethods(tileX, tileY) - - # Process images as normal. - return process_images(p) - finally: - # Restore model behaviour to normal, even if something went wrong during processing. + self.__hijackConv2DMethods(tileX, tileY, startStep, stopStep) + else: + # Restore model behaviour to normal. self.__restoreConv2DMethods() + + def postprocess(self, *args): + # Restore model behaviour to normal. + self.__restoreConv2DMethods() # [Private] # Go through all the "Conv2D" layers in the model and patch them to use the requested asymmetric tiling mode. - def __hijackConv2DMethods(self, tileX: bool, tileY: bool): + def __hijackConv2DMethods(self, tileX: bool, tileY: bool, startStep: int, stopStep: int): for layer in modules.sd_hijack.model_hijack.layers: if type(layer) == Conv2d: layer.padding_modeX = 'circular' if tileX else 'constant' layer.padding_modeY = 'circular' if tileY else 'constant' layer.paddingX = (layer._reversed_padding_repeated_twice[0], layer._reversed_padding_repeated_twice[1], 0, 0) layer.paddingY = (0, 0, layer._reversed_padding_repeated_twice[2], layer._reversed_padding_repeated_twice[3]) + layer.paddingStartStep = startStep + layer.paddingStopStep = stopStep layer._conv_forward = Script.__replacementConv2DConvForward.__get__(layer, Conv2d) # [Private] @@ -69,6 +83,11 @@ class Script(modules.scripts.Script): # paddingX (tuple, cached copy of _reversed_padding_repeated_twice with the last two values zeroed) # paddingY (tuple, cached copy of _reversed_padding_repeated_twice with the first two values zeroed) def __replacementConv2DConvForward(self, input: Tensor, weight: Tensor, bias: Optional[Tensor]): - working = F.pad(input, self.paddingX, mode=self.padding_modeX) - working = F.pad(working, self.paddingY, mode=self.padding_modeY) + step = modules.shared.state.sampling_step + if ((self.paddingStartStep < 0 or step >= self.paddingStartStep) and (self.paddingStopStep < 0 or step <= self.paddingStopStep)): + working = F.pad(input, self.paddingX, mode=self.padding_modeX) + working = F.pad(working, self.paddingY, mode=self.padding_modeY) + else: + working = F.pad(input, self.paddingX, mode='constant') + working = F.pad(working, self.paddingY, mode='constant') return F.conv2d(working, weight, bias, self.stride, _pair(0), self.dilation, self.groups)