|
1 | 1 | class AspectRatioSliderController { |
2 | | - constructor(widthSlider, heightSlider, ratioSource) { |
| 2 | + constructor(widthSlider, heightSlider, ratioSource, roundingSource, roundingMethod) { |
| 3 | + //References |
3 | 4 | this.widthSlider = new SliderComponentController(widthSlider); |
4 | 5 | this.heightSlider = new SliderComponentController(heightSlider); |
5 | 6 | this.ratioSource = new DropdownComponentController(ratioSource); |
6 | | - this.widthSlider.childRangeField.addEventListener("change", () => this.resize("width")); |
7 | | - this.widthSlider.childNumField.addEventListener("change", () => this.resize("width")); |
8 | | - this.heightSlider.childRangeField.addEventListener("change", () => this.resize("height")); |
9 | | - this.heightSlider.childNumField.addEventListener("change", () => this.resize("height")); |
| 7 | + this.roundingSource = new CheckboxComponentController(roundingSource); |
| 8 | + this.roundingMethod = new RadioComponentController(roundingMethod); |
| 9 | + this.roundingIndicatorBadge = document.createElement("div"); |
| 10 | + // Badge implementation |
| 11 | + this.roundingIndicatorBadge.innerText = "📐"; |
| 12 | + this.roundingIndicatorBadge.classList.add("rounding-badge"); |
| 13 | + this.ratioSource.element.appendChild(this.roundingIndicatorBadge); |
| 14 | + // Check initial value of ratioSource to set badge visbility |
| 15 | + let initialRatio = this.ratioSource.getVal(); |
| 16 | + if (!initialRatio.includes(":")) { |
| 17 | + this.roundingIndicatorBadge.style.display = "none"; |
| 18 | + } |
| 19 | + //Adjust badge icon if rounding is on |
| 20 | + if (this.roundingSource.getVal()) { |
| 21 | + this.roundingIndicatorBadge.classList.add("active"); |
| 22 | + this.roundingIndicatorBadge.innerText = "⚠️"; |
| 23 | + } |
| 24 | + //Make badge clickable to toggle setting |
| 25 | + this.roundingIndicatorBadge.addEventListener("click", () => { |
| 26 | + this.roundingSource.setVal(!this.roundingSource.getVal()); |
| 27 | + }); |
| 28 | + //Make rounding setting toggle badge text and style if setting changes |
| 29 | + this.roundingSource.child.addEventListener("change", () => { |
| 30 | + if (this.roundingSource.getVal()) { |
| 31 | + this.roundingIndicatorBadge.classList.add("active"); |
| 32 | + this.roundingIndicatorBadge.innerText = "⚠️"; |
| 33 | + } |
| 34 | + else { |
| 35 | + this.roundingIndicatorBadge.classList.remove("active"); |
| 36 | + this.roundingIndicatorBadge.innerText = "📐"; |
| 37 | + } |
| 38 | + this.adjustStepSize(); |
| 39 | + }); |
| 40 | + //Other event listeners |
| 41 | + this.widthSlider.childRangeField.addEventListener("change", (e) => { e.preventDefault(); this.resize("width"); }); |
| 42 | + this.widthSlider.childNumField.addEventListener("change", (e) => { e.preventDefault(); this.resize("width"); }); |
| 43 | + this.heightSlider.childRangeField.addEventListener("change", (e) => { e.preventDefault(); this.resize("height"); }); |
| 44 | + this.heightSlider.childNumField.addEventListener("change", (e) => { e.preventDefault(); this.resize("height"); }); |
| 45 | + this.ratioSource.childSelector.addEventListener("change", (e) => { |
| 46 | + e.preventDefault(); |
| 47 | + //Check and toggle display of badge conditionally on dropdown selection |
| 48 | + if (!this.ratioSource.getVal().includes(":")) { |
| 49 | + this.roundingIndicatorBadge.style.display = 'none'; |
| 50 | + } |
| 51 | + else { |
| 52 | + this.roundingIndicatorBadge.style.display = 'block'; |
| 53 | + } |
| 54 | + this.adjustStepSize(); |
| 55 | + }); |
10 | 56 | } |
11 | 57 | resize(dimension) { |
| 58 | + //For moving slider or number field |
12 | 59 | let val = this.ratioSource.getVal(); |
13 | 60 | if (!val.includes(":")) { |
14 | 61 | return; |
15 | 62 | } |
16 | 63 | let [width, height] = val.split(":").map(Number); |
17 | 64 | let ratio = width / height; |
18 | 65 | if (dimension == 'width') { |
19 | | - this.heightSlider.setVal(Math.round(parseFloat(this.widthSlider.getVal()) / ratio).toString()); |
| 66 | + let newHeight = parseInt(this.widthSlider.getVal()) / ratio; |
| 67 | + if (this.roundingSource.getVal()) { |
| 68 | + switch (this.roundingMethod.getVal()) { |
| 69 | + case 'Round': |
| 70 | + newHeight = Math.round(newHeight / 8) * 8; |
| 71 | + break; |
| 72 | + case 'Ceiling': |
| 73 | + newHeight = Math.ceil(newHeight / 8) * 8; |
| 74 | + break; |
| 75 | + case 'Floor': |
| 76 | + newHeight = Math.floor(newHeight / 8) * 8; |
| 77 | + break; |
| 78 | + } |
| 79 | + } |
| 80 | + this.heightSlider.setVal(newHeight.toString()); |
20 | 81 | } |
21 | 82 | else if (dimension == "height") { |
22 | | - this.widthSlider.setVal(Math.round(parseFloat(this.heightSlider.getVal()) * ratio).toString()); |
| 83 | + let newWidth = parseInt(this.heightSlider.getVal()) * ratio; |
| 84 | + if (this.roundingSource.getVal()) { |
| 85 | + switch (this.roundingMethod.getVal()) { |
| 86 | + case 'Round': |
| 87 | + newWidth = Math.round(newWidth / 8) * 8; |
| 88 | + break; |
| 89 | + case 'Ceiling': |
| 90 | + newWidth = Math.ceil(newWidth / 8) * 8; |
| 91 | + break; |
| 92 | + case 'Floor': |
| 93 | + newWidth = Math.floor(newWidth / 8) * 8; |
| 94 | + break; |
| 95 | + } |
| 96 | + } |
| 97 | + this.widthSlider.setVal(newWidth.toString()); |
| 98 | + } |
| 99 | + } |
| 100 | + adjustStepSize() { |
| 101 | + /* Sets scales/precision/rounding steps;*/ |
| 102 | + let val = this.ratioSource.getVal(); |
| 103 | + if (!val.includes(":")) { |
| 104 | + //If ratio unlocked |
| 105 | + this.widthSlider.childRangeField.step = "8"; |
| 106 | + this.widthSlider.childRangeField.min = "64"; |
| 107 | + this.widthSlider.childNumField.step = "8"; |
| 108 | + this.widthSlider.childNumField.min = "64"; |
| 109 | + this.heightSlider.childRangeField.step = "8"; |
| 110 | + this.heightSlider.childRangeField.min = "64"; |
| 111 | + this.heightSlider.childNumField.step = "8"; |
| 112 | + this.heightSlider.childNumField.min = "64"; |
| 113 | + return; |
| 114 | + } |
| 115 | + //Format string and calculate step sizes |
| 116 | + let [width, height] = val.split(":").map(Number); |
| 117 | + let decimalPlaces = (width.toString().split(".")[1] || []).length; |
| 118 | + //keep upto 6 decimal points of precision of ratio |
| 119 | + //euclidean gcd does not support floats, so we scale it up |
| 120 | + decimalPlaces = decimalPlaces > 6 ? 6 : decimalPlaces; |
| 121 | + let gcd = this.gcd(width * 10 ** decimalPlaces, height * 10 ** decimalPlaces) / 10 ** decimalPlaces; |
| 122 | + let stepSize = 8 * height / gcd; |
| 123 | + let stepSizeOther = 8 * width / gcd; |
| 124 | + if (this.roundingSource.getVal()) { |
| 125 | + //If rounding is on set/keep default stepsizes |
| 126 | + this.widthSlider.childRangeField.step = "8"; |
| 127 | + this.widthSlider.childRangeField.min = "64"; |
| 128 | + this.widthSlider.childNumField.step = "8"; |
| 129 | + this.widthSlider.childNumField.min = "64"; |
| 130 | + this.heightSlider.childRangeField.step = "8"; |
| 131 | + this.heightSlider.childRangeField.min = "64"; |
| 132 | + this.heightSlider.childNumField.step = "8"; |
| 133 | + this.heightSlider.childNumField.min = "64"; |
| 134 | + } |
| 135 | + else { |
| 136 | + //if rounding is off, set step sizes so they enforce snapping |
| 137 | + //min is changed, because it offsets snap positions |
| 138 | + this.widthSlider.childRangeField.step = stepSizeOther.toString(); |
| 139 | + this.widthSlider.childRangeField.min = stepSizeOther.toString(); |
| 140 | + this.widthSlider.childNumField.step = stepSizeOther.toString(); |
| 141 | + this.widthSlider.childNumField.min = stepSizeOther.toString(); |
| 142 | + this.heightSlider.childRangeField.step = stepSize.toString(); |
| 143 | + this.heightSlider.childRangeField.min = stepSize.toString(); |
| 144 | + this.heightSlider.childNumField.step = stepSize.toString(); |
| 145 | + this.heightSlider.childNumField.min = stepSize.toString(); |
| 146 | + } |
| 147 | + let currentWidth = parseInt(this.widthSlider.getVal()); |
| 148 | + //Rounding treated kinda like pythons divmod |
| 149 | + let stepsTaken = Math.round(currentWidth / stepSizeOther); |
| 150 | + //this snaps it to closest rule matches (rules being html step points, and ratio) |
| 151 | + let newWidth = stepsTaken * stepSizeOther; |
| 152 | + this.widthSlider.setVal(newWidth.toString()); |
| 153 | + this.heightSlider.setVal(Math.round(newWidth / (width / height)).toString()); |
| 154 | + } |
| 155 | + gcd(a, b) { |
| 156 | + //euclidean gcd |
| 157 | + if (b === 0) { |
| 158 | + return a; |
23 | 159 | } |
| 160 | + return this.gcd(b, a % b); |
24 | 161 | } |
25 | | - static observeStartup(widthSliderId, heightSliderId, ratioSourceId) { |
| 162 | + static observeStartup(widthSliderId, heightSliderId, ratioSourceId, roundingSourceId, roundingMethodId) { |
26 | 163 | let observer = new MutationObserver(() => { |
27 | 164 | let widthSlider = document.querySelector("gradio-app").shadowRoot.getElementById(widthSliderId); |
28 | 165 | let heightSlider = document.querySelector("gradio-app").shadowRoot.getElementById(heightSliderId); |
29 | 166 | let ratioSource = document.querySelector("gradio-app").shadowRoot.getElementById(ratioSourceId); |
30 | | - if (widthSlider && heightSlider && ratioSource) { |
| 167 | + let roundingSource = document.querySelector("gradio-app").shadowRoot.getElementById(roundingSourceId); |
| 168 | + let roundingMethod = document.querySelector("gradio-app").shadowRoot.getElementById(roundingMethodId); |
| 169 | + if (widthSlider && heightSlider && ratioSource && roundingSource && roundingMethod) { |
31 | 170 | observer.disconnect(); |
32 | | - new AspectRatioSliderController(widthSlider, heightSlider, ratioSource); |
| 171 | + new AspectRatioSliderController(widthSlider, heightSlider, ratioSource, roundingSource, roundingMethod); |
33 | 172 | } |
34 | 173 | }); |
35 | 174 | observer.observe(gradioApp(), { childList: true, subtree: true }); |
36 | 175 | } |
37 | 176 | } |
38 | 177 | document.addEventListener("DOMContentLoaded", () => { |
39 | | - AspectRatioSliderController.observeStartup("txt2img_width", "txt2img_height", "txt2img_ratio"); |
40 | | - AspectRatioSliderController.observeStartup("img2img_width", "img2img_height", "img2img_ratio"); |
| 178 | + //Register mutation observer for self start-up; |
| 179 | + AspectRatioSliderController.observeStartup("txt2img_width", "txt2img_height", "txt2img_ratio", "setting_aspect_ratios_rounding", "setting_aspect_ratios_rounding_method"); |
| 180 | + AspectRatioSliderController.observeStartup("img2img_width", "img2img_height", "img2img_ratio", "setting_aspect_ratios_rounding", "setting_aspect_ratios_rounding_method"); |
41 | 181 | }); |
0 commit comments