aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--javascript/ComponentControllers.js2
-rw-r--r--javascript/aspectRatioSliders.js164
-rw-r--r--modules/shared.py17
-rw-r--r--modules/ui.py10
-rw-r--r--style.css46
5 files changed, 218 insertions, 21 deletions
diff --git a/javascript/ComponentControllers.js b/javascript/ComponentControllers.js
index 194589c7..2888679b 100644
--- a/javascript/ComponentControllers.js
+++ b/javascript/ComponentControllers.js
@@ -189,6 +189,8 @@ class SliderComponentController {
}
eventHandler() {
this.element.dispatchEvent(new Event("input"));
+ this.childNumField.dispatchEvent(new Event("input"));
+ this.childRangeField.dispatchEvent(new Event("input"));
}
setVal(text) {
this.updateVal(text);
diff --git a/javascript/aspectRatioSliders.js b/javascript/aspectRatioSliders.js
index f577750a..d9c4f675 100644
--- a/javascript/aspectRatioSliders.js
+++ b/javascript/aspectRatioSliders.js
@@ -1,14 +1,61 @@
class AspectRatioSliderController {
- constructor(widthSlider, heightSlider, ratioSource) {
+ constructor(widthSlider, heightSlider, ratioSource, roundingSource, roundingMethod) {
+ //References
this.widthSlider = new SliderComponentController(widthSlider);
this.heightSlider = new SliderComponentController(heightSlider);
this.ratioSource = new DropdownComponentController(ratioSource);
- this.widthSlider.childRangeField.addEventListener("change", () => this.resize("width"));
- this.widthSlider.childNumField.addEventListener("change", () => this.resize("width"));
- this.heightSlider.childRangeField.addEventListener("change", () => this.resize("height"));
- this.heightSlider.childNumField.addEventListener("change", () => this.resize("height"));
+ this.roundingSource = new CheckboxComponentController(roundingSource);
+ this.roundingMethod = new RadioComponentController(roundingMethod);
+ this.roundingIndicatorBadge = document.createElement("div");
+ // Badge implementation
+ this.roundingIndicatorBadge.innerText = "📐";
+ this.roundingIndicatorBadge.classList.add("rounding-badge");
+ this.ratioSource.element.appendChild(this.roundingIndicatorBadge);
+ // Check initial value of ratioSource to set badge visbility
+ let initialRatio = this.ratioSource.getVal();
+ if (!initialRatio.includes(":")) {
+ this.roundingIndicatorBadge.style.display = "none";
+ }
+ //Adjust badge icon if rounding is on
+ if (this.roundingSource.getVal()) {
+ this.roundingIndicatorBadge.classList.add("active");
+ this.roundingIndicatorBadge.innerText = "⚠️";
+ }
+ //Make badge clickable to toggle setting
+ this.roundingIndicatorBadge.addEventListener("click", () => {
+ this.roundingSource.setVal(!this.roundingSource.getVal());
+ });
+ //Make rounding setting toggle badge text and style if setting changes
+ this.roundingSource.child.addEventListener("change", () => {
+ if (this.roundingSource.getVal()) {
+ this.roundingIndicatorBadge.classList.add("active");
+ this.roundingIndicatorBadge.innerText = "⚠️";
+ }
+ else {
+ this.roundingIndicatorBadge.classList.remove("active");
+ this.roundingIndicatorBadge.innerText = "📐";
+ }
+ this.adjustStepSize();
+ });
+ //Other event listeners
+ this.widthSlider.childRangeField.addEventListener("change", (e) => { e.preventDefault(); this.resize("width"); });
+ this.widthSlider.childNumField.addEventListener("change", (e) => { e.preventDefault(); this.resize("width"); });
+ this.heightSlider.childRangeField.addEventListener("change", (e) => { e.preventDefault(); this.resize("height"); });
+ this.heightSlider.childNumField.addEventListener("change", (e) => { e.preventDefault(); this.resize("height"); });
+ this.ratioSource.childSelector.addEventListener("change", (e) => {
+ e.preventDefault();
+ //Check and toggle display of badge conditionally on dropdown selection
+ if (!this.ratioSource.getVal().includes(":")) {
+ this.roundingIndicatorBadge.style.display = 'none';
+ }
+ else {
+ this.roundingIndicatorBadge.style.display = 'block';
+ }
+ this.adjustStepSize();
+ });
}
resize(dimension) {
+ //For moving slider or number field
let val = this.ratioSource.getVal();
if (!val.includes(":")) {
return;
@@ -16,26 +63,119 @@ class AspectRatioSliderController {
let [width, height] = val.split(":").map(Number);
let ratio = width / height;
if (dimension == 'width') {
- this.heightSlider.setVal(Math.round(parseFloat(this.widthSlider.getVal()) / ratio).toString());
+ let newHeight = parseInt(this.widthSlider.getVal()) / ratio;
+ if (this.roundingSource.getVal()) {
+ switch (this.roundingMethod.getVal()) {
+ case 'Round':
+ newHeight = Math.round(newHeight / 8) * 8;
+ break;
+ case 'Ceiling':
+ newHeight = Math.ceil(newHeight / 8) * 8;
+ break;
+ case 'Floor':
+ newHeight = Math.floor(newHeight / 8) * 8;
+ break;
+ }
+ }
+ this.heightSlider.setVal(newHeight.toString());
}
else if (dimension == "height") {
- this.widthSlider.setVal(Math.round(parseFloat(this.heightSlider.getVal()) * ratio).toString());
+ let newWidth = parseInt(this.heightSlider.getVal()) * ratio;
+ if (this.roundingSource.getVal()) {
+ switch (this.roundingMethod.getVal()) {
+ case 'Round':
+ newWidth = Math.round(newWidth / 8) * 8;
+ break;
+ case 'Ceiling':
+ newWidth = Math.ceil(newWidth / 8) * 8;
+ break;
+ case 'Floor':
+ newWidth = Math.floor(newWidth / 8) * 8;
+ break;
+ }
+ }
+ this.widthSlider.setVal(newWidth.toString());
+ }
+ }
+ adjustStepSize() {
+ /* Sets scales/precision/rounding steps;*/
+ let val = this.ratioSource.getVal();
+ if (!val.includes(":")) {
+ //If ratio unlocked
+ this.widthSlider.childRangeField.step = "8";
+ this.widthSlider.childRangeField.min = "64";
+ this.widthSlider.childNumField.step = "8";
+ this.widthSlider.childNumField.min = "64";
+ this.heightSlider.childRangeField.step = "8";
+ this.heightSlider.childRangeField.min = "64";
+ this.heightSlider.childNumField.step = "8";
+ this.heightSlider.childNumField.min = "64";
+ return;
+ }
+ //Format string and calculate step sizes
+ let [width, height] = val.split(":").map(Number);
+ let decimalPlaces = (width.toString().split(".")[1] || []).length;
+ //keep upto 6 decimal points of precision of ratio
+ //euclidean gcd does not support floats, so we scale it up
+ decimalPlaces = decimalPlaces > 6 ? 6 : decimalPlaces;
+ let gcd = this.gcd(width * 10 ** decimalPlaces, height * 10 ** decimalPlaces) / 10 ** decimalPlaces;
+ let stepSize = 8 * height / gcd;
+ let stepSizeOther = 8 * width / gcd;
+ if (this.roundingSource.getVal()) {
+ //If rounding is on set/keep default stepsizes
+ this.widthSlider.childRangeField.step = "8";
+ this.widthSlider.childRangeField.min = "64";
+ this.widthSlider.childNumField.step = "8";
+ this.widthSlider.childNumField.min = "64";
+ this.heightSlider.childRangeField.step = "8";
+ this.heightSlider.childRangeField.min = "64";
+ this.heightSlider.childNumField.step = "8";
+ this.heightSlider.childNumField.min = "64";
+ }
+ else {
+ //if rounding is off, set step sizes so they enforce snapping
+ //min is changed, because it offsets snap positions
+ this.widthSlider.childRangeField.step = stepSizeOther.toString();
+ this.widthSlider.childRangeField.min = stepSizeOther.toString();
+ this.widthSlider.childNumField.step = stepSizeOther.toString();
+ this.widthSlider.childNumField.min = stepSizeOther.toString();
+ this.heightSlider.childRangeField.step = stepSize.toString();
+ this.heightSlider.childRangeField.min = stepSize.toString();
+ this.heightSlider.childNumField.step = stepSize.toString();
+ this.heightSlider.childNumField.min = stepSize.toString();
+ }
+ let currentWidth = parseInt(this.widthSlider.getVal());
+ //Rounding treated kinda like pythons divmod
+ let stepsTaken = Math.round(currentWidth / stepSizeOther);
+ //this snaps it to closest rule matches (rules being html step points, and ratio)
+ let newWidth = stepsTaken * stepSizeOther;
+ this.widthSlider.setVal(newWidth.toString());
+ this.heightSlider.setVal(Math.round(newWidth / (width / height)).toString());
+ }
+ gcd(a, b) {
+ //euclidean gcd
+ if (b === 0) {
+ return a;
}
+ return this.gcd(b, a % b);
}
- static observeStartup(widthSliderId, heightSliderId, ratioSourceId) {
+ static observeStartup(widthSliderId, heightSliderId, ratioSourceId, roundingSourceId, roundingMethodId) {
let observer = new MutationObserver(() => {
let widthSlider = document.querySelector("gradio-app").shadowRoot.getElementById(widthSliderId);
let heightSlider = document.querySelector("gradio-app").shadowRoot.getElementById(heightSliderId);
let ratioSource = document.querySelector("gradio-app").shadowRoot.getElementById(ratioSourceId);
- if (widthSlider && heightSlider && ratioSource) {
+ let roundingSource = document.querySelector("gradio-app").shadowRoot.getElementById(roundingSourceId);
+ let roundingMethod = document.querySelector("gradio-app").shadowRoot.getElementById(roundingMethodId);
+ if (widthSlider && heightSlider && ratioSource && roundingSource && roundingMethod) {
observer.disconnect();
- new AspectRatioSliderController(widthSlider, heightSlider, ratioSource);
+ new AspectRatioSliderController(widthSlider, heightSlider, ratioSource, roundingSource, roundingMethod);
}
});
observer.observe(gradioApp(), { childList: true, subtree: true });
}
}
document.addEventListener("DOMContentLoaded", () => {
- AspectRatioSliderController.observeStartup("txt2img_width", "txt2img_height", "txt2img_ratio");
- AspectRatioSliderController.observeStartup("img2img_width", "img2img_height", "img2img_ratio");
+ //Register mutation observer for self start-up;
+ AspectRatioSliderController.observeStartup("txt2img_width", "txt2img_height", "txt2img_ratio", "setting_aspect_ratios_rounding", "setting_aspect_ratios_rounding_method");
+ AspectRatioSliderController.observeStartup("img2img_width", "img2img_height", "img2img_ratio", "setting_aspect_ratios_rounding", "setting_aspect_ratios_rounding_method");
});
diff --git a/modules/shared.py b/modules/shared.py
index ead7be36..fcd6eadf 100644
--- a/modules/shared.py
+++ b/modules/shared.py
@@ -140,16 +140,21 @@ ui_reorder_categories = [
]
aspect_ratio_defaults = [
- "🔓"
+ "🔓",
"1:1",
- "1:2",
- "2:1",
- "2:3",
"3:2",
"4:3",
"5:4",
- "9:16",
"16:9",
+ "9:16",
+ "1.85:1",
+ "2.35:1",
+ "2.39:1",
+ "2.40:1",
+ "21:9",
+ "1.375:1",
+ "1.66:1",
+ "1.75:1"
]
cmd_opts.disable_extension_access = (cmd_opts.share or cmd_opts.listen or cmd_opts.server_name) and not cmd_opts.enable_insecure_extension_access
@@ -469,6 +474,8 @@ options_templates.update(options_section(('ui', "User interface"), {
"keyedit_precision_extra": OptionInfo(0.05, "Ctrl+up/down precision when editing <extra networks:0.9>", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}),
"quicksettings": OptionInfo("sd_model_checkpoint", "Quicksettings list"),
"ui_reorder": OptionInfo(", ".join(ui_reorder_categories), "txt2img/img2img UI item order"),
+ "aspect_ratios_rounding": OptionInfo(True, "Round aspect ratios for more flexibility?", gr.Checkbox),
+ "aspect_ratios_rounding_method": OptionInfo("Ceiling", "Aspect ratios rounding method", gr.Radio,{"choices": ["Round", "Ceiling", "Floor"]}),
"aspect_ratios": OptionInfo(", ".join(aspect_ratio_defaults), "txt2img/img2img aspect ratios"),
"ui_extra_networks_tab_reorder": OptionInfo("", "Extra networks tab order"),
"localization": OptionInfo("None", "Localization (requires restart)", gr.Dropdown, lambda: {"choices": ["None"] + list(localization.localizations.keys())}, refresh=lambda: localization.list_localizations(cmd_opts.localizations_dir)),
diff --git a/modules/ui.py b/modules/ui.py
index 6853485c..873c857a 100644
--- a/modules/ui.py
+++ b/modules/ui.py
@@ -483,8 +483,9 @@ def create_ui():
width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="txt2img_width")
height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="txt2img_height")
- res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="txt2img_res_switch_btn")
- aspect_ratio_dropdown = gr.Dropdown(value="🔓", choices=aspect_ratio_list(), interactive=True, type="value", elem_id="txt2img_ratio", show_label=False, label="Aspect Ratio")
+ with gr.Column(elem_id="txt2img_size_toolbox", scale=0):
+ aspect_ratio_dropdown = gr.Dropdown(value="🔓", choices=aspect_ratio_list(), interactive=True, type="value", elem_id="txt2img_ratio", show_label=False, label="Aspect Ratio")
+ res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="txt2img_res_switch_btn")
if opts.dimensions_and_batch_together:
with gr.Column(elem_id="txt2img_column_batch"):
batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id="txt2img_batch_count")
@@ -762,8 +763,9 @@ def create_ui():
width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="img2img_width")
height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="img2img_height")
- res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="img2img_res_switch_btn")
- aspect_ratio_dropdown = gr.Dropdown(value="🔓", choices=aspect_ratio_list(), interactive=True, type="value", elem_id="img2img_ratio", show_label=False, label="Aspect Ratio")
+ with gr.Column(elem_id="img2img_size_toolbox", scale=0):
+ aspect_ratio_dropdown = gr.Dropdown(value="🔓", choices=aspect_ratio_list(), interactive=True, type="value", elem_id="img2img_ratio", show_label=False, label="Aspect Ratio")
+ res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="img2img_res_switch_btn")
if opts.dimensions_and_batch_together:
with gr.Column(elem_id="img2img_column_batch"):
batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id="img2img_batch_count")
diff --git a/style.css b/style.css
index 05572f66..5b979841 100644
--- a/style.css
+++ b/style.css
@@ -747,6 +747,52 @@ footer {
margin-left: 0em;
}
+#txt2img_size_toolbox, #img2img_size_toolbox{
+ min-width: unset !important;
+ gap: 0;
+}
+
+#txt2img_ratio, #img2img_ratio {
+ padding: 0px;
+ min-width: unset;
+ max-width: fit-content;
+}
+#txt2img_ratio select, #img2img_ratio select{
+ -o-appearance: none;
+ -ms-appearance: none;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ background-image: unset;
+ padding-right: unset;
+ min-width: 40px;
+ max-width: 40px;
+ min-height: 40px;
+ max-height: 40px;
+ line-height: 40px;
+ padding: 0;
+ text-align: center;
+}
+.rounding-badge {
+ display: inline-block;
+ border-radius: 0px;
+ background-color: #ccc;
+ cursor: pointer;
+ position: absolute;
+ top: -10px;
+ right: -10px;
+ width: 20px;
+ height: 20px;
+ padding: 1px;
+ line-height: 16px;
+ font-size: 14px;
+}
+
+.rounding-badge.active {
+ background-color: #007bff;
+ border-radius: 50%;
+}
+
.inactive{
opacity: 0.5;
}