diff --git a/frontend/css/styles.css b/frontend/css/styles.css index 46ee4aa..be27da8 100644 --- a/frontend/css/styles.css +++ b/frontend/css/styles.css @@ -1,5 +1,9 @@ [data-testid="image"] {min-height: 512px !important} -* #body>.col:nth-child(2){width:250%;max-width:89vw} +* #body>.col:nth-child(2){ + width:250%; + max-width:89vw +} + #prompt_input, #img2img_prompt_input { padding: 0px; @@ -22,3 +26,39 @@ input[type=number]:disabled { -moz-appearance: textfield; } width: 512px; height: auto; } + +#highlight{ + font-size: 1.2rem + } +#highlight .uppercase{ + text-transform: initial; + +} +#highlight .textfield .textspan:nth-child(1){ + font-size: 1.2rem +} + +/* Mobile fixes and styles */ +@media only screen and (max-width: 768px) { + /* rearrange the columns on mobile */ + + #body>.col:nth-child(2){ + width:100%; + max-width:100vw; + order: 1; + } + #body>.col:nth-child(1){ + order: 2; + } + #body>.col:nth-child(3){ + order: 3; + } + + #txt2img_actions_row, + #txt2img_output_row{ + flex-direction: row; + flex-wrap: wrap-reverse; + } + /* fix buttons layouts */ + +} \ No newline at end of file diff --git a/frontend/css_and_js.py b/frontend/css_and_js.py index 03f6a63..266cf43 100644 --- a/frontend/css_and_js.py +++ b/frontend/css_and_js.py @@ -12,8 +12,9 @@ def readTextFile(*args): def css(opt): styling = readTextFile("css", "styles.css") + # TODO: @altryne restore this before merge if not opt.no_progressbar_hiding: - styling += readTextFile("css", "no_progress_bar.css") + styling += readTextFile("css", "no_progress_bar.css") return styling @@ -23,6 +24,76 @@ def js(opt): return data + +def js_painterro_launch(to_id): + return w(f"Painterro.init('{to_id}')") + +def js_move_image(from_id, to_id): + return w(f"moveImageFromGallery('{from_id}', '{to_id}')") + +def js_copy_to_clipboard(from_id): + return w(f"copyImageFromGalleryToClipboard('{from_id}')") + +def js_img2img_submit(prompt_row_id): + return w(f"clickFirstVisibleButton('{prompt_row_id}')") + +# TODO : @altryne fix this to the new JS format +js_copy_txt2img_output = "(x) => {navigator.clipboard.writeText(document.querySelector('gradio-app').shadowRoot.querySelector('#highlight .textfield').textContent.replace(/\s+/g,' ').replace(/: /g,':'))}" + + + +js_parse_prompt =""" +(txt2img_prompt, txt2img_width, txt2img_height, txt2img_steps, txt2img_seed, txt2img_batch_count, txt2img_cfg) => { + +const prompt_input = document.querySelector('gradio-app').shadowRoot.querySelector('#prompt_input [data-testid="textbox"]'); +const multiline = document.querySelector('gradio-app').shadowRoot.querySelector('#submit_on_enter label:nth-child(2)') +if (prompt_input.scrollWidth > prompt_input.clientWidth + 10 ) { + multiline.click(); +} + + +let height_match = /(?:-h|-H|--height|height)[ :]?(?\d+) /.exec(txt2img_prompt); +if (height_match) { + txt2img_height = Math.round(height_match.groups.height / 64) * 64; + txt2img_prompt = txt2img_prompt.replace(height_match[0], ''); +} +let width_match = /(?:-w|-W|--width|width)[ :]?(?\d+) /.exec(txt2img_prompt); +if (width_match) { + txt2img_width = Math.round(width_match.groups.width / 64) * 64; + txt2img_prompt = txt2img_prompt.replace(width_match[0], ''); +} +let steps_match = /(?:-s|--steps|steps)[ :]?(?\d+) /.exec(txt2img_prompt); +if (steps_match) { + txt2img_steps = steps_match.groups.steps.trim(); + txt2img_prompt = txt2img_prompt.replace(steps_match[0], ''); +} +let seed_match = /(?:-S|--seed|seed)[ :]?(?\d+) /.exec(txt2img_prompt); +if (seed_match) { + txt2img_seed = seed_match.groups.seed; + txt2img_prompt = txt2img_prompt.replace(seed_match[0], ''); +} +let batch_count_match = /(?:-n|-N|--number|number)[ :]?(?\d+) /.exec(txt2img_prompt); +if (batch_count_match) { + txt2img_batch_count = batch_count_match.groups.batch_count; + txt2img_prompt = txt2img_prompt.replace(batch_count_match[0], ''); +} +let cfg_scale_match = /(?:-c|-C|--cfg-scale|cfg_scale|cfg)[ :]?(?\d\.?\d+?) /.exec(txt2img_prompt); +if (cfg_scale_match) { + txt2img_cfg = parseFloat(cfg_scale_match.groups.cfgscale).toFixed(1); + txt2img_prompt = txt2img_prompt.replace(cfg_scale_match[0], ''); +} +let sampler_match = /(?:-A|--sampler|sampler)[ :]?(?\w+) /.exec(txt2img_prompt); +if (sampler_match) { + + txt2img_prompt = txt2img_prompt.replace(sampler_match[0], ''); +} + +return [txt2img_prompt, parseInt(txt2img_width), parseInt(txt2img_height), parseInt(txt2img_steps), txt2img_seed, parseInt(txt2img_batch_count), parseFloat(txt2img_cfg)]; +} +""" + + +# @altryne this came up as conflict, still needed or no? # Wrap the typical SD method call into async closure for ease of use # Supplies the js function with a params object # That includes all the passed arguments and input from Gradio: x diff --git a/frontend/frontend.py b/frontend/frontend.py index 49af26e..2d45a71 100644 --- a/frontend/frontend.py +++ b/frontend/frontend.py @@ -1,5 +1,5 @@ import gradio as gr -from frontend.css_and_js import css, js, call_JS +from frontend.css_and_js import css, js, call_JS, js_parse_prompt, js_copy_txt2img_output import frontend.ui_functions as uifn def draw_gradio_ui(opt, img2img=lambda x: x, txt2img=lambda x: x,imgproc=lambda x: x, txt2img_defaults={}, RealESRGAN=True, GFPGAN=True,LDSR=True, @@ -9,7 +9,7 @@ def draw_gradio_ui(opt, img2img=lambda x: x, txt2img=lambda x: x,imgproc=lambda with gr.Blocks(css=css(opt), analytics_enabled=False, title="Stable Diffusion WebUI") as demo: with gr.Tabs(elem_id='tabss') as tabs: - with gr.TabItem("Stable Diffusion Text-to-Image Unified", id='txt2img_tab'): + with gr.TabItem("Text-to-Image", id='txt2img_tab'): with gr.Row(elem_id="prompt_row"): txt2img_prompt = gr.Textbox(label="Prompt", elem_id='prompt_input', @@ -22,54 +22,46 @@ def draw_gradio_ui(opt, img2img=lambda x: x, txt2img=lambda x: x,imgproc=lambda with gr.Row(elem_id='body').style(equal_height=False): with gr.Column(): - txt2img_width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", + txt2img_width = gr.Slider(minimum=64, maximum=1024, step=64, label="Width", value=txt2img_defaults["width"]) - txt2img_height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", + txt2img_height = gr.Slider(minimum=64, maximum=1024, step=64, label="Height", value=txt2img_defaults["height"]) txt2img_cfg = gr.Slider(minimum=-40.0, maximum=30.0, step=0.5, label='Classifier Free Guidance Scale (how strongly the image should follow the prompt)', value=txt2img_defaults['cfg_scale'], elem_id='cfg_slider') txt2img_seed = gr.Textbox(label="Seed (blank to randomize)", lines=1, max_lines=1, value=txt2img_defaults["seed"]) - txt2img_batch_count = gr.Slider(minimum=1, maximum=250, step=1, - label='Batch count (how many batches of images to generate)', + txt2img_batch_count = gr.Slider(minimum=1, maximum=10, step=1, + label='Number of images to generate', value=txt2img_defaults['n_iter']) - txt2img_batch_size = gr.Slider(minimum=1, maximum=8, step=1, - label='Batch size (how many images are in a batch; memory-hungry)', - value=txt2img_defaults['batch_size']) + txt2img_dimensions_info_text_box = gr.Textbox(label="Aspect ratio (4:3 = 1.333 | 16:9 = 1.777 | 21:9 = 2.333)") with gr.Column(): - output_txt2img_gallery = gr.Gallery(label="Images", elem_id="txt2img_gallery_output").style(grid=[4, 4]) + with gr.Box(): + output_txt2img_gallery = gr.Gallery(label="Images", elem_id="txt2img_gallery_output").style(grid=[4, 4]) + gr.Markdown("Select an image from the gallery, then click one of the buttons below to perform an action.") + with gr.Row(elem_id='txt2img_actions_row'): + gr.Button("Copy to clipboard").click(fn=None, + inputs=output_txt2img_gallery, + outputs=[], + #_js=js_copy_to_clipboard( 'txt2img_gallery_output') + ) + output_txt2img_copy_to_input_btn = gr.Button("Push to img2img") + if RealESRGAN is not None: + output_txt2img_to_upscale_esrgan = gr.Button("Upscale w/ ESRGAN") - with gr.Tabs(): - with gr.TabItem("Generated image actions", id="text2img_actions_tab"): - gr.Markdown( - 'Select an image from the gallery, then click one of the buttons below to perform an action.') - with gr.Row(): - output_txt2img_copy_clipboard = gr.Button("Copy to clipboard")\ - .click(fn=None, - inputs=output_txt2img_gallery, - outputs=[], - _js=call_JS("copyImageFromGalleryToClipboard", - fromId="txt2img_gallery_output") - ) - output_txt2img_copy_to_input_btn = gr.Button("Push to img2img") - if RealESRGAN is not None: - output_txt2img_to_upscale_esrgan = gr.Button("Upscale w/ ESRGAN",visible=False) - - with gr.TabItem("Output Info", id="text2img_output_info_tab"): - output_txt2img_params = gr.Textbox(label="Generation parameters", interactive=False) - with gr.Row(): - output_txt2img_copy_params = gr.Button("Copy full parameters").click( - inputs=output_txt2img_params, outputs=[], - fn=None, show_progress=False, - _js=call_JS("gradioInputToClipboard") - ) - output_txt2img_seed = gr.Number(label='Seed', interactive=False, visible=False) - output_txt2img_copy_seed = gr.Button("Copy only seed").click( - inputs=output_txt2img_seed, outputs=[], - _js=call_JS("gradioInputToClipboard"), fn=None, show_progress=False) - output_txt2img_stats = gr.HTML(label='Stats') + output_txt2img_params = gr.Highlightedtext(label="Generation parameters", interactive=False, elem_id='highlight') + with gr.Group(): + with gr.Row(elem_id='txt2img_output_row'): + output_txt2img_copy_params = gr.Button("Copy full parameters").click( + inputs=[output_txt2img_params], outputs=[], + _js=js_copy_txt2img_output, + fn=None, show_progress=False) + output_txt2img_seed = gr.Number(label='Seed', interactive=False, visible=False) + output_txt2img_copy_seed = gr.Button("Copy only seed").click( + inputs=[output_txt2img_seed], outputs=[], + _js='(x) => navigator.clipboard.writeText(x)', fn=None, show_progress=False) + output_txt2img_stats = gr.HTML(label='Stats') with gr.Column(): txt2img_steps = gr.Slider(minimum=1, maximum=250, step=1, label="Sampling Steps", @@ -83,13 +75,16 @@ def draw_gradio_ui(opt, img2img=lambda x: x, txt2img=lambda x: x,imgproc=lambda txt2img_submit_on_enter = gr.Radio(['Yes', 'No'], label="Submit on enter? (no means multiline)", value=txt2img_defaults['submit_on_enter'], - interactive=True) + interactive=True, elem_id='submit_on_enter') txt2img_submit_on_enter.change( lambda x: gr.update(max_lines=1 if x == 'Yes' else 25), txt2img_submit_on_enter, txt2img_prompt) with gr.TabItem('Advanced'): txt2img_toggles = gr.CheckboxGroup(label='', choices=txt2img_toggles, value=txt2img_toggle_defaults, type="index") + txt2img_batch_size = gr.Slider(minimum=1, maximum=8, step=1, + label='Batch size (how many images are in a batch; memory-hungry)', + value=txt2img_defaults['batch_size']) txt2img_realesrgan_model_name = gr.Dropdown(label='RealESRGAN model', choices=['RealESRGAN_x4plus', 'RealESRGAN_x4plus_anime_6B'], @@ -118,10 +113,19 @@ def draw_gradio_ui(opt, img2img=lambda x: x, txt2img=lambda x: x,imgproc=lambda txt2img_height, txt2img_width, txt2img_embeddings, txt2img_variant_amount, txt2img_variant_seed], [output_txt2img_gallery, output_txt2img_seed, output_txt2img_params, output_txt2img_stats] ) - txt2img_width.change(fn=uifn.update_dimensions_info, inputs=[txt2img_width, txt2img_height], outputs=txt2img_dimensions_info_text_box) - txt2img_height.change(fn=uifn.update_dimensions_info, inputs=[txt2img_width, txt2img_height], outputs=txt2img_dimensions_info_text_box) + # txt2img_width.change(fn=uifn.update_dimensions_info, inputs=[txt2img_width, txt2img_height], outputs=txt2img_dimensions_info_text_box) + # txt2img_height.change(fn=uifn.update_dimensions_info, inputs=[txt2img_width, txt2img_height], outputs=txt2img_dimensions_info_text_box) - with gr.TabItem("Stable Diffusion Image-to-Image Unified", id="img2img_tab"): + live_prompt_params = [txt2img_prompt, txt2img_width, txt2img_height, txt2img_steps, txt2img_seed, txt2img_batch_count, txt2img_cfg] + txt2img_prompt.change( + fn=None, + inputs=live_prompt_params, + outputs=live_prompt_params, + _js=js_parse_prompt + ) + + + with gr.TabItem("Image-to-Image Unified", id="img2img_tab"): with gr.Row(elem_id="prompt_row"): img2img_prompt = gr.Textbox(label="Prompt", elem_id='img2img_prompt_input', @@ -130,6 +134,7 @@ def draw_gradio_ui(opt, img2img=lambda x: x, txt2img=lambda x: x,imgproc=lambda max_lines=1 if txt2img_defaults['submit_on_enter'] == 'Yes' else 25, value=img2img_defaults['prompt'], show_label=False).style() + img2img_btn_mask = gr.Button("Generate", variant="primary", visible=False, elem_id="img2img_mask_btn") img2img_btn_editor = gr.Button("Generate", variant="primary", elem_id="img2img_edit_btn") @@ -144,14 +149,14 @@ def draw_gradio_ui(opt, img2img=lambda x: x, txt2img=lambda x: x,imgproc=lambda elem_id="img2img_mask") with gr.Tabs(): - with gr.TabItem("Editor Options"): + with gr.TabItem("Editor Options"): with gr.Column(): img2img_image_editor_mode = gr.Radio(choices=["Mask", "Crop", "Uncrop"], label="Image Editor Mode", value="Crop", elem_id='edit_mode_select') img2img_mask = gr.Radio(choices=["Keep masked area", "Regenerate only masked area"], label="Mask Mode", type="index", value=img2img_mask_modes[img2img_defaults['mask_mode']], visible=False) - + img2img_mask_blur_strength = gr.Slider(minimum=1, maximum=10, step=1, label="How much blurry should the mask be? (to avoid hard edges)", value=3, visible=False) @@ -160,7 +165,7 @@ def draw_gradio_ui(opt, img2img=lambda x: x, txt2img=lambda x: x,imgproc=lambda choices=["Just resize"], type="index", value=img2img_resize_modes[img2img_defaults['resize_mode']]) - + img2img_painterro_btn = gr.Button("Advanced Editor") with gr.TabItem("Hints"): img2img_help = gr.Markdown(visible=False, value=uifn.help_text) @@ -183,7 +188,7 @@ def draw_gradio_ui(opt, img2img=lambda x: x, txt2img=lambda x: x,imgproc=lambda with gr.Row(): output_img2img_copy_params = gr.Button("Copy full parameters").click( inputs=output_img2img_params, outputs=[], - _js=call_JS("gradioInputToClipboard"), fn=None, show_progress=False) + _js='(x) => {navigator.clipboard.writeText(x.replace(": ",":"))}', fn=None, show_progress=False) output_img2img_seed = gr.Number(label='Seed', interactive=False, visible=False) output_img2img_copy_seed = gr.Button("Copy only seed").click( inputs=output_img2img_seed, outputs=[], @@ -197,7 +202,7 @@ def draw_gradio_ui(opt, img2img=lambda x: x, txt2img=lambda x: x,imgproc=lambda value=img2img_defaults["width"]) img2img_height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=img2img_defaults["height"]) - + img2img_cfg = gr.Slider(minimum=-40.0, maximum=30.0, step=0.5, label='Classifier Free Guidance Scale (how strongly the image should follow the prompt)', value=img2img_defaults['cfg_scale'], elem_id='cfg_slider') @@ -296,6 +301,8 @@ def draw_gradio_ui(opt, img2img=lambda x: x, txt2img=lambda x: x,imgproc=lambda img2img_denoising, img2img_seed, img2img_height, img2img_width, img2img_resize, img2img_embeddings], [output_img2img_gallery, output_img2img_seed, output_img2img_params, output_img2img_stats]) + + img2img_btn_editor.click(*img2img_submit_params()) # GENERATE ON ENTER diff --git a/webui.py b/webui.py index 4ee53ff..0432b45 100644 --- a/webui.py +++ b/webui.py @@ -976,10 +976,22 @@ skip_grid, sort_samples, sampler_name, ddim_eta, n_iter, batch_size, i, denoisin mem_max_used, mem_total = mem_mon.read_and_stop() time_diff = time.time()-start_time - - info = f""" -{prompt} -Steps: {steps}, Sampler: {sampler_name}, CFG scale: {cfg_scale}, Seed: {seed}{', Denoising strength: '+str(denoising_strength) if init_img is not None else ''}{', GFPGAN' if use_GFPGAN and GFPGAN is not None else ''}{', '+realesrgan_model_name if use_RealESRGAN and RealESRGAN is not None else ''}{', Prompt Matrix Mode.' if prompt_matrix else ''}""".strip() + args_and_names = { + "seed": seed, + "width": width, + "height": height, + "steps": steps, + "cfg_scale": cfg_scale, + "sampler": sampler_name, + } + + full_string = f"{prompt}\n"+ " ".join([f"{k}:" for k,v in args_and_names.items()]) + info = { + 'text': full_string, + 'entities': [{'entity':str(v), 'start': full_string.find(f"{k}:"),'end': full_string.find(f"{k}:") + len(f"{k} ")} for k,v in args_and_names.items()] + } +# info = f""" +# {prompt} --seed {seed} --W {width} --H {height} -s {steps} -C {cfg_scale} --sampler {sampler_name} {', Denoising strength: '+str(denoising_strength) if init_img is not None else ''}{', GFPGAN' if use_GFPGAN and GFPGAN is not None else ''}{', '+realesrgan_model_name if use_RealESRGAN and RealESRGAN is not None else ''}{', Prompt Matrix Mode.' if prompt_matrix else ''}""".strip() stats = f''' Took { round(time_diff, 2) }s total ({ round(time_diff/(len(all_prompts)),2) }s per image) Peak memory usage: { -(mem_max_used // -1_048_576) } MiB / { -(mem_total // -1_048_576) } MiB / { round(mem_max_used/mem_total*100, 3) }%''' diff --git a/webui_playground.py b/webui_playground.py index f906636..a4fe0d0 100644 --- a/webui_playground.py +++ b/webui_playground.py @@ -20,14 +20,27 @@ RealESRGAN = True def run_goBIG(): pass def txt2img(*args, **kwargs): - images = [ - "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=387&q=80", - "https://images.unsplash.com/photo-1554151228-14d9def656e4?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=386&q=80", - "https://images.unsplash.com/photo-1542909168-82c3e7fdca5c?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MXx8aHVtYW4lMjBmYWNlfGVufDB8fDB8fA%3D%3D&w=1000&q=80", - "https://images.unsplash.com/photo-1546456073-92b9f0a8d413?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=387&q=80", - "https://images.unsplash.com/photo-1601412436009-d964bd02edbc?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=464&q=80", -] - return images, 1234, 'random', 'random output' + + #Output should match output_txt2img_gallery, output_txt2img_seed, output_txt2img_params, output_txt2img_stats + # info = f"""{args[0]} --seed {args[9]} --W {args[11]} --H {args[10]} -s {args[1]} -C {float(args[8])} --sampler {args[2]} """.strip() + args_and_names = { + "seed": args[9], + "width": args[11], + "height": args[10], + "steps": args[1], + "cfg_scale": str(args[8]), + "sampler": args[2], + } + + full_string = f"{args[0]}\n"+ " ".join([f"{k}:" for k,v in args_and_names.items()]) + info = { + 'text': full_string, + 'entities': [{'entity':str(v), 'start': full_string.find(f"{k}:"),'end': full_string.find(f"{k}:") + len(f"{k} ")} for k,v in args_and_names.items()] + } + images = [] + for i in range(args[6]): + images.append(f"http://placeimg.com/{args[11]}/{args[10]}/any") + return images, int(time.time()) , info, 'random output' def img2img(*args, **kwargs): images = [ "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=387&q=80", @@ -204,4 +217,4 @@ demo = draw_gradio_ui(opt, ) # demo.queue() -demo.launch(share=False, debug=True) +demo.launch(share=True, debug=True)