From 920fe8057cb325e9835f70c0389499c51cbdd3b5 Mon Sep 17 00:00:00 2001 From: EllangoK Date: Sun, 29 Jan 2023 03:36:16 -0500 Subject: fixes #7284 btn unbound error --- modules/ui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'modules/ui.py') diff --git a/modules/ui.py b/modules/ui.py index f1195692..7e193240 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -466,8 +466,8 @@ 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") if opts.dimensions_and_batch_together: - res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="txt2img_res_switch_btn") 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") batch_size = gr.Slider(minimum=1, maximum=8, step=1, label='Batch size', value=1, elem_id="txt2img_batch_size") @@ -737,8 +737,8 @@ 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") if opts.dimensions_and_batch_together: - res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="img2img_res_switch_btn") 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") batch_size = gr.Slider(minimum=1, maximum=8, step=1, label='Batch size', value=1, elem_id="img2img_batch_size") -- cgit v1.2.3 From 6c6c6636bb123d664999c888cda47a1f8bad635b Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 3 Feb 2023 18:19:56 -0500 Subject: Image CFG Added (Full Implementation) Uses separate denoiser for edit (instruct-pix2pix) models No impact to txt2img or regular img2img "Image CFG Scale" will only apply to instruct-pix2pix models and metadata will only be added if using such model --- modules/img2img.py | 3 +- modules/processing.py | 4 +- modules/sd_samplers_kdiffusion.py | 101 +++++++++++++++++++++++++++++++++++--- modules/ui.py | 3 ++ 4 files changed, 103 insertions(+), 8 deletions(-) (limited to 'modules/ui.py') diff --git a/modules/img2img.py b/modules/img2img.py index f813299c..bcc158dc 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -76,7 +76,7 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args): processed_image.save(os.path.join(output_dir, filename)) -def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_styles, init_img, sketch, init_img_with_mask, inpaint_color_sketch, inpaint_color_sketch_orig, init_img_inpaint, init_mask_inpaint, steps: int, sampler_index: int, mask_blur: int, mask_alpha: float, inpainting_fill: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, denoising_strength: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, resize_mode: int, inpaint_full_res: bool, inpaint_full_res_padding: int, inpainting_mask_invert: int, img2img_batch_input_dir: str, img2img_batch_output_dir: str, img2img_batch_inpaint_mask_dir: str, override_settings_texts, *args): +def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_styles, init_img, sketch, init_img_with_mask, inpaint_color_sketch, inpaint_color_sketch_orig, init_img_inpaint, init_mask_inpaint, steps: int, sampler_index: int, mask_blur: int, mask_alpha: float, inpainting_fill: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, image_cfg_scale: float, denoising_strength: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, resize_mode: int, inpaint_full_res: bool, inpaint_full_res_padding: int, inpainting_mask_invert: int, img2img_batch_input_dir: str, img2img_batch_output_dir: str, img2img_batch_inpaint_mask_dir: str, override_settings_texts, *args): override_settings = create_override_settings_dict(override_settings_texts) is_batch = mode == 5 @@ -142,6 +142,7 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s inpainting_fill=inpainting_fill, resize_mode=resize_mode, denoising_strength=denoising_strength, + image_cfg_scale=image_cfg_scale, inpaint_full_res=inpaint_full_res, inpaint_full_res_padding=inpaint_full_res_padding, inpainting_mask_invert=inpainting_mask_invert, diff --git a/modules/processing.py b/modules/processing.py index f299e04d..c33694cc 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -445,6 +445,7 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter "Steps": p.steps, "Sampler": p.sampler_name, "CFG scale": p.cfg_scale, + "Image CFG scale": getattr(p, 'image_cfg_scale', None), "Seed": all_seeds[index], "Face restoration": (opts.face_restoration_model if p.restore_faces else None), "Size": f"{p.width}x{p.height}", @@ -901,12 +902,13 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): sampler = None - def __init__(self, init_images: list = None, resize_mode: int = 0, denoising_strength: float = 0.75, mask: Any = None, mask_blur: int = 4, inpainting_fill: int = 0, inpaint_full_res: bool = True, inpaint_full_res_padding: int = 0, inpainting_mask_invert: int = 0, initial_noise_multiplier: float = None, **kwargs): + def __init__(self, init_images: list = None, resize_mode: int = 0, denoising_strength: float = 0.75, image_cfg_scale: float = None, mask: Any = None, mask_blur: int = 4, inpainting_fill: int = 0, inpaint_full_res: bool = True, inpaint_full_res_padding: int = 0, inpainting_mask_invert: int = 0, initial_noise_multiplier: float = None, **kwargs): super().__init__(**kwargs) self.init_images = init_images self.resize_mode: int = resize_mode self.denoising_strength: float = denoising_strength + self.image_cfg_scale: float = image_cfg_scale if shared.sd_model.cond_stage_key == "edit" else None self.init_latent = None self.image_mask = mask self.latent_mask = None diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index aa7f106b..a16ba69b 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -1,6 +1,7 @@ from collections import deque import torch import inspect +import einops import k_diffusion.sampling from modules import prompt_parser, devices, sd_samplers_common @@ -40,6 +41,90 @@ sampler_extra_params = { 'sample_dpm_2': ['s_churn', 's_tmin', 's_tmax', 's_noise'], } +class CFGDenoiserEdit(torch.nn.Module): + """ + Classifier free guidance denoiser. A wrapper for stable diffusion model (specifically for unet) + that can take a noisy picture and produce a noise-free picture using two guidances (prompts) + instead of one. Originally, the second prompt is just an empty string, but we use non-empty + negative prompt. + """ + + def __init__(self, model): + super().__init__() + self.inner_model = model + self.mask = None + self.nmask = None + self.init_latent = None + self.step = 0 + + def combine_denoised(self, x_out, conds_list, uncond, cond_scale, image_cfg_scale): + denoised_uncond = x_out[-uncond.shape[0]:] + denoised = torch.clone(denoised_uncond) + + for i, conds in enumerate(conds_list): + for cond_index, weight in conds: + out_cond, out_img_cond, out_uncond = x_out.chunk(3) + denoised[i] = out_uncond[cond_index] + cond_scale * (out_cond[cond_index] - out_img_cond[cond_index]) + image_cfg_scale * (out_img_cond[cond_index] - out_uncond[cond_index]) + + return denoised + + def forward(self, x, sigma, uncond, cond, cond_scale, image_cond, image_cfg_scale): + if state.interrupted or state.skipped: + raise sd_samplers_common.InterruptedException + + conds_list, tensor = prompt_parser.reconstruct_multicond_batch(cond, self.step) + uncond = prompt_parser.reconstruct_cond_batch(uncond, self.step) + + batch_size = len(conds_list) + repeats = [len(conds_list[i]) for i in range(batch_size)] + + x_in = torch.cat([torch.stack([x[i] for _ in range(n)]) for i, n in enumerate(repeats)] + [x] + [x]) + sigma_in = torch.cat([torch.stack([sigma[i] for _ in range(n)]) for i, n in enumerate(repeats)] + [sigma] + [sigma]) + image_cond_in = torch.cat([torch.stack([image_cond[i] for _ in range(n)]) for i, n in enumerate(repeats)] + [image_cond] + [torch.zeros_like(self.init_latent)]) + + denoiser_params = CFGDenoiserParams(x_in, image_cond_in, sigma_in, state.sampling_step, state.sampling_steps) + cfg_denoiser_callback(denoiser_params) + x_in = denoiser_params.x + image_cond_in = denoiser_params.image_cond + sigma_in = denoiser_params.sigma + + if tensor.shape[1] == uncond.shape[1]: + cond_in = torch.cat([tensor, uncond, uncond]) + + if shared.batch_cond_uncond: + x_out = self.inner_model(x_in, sigma_in, cond={"c_crossattn": [cond_in], "c_concat": [image_cond_in]}) + else: + x_out = torch.zeros_like(x_in) + for batch_offset in range(0, x_out.shape[0], batch_size): + a = batch_offset + b = a + batch_size + x_out[a:b] = self.inner_model(x_in[a:b], sigma_in[a:b], cond={"c_crossattn": [cond_in[a:b]], "c_concat": [image_cond_in[a:b]]}) + else: + x_out = torch.zeros_like(x_in) + batch_size = batch_size*2 if shared.batch_cond_uncond else batch_size + for batch_offset in range(0, tensor.shape[0], batch_size): + a = batch_offset + b = min(a + batch_size, tensor.shape[0]) + x_out[a:b] = self.inner_model(x_in[a:b], sigma_in[a:b], cond={"c_crossattn": torch.cat([tensor[a:b]], uncond) , "c_concat": [image_cond_in[a:b]]}) + + x_out[-uncond.shape[0]:] = self.inner_model(x_in[-uncond.shape[0]:], sigma_in[-uncond.shape[0]:], cond={"c_crossattn": [uncond], "c_concat": [image_cond_in[-uncond.shape[0]:]]}) + + devices.test_for_nans(x_out, "unet") + + if opts.live_preview_content == "Prompt": + sd_samplers_common.store_latent(x_out[0:uncond.shape[0]]) + elif opts.live_preview_content == "Negative prompt": + sd_samplers_common.store_latent(x_out[-uncond.shape[0]:]) + + denoised = self.combine_denoised(x_out, conds_list, uncond, cond_scale, image_cfg_scale) + + if self.mask is not None: + denoised = self.init_latent * self.mask + self.nmask * denoised + + self.step += 1 + + return denoised + class CFGDenoiser(torch.nn.Module): """ @@ -78,8 +163,8 @@ class CFGDenoiser(torch.nn.Module): repeats = [len(conds_list[i]) for i in range(batch_size)] x_in = torch.cat([torch.stack([x[i] for _ in range(n)]) for i, n in enumerate(repeats)] + [x]) - image_cond_in = torch.cat([torch.stack([image_cond[i] for _ in range(n)]) for i, n in enumerate(repeats)] + [image_cond]) sigma_in = torch.cat([torch.stack([sigma[i] for _ in range(n)]) for i, n in enumerate(repeats)] + [sigma]) + image_cond_in = torch.cat([torch.stack([image_cond[i] for _ in range(n)]) for i, n in enumerate(repeats)] + [image_cond]) denoiser_params = CFGDenoiserParams(x_in, image_cond_in, sigma_in, state.sampling_step, state.sampling_steps) cfg_denoiser_callback(denoiser_params) @@ -160,7 +245,7 @@ class KDiffusionSampler: self.funcname = funcname self.func = getattr(k_diffusion.sampling, self.funcname) self.extra_params = sampler_extra_params.get(funcname, []) - self.model_wrap_cfg = CFGDenoiser(self.model_wrap) + self.model_wrap_cfg = CFGDenoiser(self.model_wrap) if not shared.sd_model.cond_stage_key == "edit" else CFGDenoiserEdit(self.model_wrap) self.sampler_noises = None self.stop_at = None self.eta = None @@ -260,13 +345,17 @@ class KDiffusionSampler: self.model_wrap_cfg.init_latent = x self.last_latent = x - - samples = self.launch_sampling(t_enc + 1, lambda: self.func(self.model_wrap_cfg, xi, extra_args={ + extra_args={ 'cond': conditioning, 'image_cond': image_conditioning, 'uncond': unconditional_conditioning, - 'cond_scale': p.cfg_scale - }, disable=False, callback=self.callback_state, **extra_params_kwargs)) + 'cond_scale': p.cfg_scale, + } + + if p.image_cfg_scale: + extra_args['image_cfg_scale'] = p.image_cfg_scale + + samples = self.launch_sampling(t_enc + 1, lambda: self.func(self.model_wrap_cfg, xi, extra_args=extra_args, disable=False, callback=self.callback_state, **extra_params_kwargs)) return samples diff --git a/modules/ui.py b/modules/ui.py index 5e34fb07..f2f7de8b 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -766,6 +766,7 @@ def create_ui(): elif category == "cfg": with FormGroup(): cfg_scale = gr.Slider(minimum=1.0, maximum=30.0, step=0.5, label='CFG Scale', value=7.0, elem_id="img2img_cfg_scale") + image_cfg_scale = gr.Slider(minimum=0, maximum=3.0, step=0.05, label='Image CFG Scale (for instruct-pix2pix models only)', value=1.5, elem_id="img2img_image_cfg_scale") denoising_strength = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Denoising strength', value=0.75, elem_id="img2img_denoising_strength") elif category == "seed": @@ -861,6 +862,7 @@ def create_ui(): batch_count, batch_size, cfg_scale, + image_cfg_scale, denoising_strength, seed, subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox, @@ -947,6 +949,7 @@ def create_ui(): (sampler_index, "Sampler"), (restore_faces, "Face restoration"), (cfg_scale, "CFG scale"), + (image_cfg_scale, "Image CFG scale"), (seed, "Seed"), (width, "Size-1"), (height, "Size-2"), -- cgit v1.2.3 From c4b9ed1a2791e411f95a96a6324b4986b8b85b84 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 4 Feb 2023 11:18:44 +0300 Subject: make Image CFG Scale only show if instrutpix2pix model is loaded --- modules/ui.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'modules/ui.py') diff --git a/modules/ui.py b/modules/ui.py index f2f7de8b..f5df1ffe 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -765,8 +765,9 @@ def create_ui(): elif category == "cfg": with FormGroup(): - cfg_scale = gr.Slider(minimum=1.0, maximum=30.0, step=0.5, label='CFG Scale', value=7.0, elem_id="img2img_cfg_scale") - image_cfg_scale = gr.Slider(minimum=0, maximum=3.0, step=0.05, label='Image CFG Scale (for instruct-pix2pix models only)', value=1.5, elem_id="img2img_image_cfg_scale") + with FormRow(): + cfg_scale = gr.Slider(minimum=1.0, maximum=30.0, step=0.5, label='CFG Scale', value=7.0, elem_id="img2img_cfg_scale") + image_cfg_scale = gr.Slider(minimum=0, maximum=3.0, step=0.05, label='Image CFG Scale', value=1.5, elem_id="img2img_image_cfg_scale", visible=shared.sd_model and shared.sd_model.cond_stage_key == "edit") denoising_strength = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Denoising strength', value=0.75, elem_id="img2img_denoising_strength") elif category == "seed": @@ -1594,6 +1595,12 @@ def create_ui(): outputs=[component, text_settings], ) + text_settings.change( + fn=lambda: gr.update(visible=shared.sd_model and shared.sd_model.cond_stage_key == "edit"), + inputs=[], + outputs=[image_cfg_scale], + ) + button_set_checkpoint = gr.Button('Change checkpoint', elem_id='change_checkpoint', visible=False) button_set_checkpoint.click( fn=lambda value, _: run_settings_single(value, key='sd_model_checkpoint'), -- cgit v1.2.3 From 5d483bf307c766aee97caec857d414946fad47db Mon Sep 17 00:00:00 2001 From: Gerschel Date: Mon, 6 Feb 2023 08:18:04 -0800 Subject: aspect ratio for dim's; sliders adjust by ratio MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Default choices added to settings in user interface section Choices are editable by user User selects from dropdown. When you move one slider, the other adjusts according to the ratio chosen. Vice versa for the other slider. Number fields for changes work as well. For disabling ratio, an unlock pad "🔓" is available as a default. This string can be changed to anything to serve as a disable, as long as there is no colon ":". Ratios are entered in this format, floats or ints with a colon "1:1". The string is split at the colon, parses left and right as floats to perform the math. --- javascript/ComponentControllers.js | 257 +++++++++++++++++++++++++++++++++++++ javascript/aspectRatioSliders.js | 41 ++++++ modules/shared.py | 14 ++ modules/ui.py | 6 + 4 files changed, 318 insertions(+) create mode 100644 javascript/ComponentControllers.js create mode 100644 javascript/aspectRatioSliders.js (limited to 'modules/ui.py') diff --git a/javascript/ComponentControllers.js b/javascript/ComponentControllers.js new file mode 100644 index 00000000..194589c7 --- /dev/null +++ b/javascript/ComponentControllers.js @@ -0,0 +1,257 @@ +/* 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 +
+
css makes translucent + + serves as label for component + +
container for checkboxes + + ... +
+
+ */ + } + 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")); + } + 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")); + } +} diff --git a/javascript/aspectRatioSliders.js b/javascript/aspectRatioSliders.js new file mode 100644 index 00000000..f577750a --- /dev/null +++ b/javascript/aspectRatioSliders.js @@ -0,0 +1,41 @@ +class AspectRatioSliderController { + constructor(widthSlider, heightSlider, ratioSource) { + 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")); + } + resize(dimension) { + let val = this.ratioSource.getVal(); + if (!val.includes(":")) { + return; + } + 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()); + } + else if (dimension == "height") { + this.widthSlider.setVal(Math.round(parseFloat(this.heightSlider.getVal()) * ratio).toString()); + } + } + static observeStartup(widthSliderId, heightSliderId, ratioSourceId) { + 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) { + observer.disconnect(); + new AspectRatioSliderController(widthSlider, heightSlider, ratioSource); + } + }); + 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"); +}); diff --git a/modules/shared.py b/modules/shared.py index 79fbf724..ead7be36 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -139,6 +139,19 @@ ui_reorder_categories = [ "scripts", ] +aspect_ratio_defaults = [ + "🔓" + "1:1", + "1:2", + "2:1", + "2:3", + "3:2", + "4:3", + "5:4", + "9:16", + "16:9", +] + 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 = \ @@ -456,6 +469,7 @@ options_templates.update(options_section(('ui', "User interface"), { "keyedit_precision_extra": OptionInfo(0.05, "Ctrl+up/down precision when editing ", 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": 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 f5df1ffe..6853485c 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -424,6 +424,10 @@ 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) @@ -480,6 +484,7 @@ def create_ui(): 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") 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") @@ -758,6 +763,7 @@ def create_ui(): 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") 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") -- cgit v1.2.3 From 374fe636b80169c78b4b5f92013681d75fa2fad6 Mon Sep 17 00:00:00 2001 From: Gerschel Date: Wed, 8 Feb 2023 18:57:32 -0800 Subject: Squashed commit of the following: commit b030b67ad005bfe29bcda692238a00042dcae816 Author: Gerschel Date: Wed Feb 8 16:38:56 2023 -0800 styling adjustements commit 80a2acb0230dd77489b0eb466f2efe827a053f6d Author: Gerschel Date: Wed Feb 8 10:49:47 2023 -0800 badge indicator toggles visibility by selection commit 898922e025a6422ac947fb45c1fa4f1109882f0a Merge: 745382a0 31bbfa72 Author: Gerschel <9631031+Gerschel@users.noreply.github.com> Date: Wed Feb 8 08:35:26 2023 -0800 Merge pull request #1 from w-e-w/Rounding-Method Rounding Method commit 31bbfa729a15ef35fa1f905345d3ba2b17b26ab9 Author: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Wed Feb 8 19:41:45 2023 +0900 use switch commit 85dbe511c33521d3ac62224bf0e0f3a48194ce63 Author: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Wed Feb 8 16:47:52 2023 +0900 Rounding Method commit 745382a0f4b8d16241545a3460d5206915959255 Author: Gerschel Date: Tue Feb 7 21:19:20 2023 -0800 default set to round commit 728579c618af30ec98a5af0991bd3f28bdaca399 Author: Gerschel Date: Tue Feb 7 21:17:03 2023 -0800 cleaned some commented code out; added indicator commit 5b288c24a1edd8a5c2f35214b9634316d05b8dae Author: Gerschel Date: Tue Feb 7 18:19:00 2023 -0800 needs cleaning; attempt at rounding commit d9f18ae92b929576b0b8c5f1ef8b3b38e441e381 Author: Gerschel Date: Tue Feb 7 15:46:25 2023 -0800 add rounding option in setting for aspect ratio commit af22106802c9e42205649e4c71c23fcf5b8c62f6 Author: Gerschel Date: Tue Feb 7 13:18:45 2023 -0800 added some ratios, sorted ratios by commonality commit 11e2fba73cffe8cdbf4cd0860641b94428ca0e74 Author: Gerschel Date: Tue Feb 7 10:46:53 2023 -0800 snaps to mulitples of 8 and along ratio commit fa00387e07460b10ee82671a1bfa8687e00ee60b Author: Gerschel Date: Mon Feb 6 14:54:59 2023 -0800 updated slidercomponentcontroller commit 8059bc111c3e2d1edb3314e05ab21b65120fa1dd Author: Gerschel Date: Mon Feb 6 14:29:11 2023 -0800 added step size adjustment on number field commit 641157b9f27a874a24ee7b0a854a092e9eac3eec Author: Gerschel Date: Mon Feb 6 14:12:03 2023 -0800 added return step size to default when ratio is disabled commit 5fb75ad28f2476f36100ec93922a8199adbd2a68 Author: Gerschel Date: Mon Feb 6 14:09:34 2023 -0800 added step size adjustment commit e33532883bc4709cd41c3775cbb646d1d5ab0584 Author: Gerschel Date: Mon Feb 6 11:56:15 2023 -0800 adjusted dropdown size, padding, text-align commit 81937329cee77f466c5a5b23c268d0c810128f84 Author: Gerschel Date: Mon Feb 6 11:39:57 2023 -0800 added positioning and styling commit 86eb4583782d92880a9a113a54ffbac9d92f3753 Author: Gerschel Date: Mon Feb 6 08:54:45 2023 -0800 fix typo in defaults; added preventDefault in event --- javascript/ComponentControllers.js | 2 + javascript/aspectRatioSliders.js | 164 ++++++++++++++++++++++++++++++++++--- modules/shared.py | 17 ++-- modules/ui.py | 10 ++- style.css | 46 +++++++++++ 5 files changed, 218 insertions(+), 21 deletions(-) (limited to 'modules/ui.py') 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 ", 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; } -- cgit v1.2.3 From 9c7e6d5bbaa55205d0678369588c019108fb30a7 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 18 Feb 2023 11:31:02 -0500 Subject: store and print real torch version --- modules/ui.py | 7 ++++++- webui.py | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'modules/ui.py') diff --git a/modules/ui.py b/modules/ui.py index f5df1ffe..d9df3781 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1779,10 +1779,15 @@ def versions_html(): else: xformers_version = "N/A" + try: + torch_version = torch.__long_version__ + except: + torch_version = torch.__version__ + return f""" python: {python_version}  •  -torch: {torch.__version__} +torch: {torch_version}  •  xformers: {xformers_version}  •  diff --git a/webui.py b/webui.py index 5b5c2139..2363ea4e 100644 --- a/webui.py +++ b/webui.py @@ -20,6 +20,7 @@ import torch # Truncate version number of nightly/local build of PyTorch to not cause exceptions with CodeFormer or Safetensors if ".dev" in torch.__version__ or "+git" in torch.__version__: + torch.__long_version__ = torch.__version__ torch.__version__ = re.search(r'[\d.]+[\d]', torch.__version__).group(0) from modules import shared, devices, sd_samplers, upscaler, extensions, localization, ui_tempdir, ui_extra_networks -- cgit v1.2.3 From b5f69ad6afcdb8ba718da636b4a3f8aad5bd7cbf Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 19 Feb 2023 08:38:38 +0300 Subject: simply long version display for torch in UI --- modules/ui.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'modules/ui.py') diff --git a/modules/ui.py b/modules/ui.py index d9df3781..54efb6a4 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1779,15 +1779,10 @@ def versions_html(): else: xformers_version = "N/A" - try: - torch_version = torch.__long_version__ - except: - torch_version = torch.__version__ - return f""" python: {python_version}  •  -torch: {torch_version} +torch: {getattr(torch, '__long_version__',torch.__version__)}  •  xformers: {xformers_version}  •  -- cgit v1.2.3 From a742facd95189eb078087bce9cafbfad0723cff4 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 19 Feb 2023 09:30:49 +0300 Subject: make PNG info tab work properly with parameter overrides --- modules/generation_parameters_copypaste.py | 7 ++++--- modules/ui.py | 10 +++++----- 2 files changed, 9 insertions(+), 8 deletions(-) (limited to 'modules/ui.py') diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index fc9e17aa..89dc23bf 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -74,8 +74,8 @@ def image_from_url_text(filedata): return image -def add_paste_fields(tabname, init_img, fields): - paste_fields[tabname] = {"init_img": init_img, "fields": fields} +def add_paste_fields(tabname, init_img, fields, override_settings_component=None): + paste_fields[tabname] = {"init_img": init_img, "fields": fields, "override_settings_component": override_settings_component} # backwards compatibility for existing extensions import modules.ui @@ -110,6 +110,7 @@ def connect_paste_params_buttons(): for binding in registered_param_bindings: destination_image_component = paste_fields[binding.tabname]["init_img"] fields = paste_fields[binding.tabname]["fields"] + override_settings_component = binding.override_settings_component or paste_fields[binding.tabname]["override_settings_component"] destination_width_component = next(iter([field for field, name in fields if name == "Size-1"] if fields else []), None) destination_height_component = next(iter([field for field, name in fields if name == "Size-2"] if fields else []), None) @@ -130,7 +131,7 @@ def connect_paste_params_buttons(): ) if binding.source_text_component is not None and fields is not None: - connect_paste(binding.paste_button, fields, binding.source_text_component, binding.override_settings_component, binding.tabname) + connect_paste(binding.paste_button, fields, binding.source_text_component, override_settings_component, binding.tabname) if binding.source_tabname is not None and fields is not None: paste_field_names = ['Prompt', 'Negative prompt', 'Steps', 'Face restoration'] + (["Seed"] if shared.opts.send_seed else []) diff --git a/modules/ui.py b/modules/ui.py index 54efb6a4..2fdbda42 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -631,9 +631,9 @@ def create_ui(): (hr_resize_y, "Hires resize-2"), *modules.scripts.scripts_txt2img.infotext_fields ] - parameters_copypaste.add_paste_fields("txt2img", None, txt2img_paste_fields) + parameters_copypaste.add_paste_fields("txt2img", None, txt2img_paste_fields, override_settings) parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding( - paste_button=txt2img_paste, tabname="txt2img", source_text_component=txt2img_prompt, source_image_component=None, override_settings_component=override_settings, + paste_button=txt2img_paste, tabname="txt2img", source_text_component=txt2img_prompt, source_image_component=None, )) txt2img_preview_params = [ @@ -963,10 +963,10 @@ def create_ui(): (mask_blur, "Mask blur"), *modules.scripts.scripts_img2img.infotext_fields ] - parameters_copypaste.add_paste_fields("img2img", init_img, img2img_paste_fields) - parameters_copypaste.add_paste_fields("inpaint", init_img_with_mask, img2img_paste_fields) + parameters_copypaste.add_paste_fields("img2img", init_img, img2img_paste_fields, override_settings) + parameters_copypaste.add_paste_fields("inpaint", init_img_with_mask, img2img_paste_fields, override_settings) parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding( - paste_button=img2img_paste, tabname="img2img", source_text_component=img2img_prompt, source_image_component=None, override_settings_component=override_settings, + paste_button=img2img_paste, tabname="img2img", source_text_component=img2img_prompt, source_image_component=None, )) modules.scripts.scripts_current = None -- cgit v1.2.3 From fd4ac5187a1ae42be3f131770ea21e2158f75dcd Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 19 Feb 2023 10:55:39 +0300 Subject: Revert "Aspect ratio sliders" --- javascript/ComponentControllers.js | 259 ------------------------------------- javascript/aspectRatioSliders.js | 181 -------------------------- modules/shared.py | 21 --- modules/ui.py | 12 +- style.css | 46 ------- 5 files changed, 2 insertions(+), 517 deletions(-) delete mode 100644 javascript/ComponentControllers.js delete mode 100644 javascript/aspectRatioSliders.js (limited to 'modules/ui.py') diff --git a/javascript/ComponentControllers.js b/javascript/ComponentControllers.js deleted file mode 100644 index 2888679b..00000000 --- a/javascript/ComponentControllers.js +++ /dev/null @@ -1,259 +0,0 @@ -/* 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 -
-
css makes translucent - - serves as label for component - -
container for checkboxes - - ... -
-
- */ - } - 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")); - } -} diff --git a/javascript/aspectRatioSliders.js b/javascript/aspectRatioSliders.js deleted file mode 100644 index 3def5158..00000000 --- a/javascript/aspectRatioSliders.js +++ /dev/null @@ -1,181 +0,0 @@ -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"); -}); diff --git a/modules/shared.py b/modules/shared.py index 2983ee44..e324a48a 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -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 ", 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 2fc1fee5..2fdbda42 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -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,9 +479,7 @@ 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") + 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") @@ -763,9 +757,7 @@ 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") + 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 55baefb7..05572f66 100644 --- a/style.css +++ b/style.css @@ -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; } -- cgit v1.2.3