diff --git a/scripts/sd-webui-ar.py b/scripts/sd-webui-ar.py index 7edf0be..24baa97 100644 --- a/scripts/sd-webui-ar.py +++ b/scripts/sd-webui-ar.py @@ -8,6 +8,11 @@ from modules.ui_components import ToolButton aspect_ratios_dir = scripts.basedir() +calculator_symbol = '\U0001F5A9' +switch_values_symbol = '\U000021C5' +get_dimensions_symbol = '\U00002B07' +get_image_dimensions_symbol = '\U0001F5BC' + class ResButton(ToolButton): def __init__(self, res=(512, 512), **kwargs): @@ -122,6 +127,47 @@ def write_resolutions_file(filename): f.writelines(resolutions) +## Functions for calculator panel +def gcd(a, b): + if b == 0: + return a + return gcd(b, a % b) + + +def get_reduced_ratio(n, d): + if n == d: + return '1:1' + + if n < d: + temp = n + n = d + d = temp + + div = gcd(int(n), int(d)) + + if 'temp' not in locals(): + w = int(n) // div + h = int(d) // div + else: + w = int(d) // div + h = int(n) // div + + if w == 8 and h == 5: + w = 16 + h = 10 + + return f'{w}:{h}' + + +def solve_aspect_ratio(w, h, n, d): + if w != 0: + return round(w / (n / d)) + elif h != 0: + return round(h * (n / d)) + else: + return None + + class AspectRatioScript(scripts.Script): def read_aspect_ratios(self): ar_file = Path(aspect_ratios_dir, "aspect_ratios.txt") @@ -161,6 +207,11 @@ class AspectRatioScript(scripts.Script): def ui(self, is_img2img): self.read_aspect_ratios() with gr.Row(elem_id=f'{"img" if is_img2img else "txt"}2img_row_aspect_ratio'): + # Toggle calculator display button + arc_show_calculator = gr.Button(value=calculator_symbol, visible=True, variant="secondary", elem_id="arc_show_calculator_button") + arc_hide_calculator = gr.Button(value=calculator_symbol, visible=False, variant="primary", elem_id="arc_hide_calculator_button") + + # Aspect Ratio buttons btns = [ ARButton(ar=ar, value=label) for ar, label in zip( @@ -201,6 +252,123 @@ class AspectRatioScript(scripts.Script): outputs=resolution, ) + # dummy components needed for js function + dummy_text1 = gr.Text(visible=False) + dummy_text2 = gr.Text(visible=False) + dummy_text3 = gr.Text(visible=False) + dummy_text4 = gr.Text(visible=False) + + # Aspect Ratio Calculator + with gr.Column(visible=False, variant="panel", elem_id="arc_panel") as arc_panel: + arc_title_heading = gr.Markdown(value="""#### Aspect Ratio Calculator""") + with gr.Row(): + with gr.Column(min_width=150): + arc_width1 = gr.Number(label="Width 1") + arc_height1 = gr.Number(label="Height 1") + + with gr.Column(min_width=150): + arc_desired_width = gr.Number(label="Width 2") + arc_desired_height = gr.Number(label="Height 2") + + with gr.Column(min_width=150): + arc_ar_display = gr.Markdown(value="""Aspect Ratio:""") + with gr.Row(elem_id=f'{"img" if is_img2img else "txt"}2img_arc_tool_buttons'): + # Switch resolution values button + arc_swap = ToolButton(value=switch_values_symbol) + arc_swap.click(lambda w, h, w2, h2: (h, w, h2, w2), inputs=[arc_width1, arc_height1, arc_desired_width, arc_desired_height], outputs=[arc_width1, arc_height1, arc_desired_width, arc_desired_height]) + + with contextlib.suppress(AttributeError): + # For img2img tab + if is_img2img: + # Get slider dimensions button + resolution = [self.i2i_w, self.i2i_h] + arc_get_img2img_dim = ToolButton(value=get_dimensions_symbol) + arc_get_img2img_dim.click( + lambda w, h: (w, h), + inputs=resolution, + outputs=[arc_width1, arc_height1], + ) + + # Javascript function to select image element from current img2img tab + current_tab_image = """ + function current_tab_image(...args) { + const tab_index = get_img2img_tab_index(); + // Get current tab's image (on Batch tab, use image from img2img tab) + if (tab_index == 5) { + image = args[0]; + } else { + image = args[tab_index]; + } + // On Inpaint tab, select just the image and drop the mask + if (tab_index == 2 && image !== null) { + image = image["image"]; + } + return [image, null, null, null, null]; + } + + """ + # Get image dimensions + def get_dims(img: list, dummy_text1, dummy_text2, dummy_text3, dummy_text4): + if img: + width = img.size[0] + height = img.size[1] + return width, height + else: + return 0, 0 + + # Get image dimensions button + arc_get_image_dim = ToolButton(value=get_image_dimensions_symbol) + arc_get_image_dim.click(fn=get_dims,inputs=self.image,outputs=[arc_width1, arc_height1],_js=current_tab_image) + + else: + # For txt2img tab + # Get slider dimensions button + resolution = [self.t2i_w, self.t2i_h] + arc_get_txt2img_dim = ToolButton(value=get_dimensions_symbol) + arc_get_txt2img_dim.click( + lambda w, h: (w, h), + inputs=resolution, + outputs=[arc_width1, arc_height1], + ) + + # Update aspect ratio display on change + arc_width1.change(lambda w, h: ("""Aspect Ratio: **{}**""".format(get_reduced_ratio(w,h))), inputs=[arc_width1, arc_height1], outputs=[arc_ar_display]) + arc_height1.change(lambda w, h: ("""Aspect Ratio: **{}**""".format(get_reduced_ratio(w,h))), inputs=[arc_width1, arc_height1], outputs=[arc_ar_display]) + + with gr.Row(): + # Calculate and Apply buttons + arc_calc_height = gr.Button(value='Calculate Height').style(full_width=False) + arc_calc_height.click(lambda w2, w1, h1: (solve_aspect_ratio(w2,0,w1,h1)), inputs=[arc_desired_width,arc_width1,arc_height1], outputs=[arc_desired_height]) + arc_calc_width = gr.Button(value='Calculate Width').style(full_width=False) + arc_calc_width.click(lambda h2, w1, h1: (solve_aspect_ratio(0,h2,w1,h1)), inputs=[arc_desired_height,arc_width1,arc_height1], outputs=[arc_desired_width]) + arc_apply_params = gr.Button(value='Apply') + with contextlib.suppress(AttributeError): + if is_img2img: + resolution = [self.i2i_w, self.i2i_h] + else: + resolution = [self.t2i_w, self.t2i_h] + + arc_apply_params.click( + lambda w2, h2: (w2, h2), + inputs=[arc_desired_width, arc_desired_height], + outputs=resolution, + ) + + # Show calculator pane (and reset number input values) + arc_show_calculator.click( + lambda: [gr.update(visible=True), gr.update(visible=False), gr.update(visible=True), + gr.update(value=512), gr.update(value=512), gr.update(value=0), gr.update(value=0), + gr.update(value="""Aspect Ratio: **1:1**""")], + None, + [arc_panel, arc_show_calculator, arc_hide_calculator, + arc_width1, arc_height1, arc_desired_width, arc_desired_height, + arc_ar_display] + ) + # Hide calculator pane + arc_hide_calculator.click( + lambda: [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)], + None, [arc_panel, arc_show_calculator, arc_hide_calculator]) + # https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/7456#issuecomment-1414465888 def after_component(self, component, **kwargs): if kwargs.get("elem_id") == "txt2img_width": @@ -212,3 +380,15 @@ class AspectRatioScript(scripts.Script): self.i2i_w = component if kwargs.get("elem_id") == "img2img_height": self.i2i_h = component + + if kwargs.get("elem_id") == "img2img_image": + self.image = [] + self.image.append(component) + if kwargs.get("elem_id") == "img2img_sketch": + self.image.append(component) + if kwargs.get("elem_id") == "img2maskimg": + self.image.append(component) + if kwargs.get("elem_id") == "inpaint_sketch": + self.image.append(component) + if kwargs.get("elem_id") == "img_inpaint_base": + self.image.append(component) diff --git a/style.css b/style.css index cf06f52..620598e 100644 --- a/style.css +++ b/style.css @@ -11,3 +11,13 @@ max-width: fit-content !important; margin: 0 0 0.25em 0 !important; } + +/* Fix size and display of show/hide calculator buttons */ +button#arc_show_calculator_button, +button#arc_hide_calculator_button { + min-width: 1.5em !important; + height: 1.25em; + flex-grow: 0; + font-size: 2rem; + padding: 0 0 .1em; +} \ No newline at end of file