mirror of
https://github.com/altoiddealer/--sd-webui-ar-plusplus.git
synced 2026-05-01 11:41:39 +00:00
Final version
This commit is contained in:
18
javascript/sd-webui-ar-plusplus.js
Normal file
18
javascript/sd-webui-ar-plusplus.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
if ("undefined" === typeof arsp__ar_button_titles) {
|
||||||
|
arsp__ar_button_titles = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
arsp__ar_button_titles[" \u{1F513}"] = 'Toggle to lock the "average" width/height values in the UI\nIt is recommended to "Lock" when switching between ARs in "Offset" mode.\n\n\u{1F512} = Locked\n\u{1F513} = Unlocked';
|
||||||
|
arsp__ar_button_titles[" \u{025AD}"] = 'For "Offset mode" (default):\n\u{025AF} = Portrait resolutions\n\u{025AD} = Landscape resolutions\n\nFor "One Dimension" mode:\n\u{025AD} = Modify Width\n\u{025AF} = Modify Height';
|
||||||
|
arsp__ar_button_titles[" \u{02B83}"] = 'Toggle the Mode for updating resolution.\nResolution is always rounded to precision (default 64px).\n\n\u{02B83} = "Offset" updates both Width/Height from the average current resolution\n\u{02B85} = "One Dimension" changes only Width or Height';
|
||||||
|
arsp__ar_button_titles[" \u{02139}"] = 'Show the Information panel including additional settings.'
|
||||||
|
arsp__ar_button_titles[" \u{02BC5}"] = 'Hide the Information panel.'
|
||||||
|
|
||||||
|
onUiUpdate(function(){
|
||||||
|
gradioApp().querySelectorAll('#arsp__txt2img_container_aspect_ratio button, #arsp__img2img_container_aspect_ratio button').forEach(function(elem){
|
||||||
|
tooltip = arsp__ar_button_titles[elem.textContent];
|
||||||
|
if(tooltip){
|
||||||
|
elem.title = tooltip;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
if ("undefined" === typeof arsp__ar_button_titles) {
|
|
||||||
arsp__ar_button_titles = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
arsp__ar_button_titles["Calc"] = "Show or hide the aspect ratio calculator";
|
|
||||||
arsp__ar_button_titles["\u{21c5}"] = "Swap width and height values";
|
|
||||||
arsp__ar_button_titles["\u2B07\ufe0f"] = "Get dimensions from txt2img/img2img sliders";
|
|
||||||
arsp__ar_button_titles["\u{1f5bc}"] = "Get dimensions from image on current img2img tab";
|
|
||||||
arsp__ar_button_titles["Calculate Height"] = "Calculate new height based on source aspect ratio";
|
|
||||||
arsp__ar_button_titles["Calculate Width"] = "Calculate new width based on source aspect ratio";
|
|
||||||
arsp__ar_button_titles["Apply"] = "Apply calculated width and height to txt2img/img2img sliders";
|
|
||||||
arsp__ar_button_titles["\uD83D\uDD0D"] = "Round dimensions to the nearest multiples of 4 (1023x101 => 1024x100)";
|
|
||||||
|
|
||||||
onUiUpdate(function(){
|
|
||||||
gradioApp().querySelectorAll('#arsp__txt2img_container_aspect_ratio button, #arsp__img2img_container_aspect_ratio button').forEach(function(elem){
|
|
||||||
tooltip = arsp__ar_button_titles[elem.textContent];
|
|
||||||
if(tooltip){
|
|
||||||
elem.title = tooltip;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
396
scripts/sd-webui-ar-plusplus.py
Normal file
396
scripts/sd-webui-ar-plusplus.py
Normal file
@@ -0,0 +1,396 @@
|
|||||||
|
import contextlib
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import gradio as gr
|
||||||
|
|
||||||
|
import modules.scripts as scripts
|
||||||
|
from modules.ui_components import ToolButton
|
||||||
|
|
||||||
|
from fractions import Fraction
|
||||||
|
from math import gcd, sqrt
|
||||||
|
|
||||||
|
BASE_PATH = scripts.basedir()
|
||||||
|
|
||||||
|
is_locked = False
|
||||||
|
|
||||||
|
# Helper functions for calculating new width/height values
|
||||||
|
def round_to_precision(val, prec):
|
||||||
|
return round(val / prec) * prec
|
||||||
|
|
||||||
|
def res_to_model_fit(avg, w, h, prec):
|
||||||
|
mp = w * h
|
||||||
|
mp_target = avg * avg
|
||||||
|
scale = sqrt(mp_target / mp)
|
||||||
|
w = int(round_to_precision(w * scale, prec))
|
||||||
|
h = int(round_to_precision(h * scale, prec))
|
||||||
|
return w, h
|
||||||
|
|
||||||
|
def calc_width(n, d, w, h, prec):
|
||||||
|
ar = round((n / d), 2) # Convert AR parts to fraction
|
||||||
|
if ar > 1.0:
|
||||||
|
h = w / ar
|
||||||
|
elif ar < 1.0:
|
||||||
|
w = h * ar
|
||||||
|
else:
|
||||||
|
new_value = max([w, h])
|
||||||
|
w, h = new_value, new_value
|
||||||
|
w = int(round_to_precision((w + prec / 2), prec))
|
||||||
|
h = int(round_to_precision((h + prec / 2), prec))
|
||||||
|
return w, h
|
||||||
|
|
||||||
|
def calc_height(n, d, w, h, prec):
|
||||||
|
ar = round((n / d), 2) # Convert AR parts to fraction
|
||||||
|
if ar > 1.0:
|
||||||
|
w = h * ar
|
||||||
|
elif ar < 1.0:
|
||||||
|
h = w / ar
|
||||||
|
else:
|
||||||
|
new_value = min([w, h])
|
||||||
|
w, h = new_value, new_value
|
||||||
|
w = int(round_to_precision((w + prec / 2), prec))
|
||||||
|
h = int(round_to_precision((h + prec / 2), prec))
|
||||||
|
return w, h
|
||||||
|
|
||||||
|
def dims_from_ar(avg, n, d, prec):
|
||||||
|
doubleavg = avg * 2
|
||||||
|
ar_sum = n+d
|
||||||
|
# calculate width and height by factoring average with aspect ratio
|
||||||
|
w = round((n / ar_sum) * doubleavg)
|
||||||
|
h = round((d / ar_sum) * doubleavg)
|
||||||
|
# Round to correct megapixel precision
|
||||||
|
w, h = res_to_model_fit(avg, w, h, prec)
|
||||||
|
return w, h
|
||||||
|
|
||||||
|
def avg_from_dims(w, h):
|
||||||
|
avg = (w + h) // 2
|
||||||
|
if (w + h) % 2 != 0:
|
||||||
|
avg += 1
|
||||||
|
return avg
|
||||||
|
|
||||||
|
# Aspect Ratio buttons
|
||||||
|
class ARButton(ToolButton):
|
||||||
|
switched = False
|
||||||
|
alt_mode = False
|
||||||
|
|
||||||
|
def __init__(self, value='1:1', **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def apply(self, avg, prec, w=512, h=512):
|
||||||
|
ar = self.value
|
||||||
|
n, d = map(Fraction, ar.split(':')) # split numerator and denominator
|
||||||
|
if not is_locked:
|
||||||
|
avg = avg_from_dims(w, h) # Get average of current width/height values
|
||||||
|
if not ARButton.alt_mode: # True = offset, False = One dimension
|
||||||
|
w, h = dims_from_ar(avg, n, d, prec) # Calculate new w + h from avg, AR, and precision
|
||||||
|
if ARButton.switched: # Switch results if switch mode active
|
||||||
|
w, h = h, w
|
||||||
|
else: # Calculate w or h from input, AR, and precision
|
||||||
|
if ARButton.switched: # Switch results if switch mode active
|
||||||
|
w, h = calc_width(n, d, w, h, prec) # Modify width
|
||||||
|
else:
|
||||||
|
w, h = calc_height(n, d, w, h, prec) # Modify height
|
||||||
|
return avg, w, h
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def toggle_switch(cls):
|
||||||
|
cls.switched = not cls.switched
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def toggle_mode(cls):
|
||||||
|
cls.alt_mode = not cls.alt_mode
|
||||||
|
|
||||||
|
# Static Resolution buttons
|
||||||
|
class ResButton(ToolButton):
|
||||||
|
def __init__(self, res=(512, 512), **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.w, self.h = res
|
||||||
|
|
||||||
|
def reset(self, avg):
|
||||||
|
# Get average of current width/height values
|
||||||
|
if not is_locked:
|
||||||
|
avg = avg_from_dims(self.w, self.h)
|
||||||
|
return avg, self.w, self.h
|
||||||
|
|
||||||
|
# Get values for Aspect Ratios from file
|
||||||
|
def parse_aspect_ratios_file(filename):
|
||||||
|
values, flipvals, comments = [], [], []
|
||||||
|
file = Path(BASE_PATH, filename)
|
||||||
|
|
||||||
|
if not file.exists():
|
||||||
|
return values, comments, flipvals
|
||||||
|
|
||||||
|
with open(file, "r", encoding="utf-8") as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
if not lines:
|
||||||
|
return values, comments, flipvals
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
if line.startswith("#"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
value = line.strip()
|
||||||
|
|
||||||
|
comment = ""
|
||||||
|
if "#" in value:
|
||||||
|
value, comment = value.split("#")
|
||||||
|
values.append(value)
|
||||||
|
comments.append(comment)
|
||||||
|
|
||||||
|
comp1, comp2 = value.split(':')
|
||||||
|
flipval = f"{comp2}:{comp1}"
|
||||||
|
flipvals.append(flipval)
|
||||||
|
|
||||||
|
return values, comments, flipvals
|
||||||
|
|
||||||
|
|
||||||
|
# Get values for Static Resolutions from file
|
||||||
|
def parse_resolutions_file(filename):
|
||||||
|
labels, values, comments = [], [], []
|
||||||
|
file = Path(BASE_PATH, filename)
|
||||||
|
|
||||||
|
if not file.exists():
|
||||||
|
return labels, values, comments
|
||||||
|
|
||||||
|
with open(file, "r", encoding="utf-8") as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
if not lines:
|
||||||
|
return labels, values, comments
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
if line.startswith("#"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
label, width, height = line.strip().split(",")
|
||||||
|
comment = ""
|
||||||
|
if "#" in height:
|
||||||
|
height, comment = height.split("#")
|
||||||
|
|
||||||
|
resolution = (width, height)
|
||||||
|
|
||||||
|
labels.append(label)
|
||||||
|
values.append(resolution)
|
||||||
|
comments.append(comment)
|
||||||
|
|
||||||
|
return labels, values, comments
|
||||||
|
|
||||||
|
def write_aspect_ratios_file(filename):
|
||||||
|
aspect_ratios = [
|
||||||
|
"1:1 # Square\n",
|
||||||
|
"4:3 # Television Photography\n",
|
||||||
|
"3:2 # Photography\n",
|
||||||
|
"8:5 # Widescreen Displays\n",
|
||||||
|
"16:9 # Widescreen Television\n",
|
||||||
|
"21:9 # Ultrawide Cinematography"
|
||||||
|
]
|
||||||
|
with open(filename, "w", encoding="utf-8") as f:
|
||||||
|
f.writelines(aspect_ratios)
|
||||||
|
|
||||||
|
def write_resolutions_file(filename):
|
||||||
|
resolutions = [
|
||||||
|
"512, 512, 512 # 512x512\n",
|
||||||
|
"768, 768, 768 # 768x768\n",
|
||||||
|
"1024, 1024, 1024 # 1024x1024\n",
|
||||||
|
"1280, 1280, 1280 # 1280x1280\n",
|
||||||
|
"1536, 1536, 1536 # 1536x1536\n",
|
||||||
|
"2048, 2048, 2048 # 2048x2048",
|
||||||
|
]
|
||||||
|
with open(filename, "w", encoding="utf-8") as f:
|
||||||
|
f.writelines(resolutions)
|
||||||
|
|
||||||
|
def write_js_titles_file(button_titles):
|
||||||
|
filename = Path(BASE_PATH, "javascript", "button_titles.js")
|
||||||
|
content = ["// Do not put custom titles here. This file is overwritten each time the WebUI is started.\n"]
|
||||||
|
content.append("arsp__ar_button_titles = {\n")
|
||||||
|
counter = 0
|
||||||
|
while counter < len(button_titles[0]):
|
||||||
|
content.append(f' " {button_titles[0][counter]}" : "{button_titles[1][counter]}",\n')
|
||||||
|
counter = counter + 1
|
||||||
|
content.append("}")
|
||||||
|
|
||||||
|
with open(filename, "w", encoding="utf-8") as f:
|
||||||
|
f.writelines(content)
|
||||||
|
|
||||||
|
class AspectRatioScript(scripts.Script):
|
||||||
|
def read_aspect_ratios(self):
|
||||||
|
ar_file = Path(BASE_PATH, "aspect_ratios.txt")
|
||||||
|
if not ar_file.exists():
|
||||||
|
write_aspect_ratios_file(ar_file)
|
||||||
|
(self.aspect_ratios, self.aspect_ratio_comments, self.flipped_vals) = parse_aspect_ratios_file("aspect_ratios.txt")
|
||||||
|
self.ar_btns_labels = self.aspect_ratios
|
||||||
|
|
||||||
|
def read_resolutions(self):
|
||||||
|
res_file = Path(BASE_PATH, "resolutions.txt")
|
||||||
|
if not res_file.exists():
|
||||||
|
write_resolutions_file(res_file)
|
||||||
|
|
||||||
|
self.res_labels, res, self.res_comments = parse_resolutions_file("resolutions.txt")
|
||||||
|
self.res = [list(map(int, r)) for r in res]
|
||||||
|
|
||||||
|
def title(self):
|
||||||
|
return "Aspect Ratio picker"
|
||||||
|
|
||||||
|
def show(self, is_img2img):
|
||||||
|
return scripts.AlwaysVisible
|
||||||
|
|
||||||
|
def ui(self, is_img2img):
|
||||||
|
self.LOCK_OPEN_ICON = "\U0001F513" # 🔓
|
||||||
|
self.LOCK_CLOSED_ICON = "\U0001F512" # 🔒
|
||||||
|
self.LAND_AR_ICON = "\U000025AD" # ▭
|
||||||
|
self.PORT_AR_ICON = "\U000025AF" # ▯
|
||||||
|
self.INFO_ICON = "\U00002139" # ℹ
|
||||||
|
self.INFO_CLOSE_ICON = "\U00002BC5" # ⯅
|
||||||
|
self.OFFSET_ICON = "\U00002B83" # ⮃
|
||||||
|
self.ONE_DIM_ICON = "\U00002B85" # ⮅
|
||||||
|
|
||||||
|
arc_avg = gr.Number(label="Current W/H Avg.", value=0, interactive=False, render=False)
|
||||||
|
arc_prec = gr.Number(label="Precision (px)", value=64, minimum=4, maximum=128, step=4, precision=0, render=False)
|
||||||
|
|
||||||
|
with gr.Accordion(label="Aspect Ratio and Resolution Buttons", open=True):
|
||||||
|
|
||||||
|
with gr.Column(elem_id=f'arsp__{"img" if is_img2img else "txt"}2img_container_aspect_ratio'):
|
||||||
|
|
||||||
|
# Get aspect ratios from file
|
||||||
|
self.read_aspect_ratios()
|
||||||
|
|
||||||
|
# Top row
|
||||||
|
with gr.Row(elem_id=f'arsp__{"img" if is_img2img else "txt"}2img_row_aspect_ratio'):
|
||||||
|
|
||||||
|
# Lock button
|
||||||
|
arc_lock = ToolButton(value=self.LOCK_OPEN_ICON, visible=True, variant="secondary", elem_id="arsp__arc_lock_button")
|
||||||
|
# Click event handling for lock button
|
||||||
|
def toggle_lock(icon, avg, w=512, h=512):
|
||||||
|
global is_locked
|
||||||
|
icon = self.LOCK_OPEN_ICON if is_locked else self.LOCK_CLOSED_ICON
|
||||||
|
is_locked = not is_locked
|
||||||
|
if not avg:
|
||||||
|
avg = avg_from_dims(w, h)
|
||||||
|
return icon, avg
|
||||||
|
if is_img2img:
|
||||||
|
lock_w = self.i2i_w
|
||||||
|
lock_h = self.i2i_h
|
||||||
|
else:
|
||||||
|
lock_w = self.t2i_w
|
||||||
|
lock_h = self.t2i_h
|
||||||
|
arc_lock.click(toggle_lock, inputs=[arc_lock, arc_avg, lock_w, lock_h], outputs=[arc_lock, arc_avg])
|
||||||
|
|
||||||
|
# Switch button
|
||||||
|
arc_sw = ToolButton(value=self.LAND_AR_ICON, visible=True, variant="secondary", elem_id="arsp__arc_sw_button")
|
||||||
|
# Click event handling for switch button
|
||||||
|
def toggle_switch(icon):
|
||||||
|
icon = self.PORT_AR_ICON if icon == self.LAND_AR_ICON else self.LAND_AR_ICON
|
||||||
|
ARButton.toggle_switch()
|
||||||
|
return icon
|
||||||
|
arc_sw.click(toggle_switch, inputs=[arc_sw], outputs=[arc_sw])
|
||||||
|
|
||||||
|
# Aspect Ratio buttons
|
||||||
|
ar_btns = [ARButton(value=ar) for ar in self.ar_btns_labels]
|
||||||
|
# Click event handling for AR buttons
|
||||||
|
with contextlib.suppress(AttributeError):
|
||||||
|
for b in ar_btns:
|
||||||
|
if is_img2img:
|
||||||
|
w = self.i2i_w
|
||||||
|
h = self.i2i_h
|
||||||
|
else:
|
||||||
|
w = self.t2i_w
|
||||||
|
h = self.t2i_h
|
||||||
|
|
||||||
|
b.click(b.apply, inputs=[arc_avg, arc_prec, w, h], outputs=[arc_avg, w, h])
|
||||||
|
|
||||||
|
# Get resolutions from file
|
||||||
|
self.read_resolutions()
|
||||||
|
|
||||||
|
# Bottom row
|
||||||
|
with gr.Row(elem_id=f'arsp__{"img" if is_img2img else "txt"}2img_row_resolutions'):
|
||||||
|
|
||||||
|
# Info button to toggle info window
|
||||||
|
arc_show_info = ToolButton(value=self.INFO_ICON, visible=True, variant="secondary", elem_id="arsp__arc_show_info_button")
|
||||||
|
arc_hide_info = ToolButton(value=self.INFO_CLOSE_ICON, visible=False, variant="secondary", elem_id="arsp__arc_hide_info_button")
|
||||||
|
# Click event handling for info window
|
||||||
|
# Handled after everything else
|
||||||
|
|
||||||
|
# Mode button
|
||||||
|
arc_mode = ToolButton(value=self.OFFSET_ICON, visible=True, variant="secondary", elem_id="arsp__arc_mode_button")
|
||||||
|
# Click event handling for Mode button
|
||||||
|
def toggle_mode(icon):
|
||||||
|
icon = self.ONE_DIM_ICON if icon == self.OFFSET_ICON else self.OFFSET_ICON
|
||||||
|
ARButton.toggle_mode()
|
||||||
|
return icon
|
||||||
|
arc_mode.click(toggle_mode, inputs=[arc_mode], outputs=[arc_mode])
|
||||||
|
|
||||||
|
# Static resolution buttons
|
||||||
|
btns = [ResButton(res=res, value=label) for res, label in zip(self.res, self.res_labels)]
|
||||||
|
# Click event handling for static res buttons
|
||||||
|
with contextlib.suppress(AttributeError):
|
||||||
|
for b in btns:
|
||||||
|
b.click(b.reset, inputs=[arc_avg], outputs=[arc_avg, w, h])
|
||||||
|
|
||||||
|
# Write button_titles.js with labels and comments read from aspect ratios and resolutions files
|
||||||
|
button_titles = [self.aspect_ratios + self.res_labels]
|
||||||
|
button_titles.append(self.aspect_ratio_comments + self.res_comments)
|
||||||
|
write_js_titles_file(button_titles)
|
||||||
|
|
||||||
|
# Information panel
|
||||||
|
with gr.Column(visible=False, variant="panel", elem_id="arsp__arc_panel") as arc_panel:
|
||||||
|
with gr.Row():
|
||||||
|
with gr.Column(scale=2, min_width=100):
|
||||||
|
# render the current average number box
|
||||||
|
arc_avg.render()
|
||||||
|
|
||||||
|
# render the precision input box
|
||||||
|
arc_prec.render()
|
||||||
|
|
||||||
|
# Information blurb
|
||||||
|
gr.Column(scale=1, min_width=10)
|
||||||
|
with gr.Column(scale=12):
|
||||||
|
arc_title_heading = gr.Markdown(value=
|
||||||
|
'''
|
||||||
|
### AR and Static Res buttons can be customized in the 'aspect_ratios.txt' and 'resolutions.txt' files
|
||||||
|
**Aspect Ratio buttons (Top Row)**:
|
||||||
|
(1) Averages the current width/height in the UI; (2) Offsets to the exact aspect ratio; (3) Rounds to precision.
|
||||||
|
|
||||||
|
**Static Resolution buttons (Bottom Row)**:
|
||||||
|
Recommended to use 1:1 values for these, to serve as a start point before clicking AR buttons.
|
||||||
|
|
||||||
|
**64px Precision is recommended, the same rounding applied for image "bucketing" when model training.**
|
||||||
|
'''
|
||||||
|
)
|
||||||
|
|
||||||
|
# Toggle info panel
|
||||||
|
arc_show_info.click(
|
||||||
|
lambda: [
|
||||||
|
gr.update(visible=True),
|
||||||
|
gr.update(visible=False),
|
||||||
|
gr.update(visible=True),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
arc_panel,
|
||||||
|
arc_show_info,
|
||||||
|
arc_hide_info,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
# Hide info pane
|
||||||
|
arc_hide_info.click(
|
||||||
|
lambda: [
|
||||||
|
gr.update(visible=False),
|
||||||
|
gr.update(visible=True),
|
||||||
|
gr.update(visible=False),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
[arc_panel, arc_show_info, arc_hide_info],
|
||||||
|
)
|
||||||
|
|
||||||
|
## Function to update the values in appropriate Width/Height fields
|
||||||
|
# https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/7456#issuecomment-1414465888
|
||||||
|
def after_component(self, component, **kwargs):
|
||||||
|
if kwargs.get("elem_id") == "txt2img_width":
|
||||||
|
self.t2i_w = component
|
||||||
|
if kwargs.get("elem_id") == "txt2img_height":
|
||||||
|
self.t2i_h = component
|
||||||
|
if kwargs.get("elem_id") == "img2img_width":
|
||||||
|
self.i2i_w = component
|
||||||
|
if kwargs.get("elem_id") == "img2img_height":
|
||||||
|
self.i2i_h = component
|
||||||
@@ -1,422 +0,0 @@
|
|||||||
import contextlib
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import gradio as gr
|
|
||||||
|
|
||||||
import modules.scripts as scripts
|
|
||||||
from modules.ui_components import ToolButton
|
|
||||||
|
|
||||||
from fractions import Fraction
|
|
||||||
from math import gcd, sqrt
|
|
||||||
|
|
||||||
BASE_PATH = scripts.basedir()
|
|
||||||
SWITCH_SYMBOL = "\U0001F501" # 🔁
|
|
||||||
LOCK_OPEN_SYMBOL = "\U0001F513" # 🔓
|
|
||||||
LOCK_SYMBOL = "\U0001F512" # 🔒
|
|
||||||
LAND_AR_SYMBOL = "\U000025AD" # ▭
|
|
||||||
PORT_AR_SYMBOL = "\U000025AF" # ▯
|
|
||||||
INFO_SYMBOL = "\U00002139" # ℹ
|
|
||||||
|
|
||||||
is_lock_mode = False # FIXME: Global value
|
|
||||||
is_switch_mode = False # FIXME: Global value
|
|
||||||
is_locked = False
|
|
||||||
|
|
||||||
## Aspect Ratio buttons
|
|
||||||
# Helper functions for calculating new width/height from aspect ratio
|
|
||||||
def round_to_precision(val, prec):
|
|
||||||
return round(val / prec) * prec
|
|
||||||
|
|
||||||
def res_to_model_fit(w, h, mp_target, prec):
|
|
||||||
mp = w * h
|
|
||||||
scale = sqrt(mp_target / mp)
|
|
||||||
w = int(round_to_precision(w * scale, prec))
|
|
||||||
h = int(round_to_precision(h * scale, prec))
|
|
||||||
return w, h
|
|
||||||
|
|
||||||
def dims_from_ar(avg, ar, prec):
|
|
||||||
mp_target = avg*avg
|
|
||||||
doubleavg = avg*2
|
|
||||||
|
|
||||||
ar_parts = tuple(map(Fraction, ar.split(':')))
|
|
||||||
ar_sum = ar_parts[0]+ar_parts[1]
|
|
||||||
# calculate width and height by factoring average with aspect ratio
|
|
||||||
w = round((ar_parts[0]/ar_sum)*doubleavg)
|
|
||||||
h = round((ar_parts[1]/ar_sum)*doubleavg)
|
|
||||||
# Round to correct megapixel precision
|
|
||||||
w, h = res_to_model_fit(w, h, mp_target, prec)
|
|
||||||
return w, h
|
|
||||||
|
|
||||||
def avg_from_dims(w, h):
|
|
||||||
avg = (w + h) // 2
|
|
||||||
if (w + h) % 2 != 0:
|
|
||||||
avg += 1
|
|
||||||
return avg
|
|
||||||
|
|
||||||
class ARButton(ToolButton):
|
|
||||||
switched = False
|
|
||||||
|
|
||||||
def __init__(self, value='1:1', **kwargs):
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
def apply(self, avg, prec, w=512, h=512):
|
|
||||||
# Get average of current width/height values
|
|
||||||
if not is_locked:
|
|
||||||
avg = avg_from_dims(w, h)
|
|
||||||
# Calculate new w/h from avg, AR, and precision
|
|
||||||
w, h = dims_from_ar(avg, self.value, prec)
|
|
||||||
if ARButton.switched:
|
|
||||||
w, h = h, w # return switched results
|
|
||||||
return avg, w, h
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def toggle_switch(cls):
|
|
||||||
cls.switched = not cls.switched
|
|
||||||
|
|
||||||
## Static Resolution buttons
|
|
||||||
class ResButton(ToolButton):
|
|
||||||
def __init__(self, res=(512, 512), **kwargs):
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
self.w, self.h = res
|
|
||||||
|
|
||||||
def reset(self, avg):
|
|
||||||
# Get average of current width/height values
|
|
||||||
if not is_locked:
|
|
||||||
avg = avg_from_dims(self.w, self.h)
|
|
||||||
return avg, self.w, self.h
|
|
||||||
|
|
||||||
## Get values for Aspect Ratios from file
|
|
||||||
def parse_aspect_ratios_file(filename):
|
|
||||||
values, flipvals, comments = [], [], []
|
|
||||||
file = Path(BASE_PATH, filename)
|
|
||||||
|
|
||||||
if not file.exists():
|
|
||||||
return values, comments, flipvals
|
|
||||||
|
|
||||||
with open(file, "r", encoding="utf-8") as f:
|
|
||||||
lines = f.readlines()
|
|
||||||
|
|
||||||
if not lines:
|
|
||||||
return values, comments, flipvals
|
|
||||||
|
|
||||||
for line in lines:
|
|
||||||
if line.startswith("#"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
value = line.strip()
|
|
||||||
|
|
||||||
comment = ""
|
|
||||||
if "#" in value:
|
|
||||||
value, comment = value.split("#")
|
|
||||||
values.append(value)
|
|
||||||
comments.append(comment)
|
|
||||||
|
|
||||||
comp1, comp2 = value.split(':')
|
|
||||||
flipval = f"{comp2}:{comp1}"
|
|
||||||
flipvals.append(flipval)
|
|
||||||
|
|
||||||
return values, comments, flipvals
|
|
||||||
|
|
||||||
|
|
||||||
## Get values for Static Resolutions from file
|
|
||||||
def parse_resolutions_file(filename):
|
|
||||||
labels, values, comments = [], [], []
|
|
||||||
file = Path(BASE_PATH, filename)
|
|
||||||
|
|
||||||
if not file.exists():
|
|
||||||
return labels, values, comments
|
|
||||||
|
|
||||||
with open(file, "r", encoding="utf-8") as f:
|
|
||||||
lines = f.readlines()
|
|
||||||
|
|
||||||
if not lines:
|
|
||||||
return labels, values, comments
|
|
||||||
|
|
||||||
for line in lines:
|
|
||||||
if line.startswith("#"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
label, width, height = line.strip().split(",")
|
|
||||||
comment = ""
|
|
||||||
if "#" in height:
|
|
||||||
height, comment = height.split("#")
|
|
||||||
|
|
||||||
resolution = (width, height)
|
|
||||||
|
|
||||||
labels.append(label)
|
|
||||||
values.append(resolution)
|
|
||||||
comments.append(comment)
|
|
||||||
|
|
||||||
return labels, values, comments
|
|
||||||
|
|
||||||
# TODO: write a generic function handling both cases
|
|
||||||
def write_aspect_ratios_file(filename):
|
|
||||||
aspect_ratios = [
|
|
||||||
"3:2 # Photography\n",
|
|
||||||
"4:3 # Television photography\n",
|
|
||||||
"16:9 # Television photography\n",
|
|
||||||
"1.85:1 # Cinematography\n",
|
|
||||||
"2.39:1 # Cinematography",
|
|
||||||
]
|
|
||||||
with open(filename, "w", encoding="utf-8") as f:
|
|
||||||
f.writelines(aspect_ratios)
|
|
||||||
|
|
||||||
def write_resolutions_file(filename):
|
|
||||||
resolutions = [
|
|
||||||
"512, 512, 512 # 512x512\n",
|
|
||||||
"640, 640, 640 # 640x640\n",
|
|
||||||
"768, 768, 768 # 768x768\n",
|
|
||||||
"896, 896, 896 # 896x896\n",
|
|
||||||
"1024, 1024, 1024 # 1024x1024",
|
|
||||||
]
|
|
||||||
with open(filename, "w", encoding="utf-8") as f:
|
|
||||||
f.writelines(resolutions)
|
|
||||||
|
|
||||||
def write_js_titles_file(button_titles):
|
|
||||||
filename = Path(BASE_PATH, "javascript", "button_titles.js")
|
|
||||||
content = ["// Do not put custom titles here. This file is overwritten each time the WebUI is started.\n"]
|
|
||||||
content.append("arsp__ar_button_titles = {\n")
|
|
||||||
counter = 0
|
|
||||||
while counter < len(button_titles[0]):
|
|
||||||
content.append(f' " {button_titles[0][counter]}" : "{button_titles[1][counter]}",\n')
|
|
||||||
counter = counter + 1
|
|
||||||
content.append("}")
|
|
||||||
|
|
||||||
with open(filename, "w", encoding="utf-8") as f:
|
|
||||||
f.writelines(content)
|
|
||||||
|
|
||||||
class AspectRatioScript(scripts.Script):
|
|
||||||
def read_aspect_ratios(self):
|
|
||||||
ar_file = Path(BASE_PATH, "aspect_ratios.txt")
|
|
||||||
if not ar_file.exists():
|
|
||||||
write_aspect_ratios_file(ar_file)
|
|
||||||
(self.aspect_ratios, self.aspect_ratio_comments, self.flipped_vals) = parse_aspect_ratios_file("aspect_ratios.txt")
|
|
||||||
self.ar_btns_labels = self.aspect_ratios
|
|
||||||
|
|
||||||
def read_resolutions(self):
|
|
||||||
res_file = Path(BASE_PATH, "resolutions.txt")
|
|
||||||
if not res_file.exists():
|
|
||||||
write_resolutions_file(res_file)
|
|
||||||
|
|
||||||
self.res_labels, res, self.res_comments = parse_resolutions_file("resolutions.txt")
|
|
||||||
self.res = [list(map(int, r)) for r in res]
|
|
||||||
|
|
||||||
def title(self):
|
|
||||||
return "Aspect Ratio picker"
|
|
||||||
|
|
||||||
def show(self, is_img2img):
|
|
||||||
return scripts.AlwaysVisible
|
|
||||||
|
|
||||||
def ui(self, is_img2img):
|
|
||||||
arc_avg = gr.Number(label="Current W/H Avg.", value=0, interactive=False, render=False)
|
|
||||||
arc_prec = gr.Number(label="Precision (px)", info='64px is recommended, the same rounding applied for image "bucketing" when model training.', value=64, minimum=4, maximum=128, precision=0, render=False)
|
|
||||||
|
|
||||||
with gr.Column(elem_id=f'arsp__{"img" if is_img2img else "txt"}2img_container_aspect_ratio'):
|
|
||||||
# Get aspect ratios from file
|
|
||||||
self.read_aspect_ratios()
|
|
||||||
# Top row
|
|
||||||
with gr.Row(elem_id=f'arsp__{"img" if is_img2img else "txt"}2img_row_aspect_ratio'):
|
|
||||||
|
|
||||||
# Lock button
|
|
||||||
arc_show_lock = ToolButton(value=LOCK_SYMBOL, min_width=84, scale = 0, visible=True, variant="secondary", elem_id="arsp__arc_show_lock_button")
|
|
||||||
arc_hide_lock = ToolButton(value=LOCK_SYMBOL, min_width=84, scale = 0, visible=False, variant="primary", elem_id="arsp__arc_hide_lock_button")
|
|
||||||
# Click event handling for lock button
|
|
||||||
def toggle_lock(avg, w=512, h=512):
|
|
||||||
global is_locked
|
|
||||||
is_locked = not is_locked
|
|
||||||
if not avg:
|
|
||||||
avg = avg_from_dims(w, h)
|
|
||||||
return avg
|
|
||||||
if is_img2img:
|
|
||||||
lock_w = self.i2i_w
|
|
||||||
lock_h = self.i2i_h
|
|
||||||
else:
|
|
||||||
lock_w = self.t2i_w
|
|
||||||
lock_h = self.t2i_h
|
|
||||||
arc_show_lock.click(toggle_lock, inputs=[arc_avg, lock_w, lock_h], outputs=[arc_avg])
|
|
||||||
arc_hide_lock.click(toggle_lock, inputs=[arc_avg, lock_w, lock_h], outputs=[arc_avg])
|
|
||||||
|
|
||||||
# Switch button
|
|
||||||
arc_sw_on = ToolButton(value=SWITCH_SYMBOL, min_width=84, scale = 0, visible=True, variant="secondary", elem_id="arsp__arc_sw_on_button")
|
|
||||||
arc_sw_off = ToolButton(value=SWITCH_SYMBOL, min_width=84, scale = 0, visible=False, variant="primary", elem_id="arsp__arc_sw_off_button")
|
|
||||||
# Click event handling for switch button
|
|
||||||
def toggle_switch():
|
|
||||||
ARButton.toggle_switch()
|
|
||||||
# def toggle_switch():
|
|
||||||
# if self.ar_btns_labels == self.aspect_ratios:
|
|
||||||
# self.ar_btns_labels = self.flipped_vals
|
|
||||||
# else:
|
|
||||||
# self.ar_btns_labels = self.aspect_ratios
|
|
||||||
arc_sw_on.click(toggle_switch)
|
|
||||||
arc_sw_off.click(toggle_switch)
|
|
||||||
|
|
||||||
# Aspect Ratio buttons
|
|
||||||
ar_btns = [ARButton(value=ar) for ar in self.ar_btns_labels]
|
|
||||||
# Click event handling for AR buttons
|
|
||||||
with contextlib.suppress(AttributeError):
|
|
||||||
for b in ar_btns:
|
|
||||||
if is_img2img:
|
|
||||||
w = self.i2i_w
|
|
||||||
h = self.i2i_h
|
|
||||||
else:
|
|
||||||
w = self.t2i_w
|
|
||||||
h = self.t2i_h
|
|
||||||
|
|
||||||
b.click(b.apply, inputs=[arc_avg, arc_prec, w, h], outputs=[arc_avg, w, h])
|
|
||||||
|
|
||||||
# Get resolutions from file
|
|
||||||
self.read_resolutions()
|
|
||||||
# Bottom row
|
|
||||||
with gr.Row(elem_id=f'arsp__{"img" if is_img2img else "txt"}2img_row_resolutions'):
|
|
||||||
# Info button to toggle info window
|
|
||||||
arc_show_info = ToolButton(value=INFO_SYMBOL, min_width=84, scale = 1.0, visible=True, variant="secondary", elem_id="arsp__arc_show_info_button")
|
|
||||||
arc_hide_info = ToolButton(value=INFO_SYMBOL, min_width=84, scale = 1.0, visible=False, variant="primary", elem_id="arsp__arc_hide_info_button")
|
|
||||||
# Click event handling for info window
|
|
||||||
def toggle_info():
|
|
||||||
arc_panel.visible = not arc_panel.visible
|
|
||||||
arc_show_info.visible = not arc_show_info.visible
|
|
||||||
arc_hide_info.visible = not arc_hide_info.visible
|
|
||||||
|
|
||||||
arc_show_info.click(toggle_info)
|
|
||||||
arc_hide_info.click(toggle_info)
|
|
||||||
|
|
||||||
# Static resolution buttons
|
|
||||||
btns = [ResButton(res=res, value=label) for res, label in zip(self.res, self.res_labels)]
|
|
||||||
# Click event handling for static res buttons
|
|
||||||
with contextlib.suppress(AttributeError):
|
|
||||||
for b in btns:
|
|
||||||
b.click(b.reset, inputs=[arc_avg], outputs=[arc_avg, w, h])
|
|
||||||
|
|
||||||
# Write button_titles.js with labels and comments read from aspect ratios and resolutions files
|
|
||||||
button_titles = [self.aspect_ratios + self.res_labels]
|
|
||||||
button_titles.append(self.aspect_ratio_comments + self.res_comments)
|
|
||||||
write_js_titles_file(button_titles)
|
|
||||||
|
|
||||||
# Information panel
|
|
||||||
with gr.Column(visible=False, variant="panel", elem_id="arsp__arc_panel") as arc_panel:
|
|
||||||
with gr.Row():
|
|
||||||
with gr.Column(scale=2, min_width=100):
|
|
||||||
# render the current average number box
|
|
||||||
arc_avg.render()
|
|
||||||
|
|
||||||
# render the precision input box
|
|
||||||
arc_prec.render()
|
|
||||||
|
|
||||||
# Information blurb
|
|
||||||
gr.Column(scale=1, min_width=10)
|
|
||||||
with gr.Column(scale=12):
|
|
||||||
arc_title_heading = gr.Markdown(value=
|
|
||||||
'''
|
|
||||||
### AR and Static Res buttons can be customized in the 'aspect_ratios.txt' and 'resolutions.txt' files
|
|
||||||
### Top Row:
|
|
||||||
**Aspect Ratio (AR) buttons** - calculate and apply desireable width/height by: (1) Averaging the current width/height in the UI; (2) Offsetting to the exact AR; (3) Rounding to precision.
|
|
||||||
**Lock button** - captures the current average width/height in the UI. This is good when switching between ARs. When unlocked, the average will change due to rounding.
|
|
||||||
**Switch button** - toggles between landscape and portrait orientation for ease of use.
|
|
||||||
### Bottom Row:
|
|
||||||
**Static Resolution buttons** - recommended to use 1:1 values for these, to serve as a start point before clicking AR buttons.
|
|
||||||
**Mode button** will change the method of the AR buttons to calculate only one value, rather than offsetting.
|
|
||||||
'''
|
|
||||||
)
|
|
||||||
|
|
||||||
# Show info pane
|
|
||||||
arc_show_info.click(
|
|
||||||
lambda: [
|
|
||||||
gr.update(visible=True),
|
|
||||||
gr.update(visible=False),
|
|
||||||
gr.update(visible=True),
|
|
||||||
],
|
|
||||||
None,
|
|
||||||
[
|
|
||||||
arc_panel,
|
|
||||||
arc_show_info,
|
|
||||||
arc_hide_info,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
# Hide info pane
|
|
||||||
arc_hide_info.click(
|
|
||||||
lambda: [
|
|
||||||
gr.update(visible=False),
|
|
||||||
gr.update(visible=True),
|
|
||||||
gr.update(visible=False),
|
|
||||||
],
|
|
||||||
None,
|
|
||||||
[arc_panel, arc_show_info, arc_hide_info],
|
|
||||||
)
|
|
||||||
|
|
||||||
def _arc_show_lock_update():
|
|
||||||
global is_lock_mode
|
|
||||||
is_lock_mode = not is_lock_mode
|
|
||||||
|
|
||||||
return [
|
|
||||||
arc_show_lock.update(visible=False),
|
|
||||||
arc_hide_lock.update(visible=True),
|
|
||||||
]
|
|
||||||
def _arc_hide_lock_update():
|
|
||||||
global is_lock_mode
|
|
||||||
is_lock_mode = not is_lock_mode
|
|
||||||
|
|
||||||
return [
|
|
||||||
arc_show_lock.update(visible=True),
|
|
||||||
arc_hide_lock.update(visible=False),
|
|
||||||
]
|
|
||||||
|
|
||||||
arc_show_lock.click(
|
|
||||||
_arc_show_lock_update,
|
|
||||||
None,
|
|
||||||
[arc_show_lock, arc_hide_lock],
|
|
||||||
)
|
|
||||||
arc_hide_lock.click(
|
|
||||||
_arc_hide_lock_update,
|
|
||||||
None,
|
|
||||||
[arc_show_lock, arc_hide_lock],
|
|
||||||
)
|
|
||||||
|
|
||||||
def _arc_sw_on_update():
|
|
||||||
global is_switch_mode
|
|
||||||
is_switch_mode = not is_switch_mode
|
|
||||||
|
|
||||||
return [
|
|
||||||
arc_sw_on.update(visible=False),
|
|
||||||
arc_sw_off.update(visible=True),
|
|
||||||
]
|
|
||||||
def _arc_sw_off_update():
|
|
||||||
global is_switch_mode
|
|
||||||
is_switch_mode = not is_switch_mode
|
|
||||||
|
|
||||||
return [
|
|
||||||
arc_sw_on.update(visible=True),
|
|
||||||
arc_sw_off.update(visible=False),
|
|
||||||
]
|
|
||||||
|
|
||||||
arc_sw_on.click(
|
|
||||||
_arc_sw_on_update,
|
|
||||||
None,
|
|
||||||
[arc_sw_on, arc_sw_off],
|
|
||||||
)
|
|
||||||
arc_sw_off.click(
|
|
||||||
_arc_sw_off_update,
|
|
||||||
None,
|
|
||||||
[arc_sw_on, arc_sw_off],
|
|
||||||
)
|
|
||||||
|
|
||||||
## Function to update the values in appropriate Width/Height fields
|
|
||||||
# https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/7456#issuecomment-1414465888
|
|
||||||
def after_component(self, component, **kwargs):
|
|
||||||
if kwargs.get("elem_id") == "txt2img_width":
|
|
||||||
self.t2i_w = component
|
|
||||||
if kwargs.get("elem_id") == "txt2img_height":
|
|
||||||
self.t2i_h = component
|
|
||||||
if kwargs.get("elem_id") == "img2img_width":
|
|
||||||
self.i2i_w = component
|
|
||||||
if kwargs.get("elem_id") == "img2img_height":
|
|
||||||
self.i2i_h = component
|
|
||||||
if kwargs.get("elem_id") == "img2img_image":
|
|
||||||
self.image = [component]
|
|
||||||
if kwargs.get("elem_id") == "img2img_sketch":
|
|
||||||
self.image.append(component)
|
|
||||||
if kwargs.get("elem_id") == "img2maskimg":
|
|
||||||
self.image.append(component)
|
|
||||||
if kwargs.get("elem_id") == "inpaint_sketch":
|
|
||||||
self.image.append(component)
|
|
||||||
if kwargs.get("elem_id") == "img_inpaint_base":
|
|
||||||
self.image.append(component)
|
|
||||||
@@ -20,10 +20,11 @@
|
|||||||
max-width: unset !important;
|
max-width: unset !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button#arsp__arc_lock_button,
|
||||||
|
button#arsp__arc_mode_button,
|
||||||
|
button#arsp__arc_sw_button,
|
||||||
button#arsp__arc_show_info_button,
|
button#arsp__arc_show_info_button,
|
||||||
button#arsp__arc_hide_info_button,
|
button#arsp__arc_hide_info_button {
|
||||||
button#arsp__arc_show_logic_button,
|
|
||||||
button#arsp__arc_hide_logic_button {
|
|
||||||
max-width: 40px !important;
|
max-width: 40px !important;
|
||||||
min-width: unset !important;
|
min-width: unset !important;
|
||||||
padding: var(--size-0-5) var(--size-2) !important;
|
padding: var(--size-0-5) var(--size-2) !important;
|
||||||
|
|||||||
Reference in New Issue
Block a user