Unverified Commit f04f4b28 authored by AUTOMATIC1111's avatar AUTOMATIC1111 Committed by GitHub
Browse files

Merge pull request #7925 from AUTOMATIC1111/revert-7601-aspect_ratio_sliders

Revert "Aspect ratio sliders"
parents b20f28ee fd4ac518
Loading
Loading
Loading
Loading
+0 −259
Original line number Diff line number Diff line
/* This is a basic library that allows controlling elements that take some form of user input.

This was previously written in typescript, where all controllers implemented an interface. Not
all methods were needed in all the controllers, but it was done to keep a common interface, so
your main app can serve as a controller of controllers.

These controllers were built to work on the shapes of html elements that gradio components use.

There may be some notes in it that only applied to my use case, but I left them to help others
along.

You will need the parent element for these to work.
The parent element can be defined as the element (div) that gets the element id when assigning
an element id to a gradio component.

Example:
  gr.TextBox(value="...", elem_id="THISID")

Basic usage, grab an element that is the parent container for the component.

Send it in to the class, like a function, don't forget the "new" keyword so it calls the constructor
and sends back a new object.

Example:

let txt2imgPrompt = new TextComponentController(gradioApp().querySelector("#txt2img_prompt"))

Then use the getVal() method to get the value, or use the setVal(myValue) method to set the value.

Input types that are groups, like Checkbox groups (not individual checkboxes), take in an array of values.

Checkbox group has to reset all values to False (unchecked), then set the values in your array to true (checked).
If you don't hold a reference to the values (the labels in string format), you can acquire them using the getVal() method.
*/
class DropdownComponentController {
    constructor(element) {
        this.element = element;
        this.childSelector = this.element.querySelector('select');
        this.children = new Map();
        Array.from(this.childSelector.querySelectorAll('option')).forEach(opt => this.children.set(opt.value, opt));
    }
    getVal() {
        return this.childSelector.value;
    }
    updateVal(optionElement) {
        optionElement.selected = true;
    }
    setVal(name) {
        this.updateVal(this.children.get(name));
        this.eventHandler();
    }
    eventHandler() {
        this.childSelector.dispatchEvent(new Event("change"));
    }
}
class CheckboxComponentController {
    constructor(element) {
        this.element = element;
        this.child = this.element.querySelector('input');
    }
    getVal() {
        return this.child.checked;
    }
    updateVal(checked) {
        this.child.checked = checked;
    }
    setVal(checked) {
        this.updateVal(checked);
        this.eventHandler();
    }
    eventHandler() {
        this.child.dispatchEvent(new Event("change"));
    }
}
class CheckboxGroupComponentController {
    constructor(element) {
        this.element = element;
        //this.checkBoxes = new Object;
        this.children = new Map();
        Array.from(this.element.querySelectorAll('input')).forEach(input => this.children.set(input.nextElementSibling.innerText, input));
        /* element id gets use fieldset, grab all inputs (the bool val) get the userfriendly label, use as key, put bool value in mapping   */
        //Array.from(this.component.querySelectorAll("input")).forEach( _input => this.checkBoxes[_input.nextElementSibling.innerText] = _input)
        /*Checkboxgroup structure
        <fieldset>
            <div> css makes translucent
            <span>
              serves as label for component
            </span>
            <div data-testid='checkbox-group'> container for checkboxes
                <label>
                    <input type=checkbox>
                    <span>checkbox words</span>
                </label>
                ...
            </div>
        </fieldset>
        */
    }
    updateVal(label) {
        /*********
         calls updates using a throttle or else the backend does not get updated properly
         * ********/
        setTimeout(() => this.conditionalToggle(true, this.children.get(label)), 2);
    }
    setVal(labels) {
        /* Handles reset and updates all in array to true */
        this.reupdateVals();
        labels.forEach(l => this.updateVal(l));
    }
    getVal() {
        //return the list of values that are true
        return [...this.children].filter(([k, v]) => v.checked).map(arr => arr[0]);
    }
    reupdateVals() {
        /**************
         * for reupdating all vals, first set to false
         **************/
        this.children.forEach(inputChild => this.conditionalToggle(false, inputChild));
    }
    conditionalToggle(desiredVal, inputChild) {
        //This method behaves like 'set this value to this'
        //Using element.checked = true/false, does not register the change, even if you called change afterwards,
        //  it only sets what it looks like in our case, because there is no form submit, a person then has to click on it twice.
        //Options are to use .click() or dispatch an event
        if (desiredVal != inputChild.checked) {
            inputChild.dispatchEvent(new Event("change")); //using change event instead of click, in case browser ad-blockers blocks the click method
        }
    }
    eventHandler(checkbox) {
        checkbox.dispatchEvent(new Event("change"));
    }
}
class RadioComponentController {
    constructor(element) {
        this.element = element;
        this.children = new Map();
        Array.from(this.element.querySelectorAll("input")).forEach(input => this.children.set(input.value, input));
    }
    getVal() {
        //radio groups have a single element that's checked is true
        //           as array       arr k,v pair    element.checked  ) -> array of len(1) with [k,v] so either [0] [1].value
        return [...this.children].filter(([l, e]) => e.checked)[0][0];
        //return Array.from(this.children).filter( ([label, input]) => input.checked)[0][1].value
    }
    updateVal(child) {
        this.eventHandler(child);
    }
    setVal(name) {
        //radio will trigger all false except the one that get the event change
        //to keep the api similar, other methods are still called
        this.updateVal(this.children.get(name));
    }
    eventHandler(child) {
        child.dispatchEvent(new Event("change"));
    }
}
class NumberComponentController {
    constructor(element) {
        this.element = element;
        this.childNumField = element.querySelector('input[type=number]');
    }
    getVal() {
        return this.childNumField.value;
    }
    updateVal(text) {
        this.childNumField.value = text;
    }
    eventHandler() {
        this.element.dispatchEvent(new Event("input"));
    }
    setVal(text) {
        this.updateVal(text);
        this.eventHandler();
    }
}
class SliderComponentController {
    constructor(element) {
        this.element = element;
        this.childNumField = this.element.querySelector('input[type=number]');
        this.childRangeField = this.element.querySelector('input[type=range]');
    }
    getVal() {
        return this.childNumField.value;
    }
    updateVal(text) {
        //both are not needed, either works, both are left in so one is a fallback in case of gradio changes 
        this.childNumField.value = text;
        this.childRangeField.value = text;
    }
    eventHandler() {
        this.element.dispatchEvent(new Event("input"));
        this.childNumField.dispatchEvent(new Event("input"));
        this.childRangeField.dispatchEvent(new Event("input"));
    }
    setVal(text) {
        this.updateVal(text);
        this.eventHandler();
    }
}
class TextComponentController {
    constructor(element) {
        this.element = element;
        this.child = element.querySelector('textarea');
    }
    getVal() {
        return this.child.value;
    }
    eventHandler() {
        this.element.dispatchEvent(new Event("input"));
        this.child.dispatchEvent(new Event("change"));
        //Workaround to solve no target with v(o) on eventhandler, define my own target
        let ne = new Event("input");
        Object.defineProperty(ne, "target", { value: this.child });
        this.child.dispatchEvent(ne);
    }
    updateVal(text) {
        this.child.value = text;
    }
    appendValue(text) {
        //might add delimiter option
        this.child.value += ` ${text}`;
    }
    setVal(text, append = false) {
        if (append) {
            this.appendValue(text);
        }
        else {
            this.updateVal(text);
        }
        this.eventHandler();
    }
}
class JsonComponentController extends TextComponentController {
    constructor(element) {
        super(element);
    }
    getVal() {
        return JSON.parse(this.child.value);
    }
}
class ColorComponentController {
    constructor(element) {
        this.element = element;
        this.child = this.element.querySelector('input[type=color]');
    }
    updateVal(text) {
        this.child.value = text;
    }
    getVal() {
        return this.child.value;
    }
    setVal(text) {
        this.updateVal(text);
        this.eventHandler();
    }
    eventHandler() {
        this.child.dispatchEvent(new Event("input"));
    }
}

javascript/aspectRatioSliders.js

deleted100644 → 0
+0 −181
Original line number Diff line number Diff line
class AspectRatioSliderController {
    constructor(widthSlider, heightSlider, ratioSource, roundingSource, roundingMethod) {
        //References
        this.widthSlider = new SliderComponentController(widthSlider);
        this.heightSlider = new SliderComponentController(heightSlider);
        this.ratioSource = new DropdownComponentController(ratioSource);
        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;
        }
        let [width, height] = val.split(":").map(Number);
        let ratio = width / height;
        if (dimension == 'width') {
            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") {
            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, 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);
            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, roundingSource, roundingMethod);
            }
        });
        observer.observe(gradioApp(), { childList: true, subtree: true });
    }
}
document.addEventListener("DOMContentLoaded", () => {
    //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");
});
+0 −21
Original line number Diff line number Diff line
@@ -139,24 +139,6 @@ ui_reorder_categories = [
    "scripts",
]

aspect_ratio_defaults = [
    "🔓",
    "1:1",
    "3:2",
    "4:3",
    "5:4",
    "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

devices.device, devices.device_interrogate, devices.device_gfpgan, devices.device_esrgan, devices.device_codeformer = \
@@ -477,9 +459,6 @@ 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)),
}))
+2 −10
Original line number Diff line number Diff line
@@ -424,10 +424,6 @@ def ordered_ui_categories():
        yield category


def aspect_ratio_list():
    return [ratio.strip() for ratio in shared.opts.aspect_ratios.split(",")]


def get_value_for_setting(key):
    value = getattr(opts, key)

@@ -483,8 +479,6 @@ 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")

                            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"):
@@ -763,8 +757,6 @@ 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")

                            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"):
+0 −46
Original line number Diff line number Diff line
@@ -747,52 +747,6 @@ 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;
}