diff --git a/frontend/__init__.py b/frontend/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/css_and_js.py b/frontend/css_and_js.py
new file mode 100644
index 0000000..0506b0d
--- /dev/null
+++ b/frontend/css_and_js.py
@@ -0,0 +1,66 @@
+def css(opt):
+ css_hide_progressbar = """
+ .wrap .m-12 svg { display:none!important; }
+ .wrap .m-12::before { content:"Loading..." }
+ .progress-bar { display:none!important; }
+ .meta-text { display:none!important; }
+ """
+ styling = """
+
+ [data-testid="image"] {min-height: 512px !important}
+ * #body>.col:nth-child(2){width:250%;max-width:89vw}
+
+ #prompt_row input{
+ font-size:20px
+ }
+ #edit_mode_select{width:auto !important}
+ input[type=number]:disabled { -moz-appearance: textfield;+ }
+ #generate, #img2img_mask_btn,#img2img_edit_btn{
+ position: absolute;
+ right: 16px;
+ top: 14px;
+ }
+ @media (max-width: 420px){
+ #generate, #img2img_mask_btn,#img2img_edit_btn{
+ position: absolute;
+ right: 16px;
+ top: 5px;
+ }
+ }
+ """
+ return styling if opt.no_progressbar_hiding else styling + css_hide_progressbar
+
+# This is the code that finds which selected item the user has in the gallery
+js_part_getindex_txt2img="""
+const root = document.querySelector('gradio-app').shadowRoot;
+const getIndex = function(){
+const selected = root.querySelector('#txt2img_gallery_output .\\\\!ring-2');
+return selected ? [...selected.parentNode.children].indexOf(selected) : 0;
+};"""
+js_part_getindex_img2img="""
+const root = document.querySelector('gradio-app').shadowRoot;
+const getIndex = function(){
+ const selected = root.querySelector('#img2img_gallery_output .\\\\!ring-2');
+ return selected ? [...selected.parentNode.children].indexOf(selected) : 0;
+};"""
+js_part_clear_img2img="""
+root.querySelector('#img2img_editor .modify-upload button:last-child')?.click();
+
+
+"""
+js_return_selected_txt2img = "(x) => {" + js_part_getindex_txt2img + js_part_clear_img2img + """
+return [x[getIndex()].replace('data:;','data:image/png;')];
+}"""
+js_return_selected_img2img = "(x) => {" + js_part_getindex_img2img + js_part_clear_img2img + """
+return [x[getIndex()].replace('data:;','data:image/png;')];
+}"""
+
+js_part_copy_to_clipboard="""
+const data = x[getIndex()];
+const blob = await (await fetch(data.replace('data:;','data:image/png;'))).blob();
+const item = new ClipboardItem({'image/png': blob});
+navigator.clipboard.write([item]);
+return x;
+}"""
+js_copy_selected_txt2img = "async (x) => {" + js_part_getindex_txt2img + js_part_copy_to_clipboard
+js_copy_selected_img2img = "async (x) => {" + js_part_getindex_img2img + js_part_copy_to_clipboard
diff --git a/frontend/frontend.py b/frontend/frontend.py
new file mode 100644
index 0000000..757e101
--- /dev/null
+++ b/frontend/frontend.py
@@ -0,0 +1,366 @@
+import gradio as gr
+from frontend.css_and_js import *
+from frontend.css_and_js import css
+import frontend.ui_functions as uifn
+
+def draw_gradio_ui(opt, img2img=lambda x: x, txt2img=lambda x: x, txt2img_defaults={}, RealESRGAN=True, GFPGAN=True,
+ txt2img_toggles={}, txt2img_toggle_defaults='k_euler', show_embeddings=False, img2img_defaults={},
+ img2img_toggles={}, img2img_toggle_defaults={}, sample_img2img=None, img2img_mask_modes=None,
+ img2img_resize_modes=None, user_defaults={}, run_GFPGAN=lambda x: x, run_RealESRGAN=lambda x: x):
+
+ 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.Row(elem_id="prompt_row"):
+ txt2img_prompt = gr.Textbox(label="Prompt",
+ elem_id='prompt_input',
+ placeholder="A corgi wearing a top hat as an oil painting.",
+ lines=1,
+ max_lines=1 if txt2img_defaults['submit_on_enter'] == 'Yes' else 25,
+ value=txt2img_defaults['prompt'],
+ show_label=False)
+ txt2img_btn = gr.Button("Generate", elem_id="generate", variant="primary")
+
+ with gr.Row(elem_id='body').style(equal_height=False):
+ with gr.Column():
+ txt2img_height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height",
+ value=txt2img_defaults["height"])
+ txt2img_width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width",
+ value=txt2img_defaults["width"])
+ txt2img_cfg = gr.Slider(minimum=1.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'])
+ 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)',
+ 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'])
+ with gr.Column():
+ output_txt2img_gallery = gr.Gallery(label="Images", elem_id="txt2img_gallery_output").style(grid=[4, 4])
+
+ 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=js_copy_selected_txt2img)
+ 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.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=[],
+ _js='(x) => navigator.clipboard.writeText(x)', 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",
+ value=txt2img_defaults['ddim_steps'])
+ txt2img_sampling = gr.Dropdown(label='Sampling method (k_lms is default k-diffusion sampler)',
+ choices=["DDIM", "PLMS", 'k_dpm_2_a', 'k_dpm_2', 'k_euler_a',
+ 'k_euler', 'k_heun', 'k_lms'],
+ value=txt2img_defaults['sampler_name'])
+ with gr.Tabs():
+ with gr.TabItem('Simple'):
+ txt2img_submit_on_enter = gr.Radio(['Yes', 'No'],
+ label="Submit on enter? (no means multiline)",
+ value=txt2img_defaults['submit_on_enter'],
+ interactive=True)
+ txt2img_submit_on_enter.change(
+ lambda x: gr.update(max_lines=1 if x == 'Single' 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_realesrgan_model_name = gr.Dropdown(label='RealESRGAN model',
+ choices=['RealESRGAN_x4plus',
+ 'RealESRGAN_x4plus_anime_6B'],
+ value='RealESRGAN_x4plus',
+ visible=RealESRGAN is not None) # TODO: Feels like I shouldnt slot it in here.
+ txt2img_ddim_eta = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label="DDIM ETA",
+ value=txt2img_defaults['ddim_eta'], visible=False)
+ txt2img_embeddings = gr.File(label="Embeddings file for textual inversion",
+ visible=show_embeddings)
+
+ txt2img_btn.click(
+ txt2img,
+ [txt2img_prompt, txt2img_steps, txt2img_sampling, txt2img_toggles, txt2img_realesrgan_model_name,
+ txt2img_ddim_eta, txt2img_batch_count, txt2img_batch_size, txt2img_cfg, txt2img_seed,
+ txt2img_height, txt2img_width, txt2img_embeddings],
+ [output_txt2img_gallery, output_txt2img_seed, output_txt2img_params, output_txt2img_stats]
+ )
+ txt2img_prompt.submit(
+ txt2img,
+ [txt2img_prompt, txt2img_steps, txt2img_sampling, txt2img_toggles, txt2img_realesrgan_model_name,
+ txt2img_ddim_eta, txt2img_batch_count, txt2img_batch_size, txt2img_cfg, txt2img_seed,
+ txt2img_height, txt2img_width, txt2img_embeddings],
+ [output_txt2img_gallery, output_txt2img_seed, output_txt2img_params, output_txt2img_stats]
+ )
+
+ with gr.TabItem("Stable Diffusion 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',
+ placeholder="A fantasy landscape, trending on artstation.",
+ lines=1,
+ 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")
+ with gr.Row().style(equal_height=False):
+ with gr.Column():
+ gr.Markdown('#### Img2Img input')
+ img2img_image_editor = gr.Image(value=sample_img2img, source="upload", interactive=True,
+ type="pil", tool="select", elem_id="img2img_editor")
+ img2img_image_mask = gr.Image(value=sample_img2img, source="upload", interactive=True,
+ type="pil", tool="sketch", visible=False,
+ elem_id="img2img_mask")
+
+ with gr.Row():
+ img2img_image_editor_mode = gr.Radio(choices=["Mask", "Crop"], label="Image Editor Mode",
+ value="Crop", elem_id='edit_mode_select')
+
+ img2img_painterro_btn = gr.Button("Advanced Editor")
+ img2img_copy_from_painterro_btn = gr.Button(value="Get Image from Advanced Editor")
+ img2img_show_help_btn = gr.Button("Show Hints")
+ img2img_hide_help_btn = gr.Button("Hide Hints", visible=False)
+ img2img_help = gr.Markdown(visible=False, value="")
+
+
+
+ with gr.Column():
+ gr.Markdown('#### Img2Img Results')
+ output_img2img_gallery = gr.Gallery(label="Images", elem_id="img2img_gallery_output").style(grid=[4,4,4])
+ with gr.Tabs():
+ with gr.TabItem("Generated image actions", id="img2img_actions_tab"):
+ with gr.Group():
+ gr.Markdown("Select an image, then press one of the buttons below")
+ output_img2img_copy_to_clipboard_btn = gr.Button("Copy to clipboard")
+ output_img2img_copy_to_input_btn = gr.Button("Push to img2img input")
+ output_img2img_copy_to_mask_btn = gr.Button("Push to img2img input mask")
+ gr.Markdown("Warning: This will clear your current image and mask settings!")
+ with gr.TabItem("Output info", id="img2img_output_info_tab"):
+ output_img2img_params = gr.Textbox(label="Generation parameters")
+ with gr.Row():
+ output_img2img_copy_params = gr.Button("Copy full parameters").click(
+ inputs=output_img2img_params, outputs=[],
+ _js='(x) => navigator.clipboard.writeText(x)', 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=[],
+ _js='(x) => navigator.clipboard.writeText(x)', fn=None, show_progress=False)
+ output_img2img_stats = gr.HTML(label='Stats')
+ gr.Markdown('# img2img settings')
+ with gr.Row():
+
+ with gr.Column():
+ img2img_batch_size = gr.Slider(minimum=1, maximum=8, step=1,
+ label='Batch size (how many images are in a batch; memory-hungry)',
+ value=img2img_defaults['batch_size'])
+ img2img_width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width",
+ value=img2img_defaults["width"])
+ img2img_height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height",
+ value=img2img_defaults["height"])
+ img2img_seed = gr.Textbox(label="Seed (blank to randomize)", lines=1,
+ value=img2img_defaults["seed"])
+ img2img_steps = gr.Slider(minimum=1, maximum=250, step=1, label="Sampling Steps",
+ value=img2img_defaults['ddim_steps'])
+ img2img_batch_count = gr.Slider(minimum=1, maximum=250, step=1,
+ label='Batch count (how many batches of images to generate)',
+ value=img2img_defaults['n_iter'])
+ with gr.Column():
+ 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)
+
+ img2img_sampling = gr.Dropdown(label='Sampling method (k_lms is default k-diffusion sampler)',
+ choices=["DDIM", 'k_dpm_2_a', 'k_dpm_2', 'k_euler_a', 'k_euler',
+ 'k_heun', 'k_lms'],
+ value=img2img_defaults['sampler_name'])
+ img2img_toggles = gr.CheckboxGroup(label='', choices=img2img_toggles,
+ value=img2img_toggle_defaults, type="index")
+ img2img_realesrgan_model_name = gr.Dropdown(label='RealESRGAN model',
+ choices=['RealESRGAN_x4plus',
+ 'RealESRGAN_x4plus_anime_6B'],
+ value='RealESRGAN_x4plus',
+ visible=RealESRGAN is not None) # TODO: Feels like I shouldnt slot it in here.
+
+
+ img2img_cfg = gr.Slider(minimum=1.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'])
+ img2img_denoising = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Denoising Strength',
+ value=img2img_defaults['denoising_strength'])
+
+ img2img_resize = gr.Radio(label="Resize mode",
+ choices=["Just resize", "Crop and resize", "Resize and fill"],
+ type="index",
+ value=img2img_resize_modes[img2img_defaults['resize_mode']])
+ img2img_embeddings = gr.File(label="Embeddings file for textual inversion",
+ visible=show_embeddings)
+
+ img2img_image_editor_mode.change(
+ uifn.change_image_editor_mode,
+ [img2img_image_editor_mode, img2img_image_editor, img2img_resize, img2img_width, img2img_height],
+ [img2img_image_editor, img2img_image_mask, img2img_btn_editor, img2img_btn_mask,
+ img2img_painterro_btn, img2img_copy_from_painterro_btn, img2img_mask, img2img_mask_blur_strength]
+ )
+
+ img2img_image_editor.edit(
+ uifn.update_image_mask,
+ [img2img_image_editor, img2img_resize, img2img_width, img2img_height],
+ img2img_image_mask
+ )
+
+ img2img_show_help_btn.click(
+ uifn.show_help,
+ None,
+ [img2img_show_help_btn, img2img_hide_help_btn, img2img_help]
+ )
+
+ img2img_hide_help_btn.click(
+ uifn.hide_help,
+ None,
+ [img2img_show_help_btn, img2img_hide_help_btn, img2img_help]
+ )
+
+ output_txt2img_copy_to_input_btn.click(
+ uifn.copy_img_to_input,
+ [output_txt2img_gallery],
+ [img2img_image_editor, img2img_image_mask, tabs],
+ _js=js_return_selected_txt2img
+ )
+
+ output_img2img_copy_to_input_btn.click(
+ uifn.copy_img_to_edit,
+ [output_img2img_gallery],
+ [img2img_image_editor, tabs, img2img_image_editor_mode],
+ _js=js_return_selected_img2img
+ )
+ output_img2img_copy_to_mask_btn.click(
+ uifn.copy_img_to_mask,
+ [output_img2img_gallery],
+ [img2img_image_mask, tabs, img2img_image_editor_mode],
+ _js=js_return_selected_img2img
+ )
+
+ output_img2img_copy_to_clipboard_btn.click(fn=None, inputs=output_img2img_gallery, outputs=[],
+ _js=js_copy_selected_img2img)
+
+ img2img_btn_mask.click(
+ img2img,
+ [img2img_prompt, img2img_image_editor_mode, img2img_image_mask, img2img_mask,
+ img2img_mask_blur_strength, img2img_steps, img2img_sampling, img2img_toggles,
+ img2img_realesrgan_model_name, img2img_batch_count, img2img_batch_size, img2img_cfg,
+ img2img_denoising, img2img_seed, img2img_height, img2img_width, img2img_resize,
+ img2img_embeddings],
+ [output_img2img_gallery, output_img2img_seed, output_img2img_params, output_img2img_stats]
+ )
+ def img2img_submit_params():
+ return (img2img,
+ [img2img_prompt, img2img_image_editor_mode, img2img_image_editor, img2img_mask,
+ img2img_mask_blur_strength, img2img_steps, img2img_sampling, img2img_toggles,
+ img2img_realesrgan_model_name, img2img_batch_count, img2img_batch_size, img2img_cfg,
+ 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())
+ img2img_prompt.submit(*img2img_submit_params())
+
+ img2img_painterro_btn.click(None, [img2img_image_editor], None, _js="""(img) => {
+ try {
+ Painterro({
+ hiddenTools: ['arrow'],
+ saveHandler: function (image, done) {
+ localStorage.setItem('painterro-image', image.asDataURL());
+ done(true);
+ },
+ }).show(Array.isArray(img) ? img[0] : img);
+ } catch(e) {
+ const script = document.createElement('script');
+ script.src = 'https://unpkg.com/painterro@1.2.78/build/painterro.min.js';
+ document.head.appendChild(script);
+ const style = document.createElement('style');
+ style.appendChild(document.createTextNode('.ptro-holder-wrapper { z-index: 9999 !important; }'));
+ document.head.appendChild(style);
+ }
+ return [];
+ }""")
+
+ img2img_copy_from_painterro_btn.click(None, None, [img2img_image_editor, img2img_image_mask], _js="""() => {
+ const image = localStorage.getItem('painterro-image')
+ return [image, image];
+ }""")
+
+ if GFPGAN is not None:
+ gfpgan_defaults = {
+ 'strength': 100,
+ }
+
+ if 'gfpgan' in user_defaults:
+ gfpgan_defaults.update(user_defaults['gfpgan'])
+
+ with gr.TabItem("GFPGAN", id='cfpgan_tab'):
+ gr.Markdown("Fix faces on images")
+ with gr.Row():
+ with gr.Column():
+ gfpgan_source = gr.Image(label="Source", source="upload", interactive=True, type="pil")
+ gfpgan_strength = gr.Slider(minimum=0.0, maximum=1.0, step=0.001, label="Effect strength",
+ value=gfpgan_defaults['strength'])
+ gfpgan_btn = gr.Button("Generate", variant="primary")
+ with gr.Column():
+ gfpgan_output = gr.Image(label="Output")
+ gfpgan_btn.click(
+ run_GFPGAN,
+ [gfpgan_source, gfpgan_strength],
+ [gfpgan_output]
+ )
+ if RealESRGAN is not None:
+ with gr.TabItem("RealESRGAN", id='realesrgan_tab'):
+ gr.Markdown("Upscale images")
+ with gr.Row():
+ with gr.Column():
+ realesrgan_source = gr.Image(label="Source", source="upload", interactive=True, type="pil")
+ realesrgan_model_name = gr.Dropdown(label='RealESRGAN model', choices=['RealESRGAN_x4plus',
+ 'RealESRGAN_x4plus_anime_6B'],
+ value='RealESRGAN_x4plus')
+ realesrgan_btn = gr.Button("Generate")
+ with gr.Column():
+ realesrgan_output = gr.Image(label="Output")
+ realesrgan_btn.click(
+ run_RealESRGAN,
+ [realesrgan_source, realesrgan_model_name],
+ [realesrgan_output]
+ )
+ output_txt2img_to_upscale_esrgan.click(
+ uifn.copy_img_to_upscale_esrgan,
+ output_txt2img_gallery,
+ [realesrgan_source, tabs],
+ _js=js_return_selected_txt2img)
+
+ gr.HTML("""
+
+
For help and advanced usage guides, visit the Project Wiki
+
Stable Diffusion WebUI is an open-source project. You can find the latest stable builds on the main repository.
+ If you would like to contribute to developement or test bleeding edge builds, you can visit the developement repository.
+
+ """)
+ return demo
\ No newline at end of file
diff --git a/frontend/ui_functions.py b/frontend/ui_functions.py
new file mode 100644
index 0000000..c115151
--- /dev/null
+++ b/frontend/ui_functions.py
@@ -0,0 +1,115 @@
+import re
+import gradio as gr
+from PIL import Image, ImageFont, ImageDraw, ImageFilter, ImageOps
+from io import BytesIO
+import base64
+import re
+
+
+def change_image_editor_mode(choice, cropped_image, resize_mode, width, height):
+ if choice == "Mask":
+ return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), gr.update(visible=False), gr.update(visible=True), gr.update(visible=True)]
+ return [gr.update(visible=True), gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), gr.update(visible=True), gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)]
+
+def update_image_mask(cropped_image, resize_mode, width, height):
+ resized_cropped_image = resize_image(resize_mode, cropped_image, width, height) if cropped_image else None
+ return gr.update(value=resized_cropped_image)
+
+def copy_img_to_input(img):
+ try:
+ image_data = re.sub('^data:image/.+;base64,', '', img)
+ processed_image = Image.open(BytesIO(base64.b64decode(image_data)))
+ tab_update = gr.update(selected='img2img_tab')
+ img_update = gr.update(value=processed_image)
+ return processed_image, processed_image , tab_update
+ except IndexError:
+ return [None, None]
+
+def copy_img_to_edit(img):
+ try:
+ image_data = re.sub('^data:image/.+;base64,', '', img)
+ processed_image = Image.open(BytesIO(base64.b64decode(image_data)))
+ tab_update = gr.update(selected='img2img_tab')
+ img_update = gr.update(value=processed_image)
+ mode_update = gr.update(value='Crop')
+ return processed_image, tab_update, mode_update
+ except IndexError:
+ return [None, None]
+
+def copy_img_to_mask(img):
+ try:
+ image_data = re.sub('^data:image/.+;base64,', '', img)
+ processed_image = Image.open(BytesIO(base64.b64decode(image_data)))
+ tab_update = gr.update(selected='img2img_tab')
+ img_update = gr.update(value=processed_image)
+ mode_update = gr.update(value='Mask')
+ return processed_image, tab_update, mode_update
+ except IndexError:
+ return [None, None]
+
+
+
+def copy_img_to_upscale_esrgan(img):
+ tabs_update = gr.update(selected='realesrgan_tab')
+ image_data = re.sub('^data:image/.+;base64,', '', img)
+ processed_image = Image.open(BytesIO(base64.b64decode(image_data)))
+ return processed_image, tabs_update
+
+
+help_text = """
+ ## Mask/Crop
+ * The masking/cropping is very temperamental.
+ * It may take some time for the image to show when switching from Crop to Mask.
+ * If the image doesn't appear after switching to Mask, switch back to Crop and then back again to Mask
+ * If the mask appears distorted (the brush is weirdly shaped instead of round), switch back to Crop and then back again to Mask.
+
+ ## Advanced Editor
+ * For now the button needs to be clicked twice the first time.
+ * Once you have edited your image, you _need_ to click the save button for the next step to work.
+ * Clear the image from the crop editor (click the x)
+ * Click "Get Image from Advanced Editor" to get the image you saved. If it doesn't work, try opening the editor and saving again.
+
+ If it keeps not working, try switching modes again, switch tabs, clear the image or reload.
+"""
+
+def show_help():
+ return [gr.update(visible=False), gr.update(visible=True), gr.update(value=help_text)]
+
+def hide_help():
+ return [gr.update(visible=True), gr.update(visible=False), gr.update(value="")]
+
+def resize_image(resize_mode, im, width, height):
+ LANCZOS = (Image.Resampling.LANCZOS if hasattr(Image, 'Resampling') else Image.LANCZOS)
+ if resize_mode == 0:
+ res = im.resize((width, height), resample=LANCZOS)
+ elif resize_mode == 1:
+ ratio = width / height
+ src_ratio = im.width / im.height
+
+ src_w = width if ratio > src_ratio else im.width * height // im.height
+ src_h = height if ratio <= src_ratio else im.height * width // im.width
+
+ resized = im.resize((src_w, src_h), resample=LANCZOS)
+ res = Image.new("RGB", (width, height))
+ res.paste(resized, box=(width // 2 - src_w // 2, height // 2 - src_h // 2))
+ else:
+ ratio = width / height
+ src_ratio = im.width / im.height
+
+ src_w = width if ratio < src_ratio else im.width * height // im.height
+ src_h = height if ratio >= src_ratio else im.height * width // im.width
+
+ resized = im.resize((src_w, src_h), resample=LANCZOS)
+ res = Image.new("RGB", (width, height))
+ res.paste(resized, box=(width // 2 - src_w // 2, height // 2 - src_h // 2))
+
+ if ratio < src_ratio:
+ fill_height = height // 2 - src_h // 2
+ res.paste(resized.resize((width, fill_height), box=(0, 0, width, 0)), box=(0, 0))
+ res.paste(resized.resize((width, fill_height), box=(0, resized.height, width, resized.height)), box=(0, fill_height + src_h))
+ elif ratio > src_ratio:
+ fill_width = width // 2 - src_w // 2
+ res.paste(resized.resize((fill_width, height), box=(0, 0, 0, height)), box=(0, 0))
+ res.paste(resized.resize((fill_width, height), box=(resized.width, 0, resized.width, height)), box=(fill_width + src_w, 0))
+
+ return res
diff --git a/webui.py b/webui.py
index 1bd4875..5627e65 100644
--- a/webui.py
+++ b/webui.py
@@ -1,5 +1,7 @@
import argparse, os, sys, glob, re
+from frontend.frontend import draw_gradio_ui
+
parser = argparse.ArgumentParser()
parser.add_argument("--outdir", type=str, nargs="?", help="dir to write results to", default=None)
parser.add_argument("--outdir_txt2img", type=str, nargs="?", help="dir to write txt2img results to (overrides --outdir)", default=None)
@@ -471,40 +473,6 @@ def draw_prompt_matrix(im, width, height, all_prompts):
return result
-def resize_image(resize_mode, im, width, height):
- if resize_mode == 0:
- res = im.resize((width, height), resample=LANCZOS)
- elif resize_mode == 1:
- ratio = width / height
- src_ratio = im.width / im.height
-
- src_w = width if ratio > src_ratio else im.width * height // im.height
- src_h = height if ratio <= src_ratio else im.height * width // im.width
-
- resized = im.resize((src_w, src_h), resample=LANCZOS)
- res = Image.new("RGB", (width, height))
- res.paste(resized, box=(width // 2 - src_w // 2, height // 2 - src_h // 2))
- else:
- ratio = width / height
- src_ratio = im.width / im.height
-
- src_w = width if ratio < src_ratio else im.width * height // im.height
- src_h = height if ratio >= src_ratio else im.height * width // im.width
-
- resized = im.resize((src_w, src_h), resample=LANCZOS)
- res = Image.new("RGB", (width, height))
- res.paste(resized, box=(width // 2 - src_w // 2, height // 2 - src_h // 2))
-
- if ratio < src_ratio:
- fill_height = height // 2 - src_h // 2
- res.paste(resized.resize((width, fill_height), box=(0, 0, width, 0)), box=(0, 0))
- res.paste(resized.resize((width, fill_height), box=(0, resized.height, width, resized.height)), box=(0, fill_height + src_h))
- elif ratio > src_ratio:
- fill_width = width // 2 - src_w // 2
- res.paste(resized.resize((fill_width, height), box=(0, 0, 0, height)), box=(0, 0))
- res.paste(resized.resize((fill_width, height), box=(resized.width, 0, resized.width, height)), box=(fill_width + src_w, 0))
-
- return res
def check_prompt_length(prompt, comments):
@@ -1463,295 +1431,7 @@ def hide_help():
return [gr.update(visible=True), gr.update(visible=False), gr.update(value="")]
-css_hide_progressbar = """
-.wrap .m-12 svg { display:none!important; }
-.wrap .m-12::before { content:"Loading..." }
-.progress-bar { display:none!important; }
-.meta-text { display:none!important; }
-"""
-
-styling = """
-[data-testid="image"] {min-height: 512px !important}
-* #body>.col:nth-child(2){width:250%;max-width:89vw}
-#generate{width: 100%; }
-#prompt_row input{
- font-size:20px
-}
-input[type=number]:disabled { -moz-appearance: textfield;+ }
-"""
-
-css = styling if opt.no_progressbar_hiding else styling + css_hide_progressbar
-# This is the code that finds which selected item the user has in the gallery
-js_part_getindex_txt2img="""
-const root = document.querySelector('gradio-app').shadowRoot;
-const getIndex = function(){
-const selected = root.querySelector('#txt2img_gallery_output .\\\\!ring-2');
-return selected ? [...selected.parentNode.children].indexOf(selected) : 0;
-};"""
-js_part_getindex_img2img="""
-const root = document.querySelector('gradio-app').shadowRoot;
-const getIndex = function(){
- const selected = root.querySelector('#img2img_gallery_output .\\\\!ring-2');
- return selected ? [...selected.parentNode.children].indexOf(selected) : 0;
-};"""
-js_part_clear_img2img="""
-root.querySelector('#img2img_editor .modify-upload button:last-child')?.click();
-root.querySelector('#img2img_editor')?.parentNode.querySelector('input[type="radio"][value="Mask"]')?.click();
-root.querySelector('#img2img_mask .modify-upload button:last-child')?.click();
-"""
-js_return_selected_txt2img = "(x) => {" + js_part_getindex_txt2img + js_part_clear_img2img + """
-return [x[getIndex()].replace('data:;','data:image/png;')];
-}"""
-js_return_selected_img2img = "(x) => {" + js_part_getindex_img2img + js_part_clear_img2img + """
-return [x[getIndex()].replace('data:;','data:image/png;')];
-}"""
-js_part_copy_to_clipboard="""
-const data = x[getIndex()];
-const blob = await (await fetch(data.replace('data:;','data:image/png;'))).blob();
-const item = new ClipboardItem({'image/png': blob});
-navigator.clipboard.write([item]);
-return x;
-}"""
-js_copy_selected_txt2img = "async (x) => {" + js_part_getindex_txt2img + js_part_copy_to_clipboard
-js_copy_selected_img2img = "async (x) => {" + js_part_getindex_img2img + js_part_copy_to_clipboard
-
-with gr.Blocks(css=css, 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.Row(elem_id="prompt_row"):
- txt2img_prompt = gr.Textbox(label="Prompt",
- elem_id='prompt_input',
- placeholder="A corgi wearing a top hat as an oil painting.",
- lines=1,
- max_lines=1 if txt2img_defaults['submit_on_enter'] == 'Yes' else 25,
- value=txt2img_defaults['prompt'],
- show_label=False)
-
- with gr.Row(elem_id='body').style(equal_height=False):
- with gr.Column():
- txt2img_height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=txt2img_defaults["height"])
- txt2img_width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=txt2img_defaults["width"])
- txt2img_cfg = gr.Slider(minimum=1.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'])
- 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)', 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'])
- with gr.Column():
- output_txt2img_gallery = gr.Gallery(label="Images", elem_id="txt2img_gallery_output").style(grid=[4,4])
- 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=js_copy_selected_txt2img)
- 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.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=[], _js='(x) => navigator.clipboard.writeText(x)', 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_btn = gr.Button("Generate", elem_id="generate", variant="primary")
- txt2img_steps = gr.Slider(minimum=1, maximum=250, step=1, label="Sampling Steps", value=txt2img_defaults['ddim_steps'])
- txt2img_sampling = gr.Dropdown(label='Sampling method (k_lms is default k-diffusion sampler)', choices=["DDIM", "PLMS", 'k_dpm_2_a', 'k_dpm_2', 'k_euler_a', 'k_euler', 'k_heun', 'k_lms'], value=txt2img_defaults['sampler_name'])
- with gr.Tabs():
- with gr.TabItem('Simple'):
- txt2img_submit_on_enter = gr.Radio(['Yes', 'No'], label="Submit on enter? (no means multiline)", value=txt2img_defaults['submit_on_enter'], interactive=True)
- txt2img_submit_on_enter.change(lambda x: gr.update(max_lines=1 if x == 'Single' 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_realesrgan_model_name = gr.Dropdown(label='RealESRGAN model', choices=['RealESRGAN_x4plus', 'RealESRGAN_x4plus_anime_6B'], value='RealESRGAN_x4plus', visible=RealESRGAN is not None) # TODO: Feels like I shouldnt slot it in here.
- txt2img_ddim_eta = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label="DDIM ETA", value=txt2img_defaults['ddim_eta'], visible=False)
- txt2img_embeddings = gr.File(label = "Embeddings file for textual inversion", visible=hasattr(model, "embedding_manager"))
-
- txt2img_btn.click(
- txt2img,
- [txt2img_prompt, txt2img_steps, txt2img_sampling, txt2img_toggles, txt2img_realesrgan_model_name, txt2img_ddim_eta, txt2img_batch_count, txt2img_batch_size, txt2img_cfg, txt2img_seed, txt2img_height, txt2img_width, txt2img_embeddings],
- [output_txt2img_gallery, output_txt2img_seed, output_txt2img_params, output_txt2img_stats]
- )
- txt2img_prompt.submit(
- txt2img,
- [txt2img_prompt, txt2img_steps, txt2img_sampling, txt2img_toggles, txt2img_realesrgan_model_name, txt2img_ddim_eta, txt2img_batch_count, txt2img_batch_size, txt2img_cfg, txt2img_seed, txt2img_height, txt2img_width, txt2img_embeddings],
- [output_txt2img_gallery, output_txt2img_seed, output_txt2img_params, output_txt2img_stats]
- )
-
- with gr.TabItem("Stable Diffusion 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',
- placeholder="A fantasy landscape, trending on artstation.",
- lines=1,
- 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_editot_btn")
- with gr.Row().style(equal_height=False):
- with gr.Column():
- img2img_image_editor_mode = gr.Radio(choices=["Mask", "Crop"], label="Image Editor Mode", value="Crop")
- img2img_show_help_btn = gr.Button("Show Hints")
- img2img_hide_help_btn = gr.Button("Hide Hints", visible=False)
- img2img_help = gr.Markdown(visible=False, value="")
- with gr.Row():
- img2img_painterro_btn = gr.Button("Advanced Editor")
- img2img_copy_from_painterro_btn = gr.Button(value="Get Image from Advanced Editor")
- img2img_image_editor = gr.Image(value=sample_img2img, source="upload", interactive=True, type="pil", tool="select", elem_id="img2img_editor")
- img2img_image_mask = gr.Image(value=sample_img2img, source="upload", interactive=True, type="pil", tool="sketch", visible=False, elem_id="img2img_mask")
- 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)
- img2img_steps = gr.Slider(minimum=1, maximum=250, step=1, label="Sampling Steps", value=img2img_defaults['ddim_steps'])
- img2img_sampling = gr.Dropdown(label='Sampling method (k_lms is default k-diffusion sampler)', choices=["DDIM", 'k_dpm_2_a', 'k_dpm_2', 'k_euler_a', 'k_euler', 'k_heun', 'k_lms'], value=img2img_defaults['sampler_name'])
- img2img_toggles = gr.CheckboxGroup(label='', choices=img2img_toggles, value=img2img_toggle_defaults, type="index")
- img2img_realesrgan_model_name = gr.Dropdown(label='RealESRGAN model', choices=['RealESRGAN_x4plus', 'RealESRGAN_x4plus_anime_6B'], value='RealESRGAN_x4plus', visible=RealESRGAN is not None) # TODO: Feels like I shouldnt slot it in here.
- img2img_batch_count = gr.Slider(minimum=1, maximum=250, step=1, label='Batch count (how many batches of images to generate)', value=img2img_defaults['n_iter'])
- img2img_batch_size = gr.Slider(minimum=1, maximum=8, step=1, label='Batch size (how many images are in a batch; memory-hungry)', value=img2img_defaults['batch_size'])
- img2img_cfg = gr.Slider(minimum=1.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'])
- img2img_denoising = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Denoising Strength', value=img2img_defaults['denoising_strength'])
- img2img_seed = gr.Textbox(label="Seed (blank to randomize)", lines=1, value=img2img_defaults["seed"])
- img2img_height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=img2img_defaults["height"])
- img2img_width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=img2img_defaults["width"])
- img2img_resize = gr.Radio(label="Resize mode", choices=["Just resize", "Crop and resize", "Resize and fill"], type="index", value=img2img_resize_modes[img2img_defaults['resize_mode']])
- img2img_embeddings = gr.File(label = "Embeddings file for textual inversion", visible=hasattr(model, "embedding_manager"))
-
- with gr.Column():
- output_img2img_gallery = gr.Gallery(label="Images", elem_id="img2img_gallery_output")
- with gr.Tabs():
- with gr.TabItem("Generated image actions", id="img2img_actions_tab"):
- gr.Markdown("Select an image, then press one of the buttons below")
- output_img2img_copy_to_clipboard_btn = gr.Button("Copy to clipboard")
- output_img2img_copy_to_input_btn = gr.Button("Push to img2img input")
- gr.Markdown("Warning: This will clear your current image and mask settings!")
- with gr.TabItem("Output info", id="img2img_output_info_tab"):
- output_img2img_params = gr.Textbox(label="Generation parameters")
- with gr.Row():
- output_img2img_copy_params = gr.Button("Copy full parameters").click(inputs=output_img2img_params, outputs=[], _js='(x) => navigator.clipboard.writeText(x)', 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=[], _js='(x) => navigator.clipboard.writeText(x)', fn=None, show_progress=False)
- output_img2img_stats = gr.HTML(label='Stats')
-
- img2img_image_editor_mode.change(
- change_image_editor_mode,
- [img2img_image_editor_mode, img2img_image_editor, img2img_resize, img2img_width, img2img_height],
- [img2img_image_editor, img2img_image_mask, img2img_btn_editor, img2img_btn_mask, img2img_painterro_btn, img2img_copy_from_painterro_btn, img2img_mask, img2img_mask_blur_strength]
- )
-
- img2img_image_editor.edit(
- update_image_mask,
- [img2img_image_editor, img2img_resize, img2img_width, img2img_height],
- img2img_image_mask
- )
-
- img2img_show_help_btn.click(
- show_help,
- None,
- [img2img_show_help_btn, img2img_hide_help_btn, img2img_help]
- )
-
- img2img_hide_help_btn.click(
- hide_help,
- None,
- [img2img_show_help_btn, img2img_hide_help_btn, img2img_help]
- )
-
- output_txt2img_copy_to_input_btn.click(
- copy_img_to_input,
- [output_txt2img_gallery],
- [img2img_image_editor, img2img_image_mask, tabs],
- _js=js_return_selected_txt2img
- )
-
- output_img2img_copy_to_input_btn.click(
- copy_img_to_input,
- [output_img2img_gallery],
- [img2img_image_editor, img2img_image_mask, tabs],
- _js=js_return_selected_img2img
- )
-
- output_img2img_copy_to_clipboard_btn.click(fn=None, inputs=output_img2img_gallery, outputs=[], _js=js_copy_selected_img2img)
-
- img2img_btn_mask.click(
- img2img,
- [img2img_prompt, img2img_image_editor_mode, img2img_image_mask, img2img_mask, img2img_mask_blur_strength, img2img_steps, img2img_sampling, img2img_toggles, img2img_realesrgan_model_name, img2img_batch_count, img2img_batch_size, img2img_cfg, 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,
- [img2img_prompt, img2img_image_editor_mode, img2img_image_editor, img2img_mask, img2img_mask_blur_strength, img2img_steps, img2img_sampling, img2img_toggles, img2img_realesrgan_model_name, img2img_batch_count, img2img_batch_size, img2img_cfg, 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_painterro_btn.click(None, [img2img_image_editor], None, _js="""(img) => {
- try {
- Painterro({
- hiddenTools: ['arrow'],
- saveHandler: function (image, done) {
- localStorage.setItem('painterro-image', image.asDataURL());
- done(true);
- },
- }).show(Array.isArray(img) ? img[0] : img);
- } catch(e) {
- const script = document.createElement('script');
- script.src = 'https://unpkg.com/painterro@1.2.78/build/painterro.min.js';
- document.head.appendChild(script);
- const style = document.createElement('style');
- style.appendChild(document.createTextNode('.ptro-holder-wrapper { z-index: 9999 !important; }'));
- document.head.appendChild(style);
- }
- return [];
- }""")
-
- img2img_copy_from_painterro_btn.click(None, None, [img2img_image_editor, img2img_image_mask], _js="""() => {
- const image = localStorage.getItem('painterro-image')
- return [image, image];
- }""")
-
- if GFPGAN is not None:
- gfpgan_defaults = {
- 'strength': 100,
- }
-
- if 'gfpgan' in user_defaults:
- gfpgan_defaults.update(user_defaults['gfpgan'])
-
- with gr.TabItem("GFPGAN", id='cfpgan_tab'):
- gr.Markdown("Fix faces on images")
- with gr.Row():
- with gr.Column():
- gfpgan_source = gr.Image(label="Source", source="upload", interactive=True, type="pil")
- gfpgan_strength = gr.Slider(minimum=0.0, maximum=1.0, step=0.001, label="Effect strength", value=gfpgan_defaults['strength'])
- gfpgan_btn = gr.Button("Generate", variant="primary")
- with gr.Column():
- gfpgan_output = gr.Image(label="Output")
- gfpgan_btn.click(
- run_GFPGAN,
- [gfpgan_source, gfpgan_strength],
- [gfpgan_output]
- )
- if RealESRGAN is not None:
- with gr.TabItem("RealESRGAN", id='realesrgan_tab'):
- gr.Markdown("Upscale images")
- with gr.Row():
- with gr.Column():
- realesrgan_source = gr.Image(label="Source", source="upload", interactive=True, type="pil")
- realesrgan_model_name = gr.Dropdown(label='RealESRGAN model', choices=['RealESRGAN_x4plus', 'RealESRGAN_x4plus_anime_6B'], value='RealESRGAN_x4plus')
- realesrgan_btn = gr.Button("Generate")
- with gr.Column():
- realesrgan_output = gr.Image(label="Output")
- realesrgan_btn.click(
- run_RealESRGAN,
- [realesrgan_source, realesrgan_model_name],
- [realesrgan_output]
- )
- gr.HTML("""
-
-
For help and advanced usage guides, visit the Project Wiki
-
Stable Diffusion WebUI is an open-source project. You can find the latest stable builds on the main repository.
- If you would like to contribute to developement or test bleeding edge builds, you can visit the developement repository.
-
- """)
+demo = draw_gradio_ui(opt, txt2img_defaults=txt2img_defaults)
class ServerLauncher(threading.Thread):
def __init__(self, demo):
diff --git a/webui_playground.py b/webui_playground.py
index d9cf72f..2012d88 100644
--- a/webui_playground.py
+++ b/webui_playground.py
@@ -6,6 +6,8 @@ from io import BytesIO
import base64
import re
+from frontend.frontend import draw_gradio_ui
+
"""
This file is here to play around with the interface without loading the whole model
@@ -161,60 +163,6 @@ if 'img2img' in user_defaults:
img2img_toggle_defaults = [img2img_toggles[i] for i in img2img_defaults['toggles']]
img2img_image_mode = 'sketch'
-def change_image_editor_mode(choice, cropped_image, resize_mode, width, height):
- if choice == "Mask":
- return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), gr.update(visible=False), gr.update(visible=True), gr.update(visible=True)]
- return [gr.update(visible=True), gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), gr.update(visible=True), gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)]
-
-def update_image_mask(cropped_image, resize_mode, width, height):
- resized_cropped_image = resize_image(resize_mode, cropped_image, width, height) if cropped_image else None
- return gr.update(value=resized_cropped_image)
-
-def copy_img_to_input(img):
- try:
- image_data = re.sub('^data:image/.+;base64,', '', img)
- processed_image = Image.open(BytesIO(base64.b64decode(image_data)))
- tab_update = gr.update(selected='img2img_tab')
- img_update = gr.update(value=processed_image)
- return {img2img_image_mask: processed_image, img2img_image_editor: img_update, tabs: tab_update}
- except IndexError:
- return [None, None]
-
-
-def copy_img_to_upscale_esrgan(img):
- update = gr.update(selected='realesrgan_tab')
- image_data = re.sub('^data:image/.+;base64,', '', img)
- processed_image = Image.open(BytesIO(base64.b64decode(image_data)))
- return {realesrgan_source: processed_image, tabs: update}
-
-def copy_img_to_upscale_gobig(img):
- update = gr.update(selected='gobig_tab')
- image_data = re.sub('^data:image/.+;base64,', '', img)
- processed_image = Image.open(BytesIO(base64.b64decode(image_data)))
- return {realesrganGoBig_source: processed_image, tabs: update}
-
-help_text = """
- ## Mask/Crop
- * The masking/cropping is very temperamental.
- * It may take some time for the image to show when switching from Crop to Mask.
- * If the image doesn't appear after switching to Mask, switch back to Crop and then back again to Mask
- * If the mask appears distorted (the brush is weirdly shaped instead of round), switch back to Crop and then back again to Mask.
-
- ## Advanced Editor
- * For now the button needs to be clicked twice the first time.
- * Once you have edited your image, you _need_ to click the save button for the next step to work.
- * Clear the image from the crop editor (click the x)
- * Click "Get Image from Advanced Editor" to get the image you saved. If it doesn't work, try opening the editor and saving again.
-
- If it keeps not working, try switching modes again, switch tabs, clear the image or reload.
-"""
-
-def show_help():
- return [gr.update(visible=False), gr.update(visible=True), gr.update(value=help_text)]
-
-def hide_help():
- return [gr.update(visible=True), gr.update(visible=False), gr.update(value="")]
-
css_hide_progressbar = """
.wrap .m-12 svg { display:none!important; }
@@ -233,283 +181,25 @@ styling = """
input[type=number]:disabled { -moz-appearance: textfield;+ }
"""
-css = styling if opt.no_progressbar_hiding else styling + css_hide_progressbar
-# This is the code that finds which selected item the user has in the gallery
-js_part="""let getIndex = function(){
- let selected = document.querySelector('gradio-app').shadowRoot.querySelector('#gallery_output .\\\\!ring-2');
- return selected ? [...selected.parentNode.children].indexOf(selected) : 0;
- };"""
-return_selected_img_js = "(x) => {" + js_part+ " document.querySelector('gradio-app').shadowRoot.querySelector('#img2img_editor .modify-upload button:last-child')?.click();return [x[getIndex()].replace('data:;','data:image/png;')]}"
-copy_selected_img_js = "async (x) => {" + js_part+ """
-let data = x[getIndex()];
-const blob = await (await fetch(data.replace('data:;','data:image/png;'))).blob();
-let item = new ClipboardItem({'image/png': blob})
-navigator.clipboard.write([item]);
-return x
-}"""
+demo = draw_gradio_ui(opt,
+ user_defaults=user_defaults,
+ txt2img=txt2img,
+ img2img=img2img,
+ txt2img_defaults=txt2img_defaults,
+ txt2img_toggles=txt2img_toggles,
+ txt2img_toggle_defaults=txt2img_toggle_defaults,
+ show_embeddings=hasattr(model, "embedding_manager"),
+ img2img_defaults=img2img_defaults,
+ img2img_toggles=img2img_toggles,
+ img2img_toggle_defaults=img2img_toggle_defaults,
+ img2img_mask_modes=img2img_mask_modes,
+ img2img_resize_modes=img2img_resize_modes,
+ sample_img2img=sample_img2img,
+ RealESRGAN=RealESRGAN,
+ GFPGAN=GFPGAN,
+ run_GFPGAN=run_GFPGAN,
+ run_RealESRGAN=run_RealESRGAN
+ )
-with gr.Blocks(css=css, 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.Row(elem_id="prompt_row"):
- txt2img_prompt = gr.Textbox(label="Prompt",
- elem_id='prompt_input',
- placeholder="A corgi wearing a top hat as an oil painting.",
- lines=1,
- max_lines=1 if txt2img_defaults['submit_on_enter'] == 'Yes' else 25,
- value=txt2img_defaults['prompt'],
- show_label=False)
-
- with gr.Row(elem_id='body').style(equal_height=False):
- with gr.Column():
- txt2img_height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=txt2img_defaults["height"])
- txt2img_width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=txt2img_defaults["width"])
- txt2img_cfg = gr.Slider(minimum=1.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'])
- 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)', 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'])
- with gr.Column():
- with gr.Group():
- output_txt2img_gallery = gr.Gallery(label="Images", elem_id="gallery_output").style(grid=[4,4])
- gr.Markdown('Selected image actions:')
- output_txt2img_copy_clipboard = gr.Button("Copy to clipboard").click(fn=None, inputs=output_txt2img_gallery, outputs=[], _js=copy_selected_img_js)
- 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/ ESRCan")
- output_txt2img_to_upscale_gobig = gr.Button("Upscale w/ GoBig")
-
- with gr.Row():
- with gr.Group():
- output_txt2img_seed = gr.Number(label='Seed', interactive=False)
- output_txt2img_copy_seed = gr.Button("Copy").click(inputs=output_txt2img_seed, outputs=[], _js='(x) => navigator.clipboard.writeText(x)', fn=None, show_progress=False)
- with gr.Group():
- output_txt2img_select_image = gr.Number(label='Image # and click Copy to copy to img2img', value=1, precision=None)
-
-
-
- with gr.Group():
- output_txt2img_params = gr.Textbox(label="Copy-paste generation parameters", interactive=False)
- output_txt2img_copy_params = gr.Button("Copy").click(inputs=output_txt2img_params, outputs=[], _js='(x) => navigator.clipboard.writeText(x)', fn=None, show_progress=False)
- output_txt2img_stats = gr.HTML(label='Stats')
- with gr.Column():
- txt2img_btn = gr.Button("Generate", elem_id="generate", variant="primary")
- txt2img_steps = gr.Slider(minimum=1, maximum=250, step=1, label="Sampling Steps", value=txt2img_defaults['ddim_steps'])
- txt2img_sampling = gr.Dropdown(label='Sampling method (k_lms is default k-diffusion sampler)', choices=["DDIM", "PLMS", 'k_dpm_2_a', 'k_dpm_2', 'k_euler_a', 'k_euler', 'k_heun', 'k_lms'], value=txt2img_defaults['sampler_name'])
- with gr.Tabs():
- with gr.TabItem('Simple'):
- txt2img_submit_on_enter = gr.Radio(['Yes', 'No'], label="Submit on enter? (no means multiline)", value=txt2img_defaults['submit_on_enter'], interactive=True)
- txt2img_submit_on_enter.change(lambda x: gr.update(max_lines=1 if x == 'Single' 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_realesrgan_model_name = gr.Dropdown(label='RealESRGAN model', choices=['RealESRGAN_x4plus', 'RealESRGAN_x4plus_anime_6B'], value='RealESRGAN_x4plus', visible=RealESRGAN is not None) # TODO: Feels like I shouldnt slot it in here.
- txt2img_ddim_eta = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label="DDIM ETA", value=txt2img_defaults['ddim_eta'], visible=False)
- txt2img_embeddings = gr.File(label = "Embeddings file for textual inversion", visible=hasattr(model, "embedding_manager"))
-
- txt2img_btn.click(
- txt2img,
- [txt2img_prompt, txt2img_steps, txt2img_sampling, txt2img_toggles, txt2img_realesrgan_model_name, txt2img_ddim_eta, txt2img_batch_count, txt2img_batch_size, txt2img_cfg, txt2img_seed, txt2img_height, txt2img_width, txt2img_embeddings],
- [output_txt2img_gallery, output_txt2img_seed, output_txt2img_params, output_txt2img_stats]
- )
- txt2img_prompt.submit(
- txt2img,
- [txt2img_prompt, txt2img_steps, txt2img_sampling, txt2img_toggles, txt2img_realesrgan_model_name, txt2img_ddim_eta, txt2img_batch_count, txt2img_batch_size, txt2img_cfg, txt2img_seed, txt2img_height, txt2img_width, txt2img_embeddings],
- [output_txt2img_gallery, output_txt2img_seed, output_txt2img_params, output_txt2img_stats]
- )
-
- with gr.TabItem("Stable Diffusion 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',
- placeholder="A fantasy landscape, trending on artstation.",
- lines=1,
- 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_editot_btn")
- with gr.Row().style(equal_height=False):
- with gr.Column():
- img2img_image_editor_mode = gr.Radio(choices=["Mask", "Crop"], label="Image Editor Mode", value="Crop")
- img2img_show_help_btn = gr.Button("Show Hints")
- img2img_hide_help_btn = gr.Button("Hide Hints", visible=False)
- img2img_help = gr.Markdown(visible=False, value="")
- with gr.Row():
- img2img_painterro_btn = gr.Button("Advanced Editor")
- img2img_copy_from_painterro_btn = gr.Button(value="Get Image from Advanced Editor")
- img2img_image_editor = gr.Image(value=sample_img2img, source="upload", interactive=True, type="pil", tool="select", elem_id="img2img_editor")
- img2img_image_mask = gr.Image(value=sample_img2img, source="upload", interactive=True, type="pil", tool="sketch", visible=False, elem_id="img2img_mask")
- 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)
- img2img_steps = gr.Slider(minimum=1, maximum=250, step=1, label="Sampling Steps", value=img2img_defaults['ddim_steps'])
- img2img_sampling = gr.Dropdown(label='Sampling method (k_lms is default k-diffusion sampler)', choices=["DDIM", 'k_dpm_2_a', 'k_dpm_2', 'k_euler_a', 'k_euler', 'k_heun', 'k_lms'], value=img2img_defaults['sampler_name'])
- img2img_toggles = gr.CheckboxGroup(label='', choices=img2img_toggles, value=img2img_toggle_defaults, type="index")
- img2img_realesrgan_model_name = gr.Dropdown(label='RealESRGAN model', choices=['RealESRGAN_x4plus', 'RealESRGAN_x4plus_anime_6B'], value='RealESRGAN_x4plus', visible=RealESRGAN is not None) # TODO: Feels like I shouldnt slot it in here.
- img2img_batch_count = gr.Slider(minimum=1, maximum=250, step=1, label='Batch count (how many batches of images to generate)', value=img2img_defaults['n_iter'])
- img2img_batch_size = gr.Slider(minimum=1, maximum=8, step=1, label='Batch size (how many images are in a batch; memory-hungry)', value=img2img_defaults['batch_size'])
- img2img_cfg = gr.Slider(minimum=1.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'])
- img2img_denoising = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Denoising Strength', value=img2img_defaults['denoising_strength'])
- img2img_seed = gr.Textbox(label="Seed (blank to randomize)", lines=1, value=img2img_defaults["seed"])
- img2img_height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=img2img_defaults["height"])
- img2img_width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=img2img_defaults["width"])
- img2img_resize = gr.Radio(label="Resize mode", choices=["Just resize", "Crop and resize", "Resize and fill"], type="index", value=img2img_resize_modes[img2img_defaults['resize_mode']])
- img2img_embeddings = gr.File(label = "Embeddings file for textual inversion", visible=hasattr(model, "embedding_manager"))
-
- with gr.Column():
- with gr.Group():
- output_img2img_gallery = gr.Gallery(label="Generated Images", elem_id="gallery_output").style(grid=[4,4])
- output_img2img_copy_to_input_btn = gr.Button("⬅️ Copy selected image to input")
- if RealESRGAN is not None:
- output_txt2img_copy_to_gobig_input_btn = gr.Button("Upscale w/ goBig input")
- gr.Markdown("Clear the input image before copying your output to your input. It may take some time to load the image.")
-
- output_img2img_seed = gr.Number(label='Seed')
- output_img2img_params = gr.Textbox(label="Copy-paste generation parameters")
- output_img2img_stats = gr.HTML(label='Stats')
-
- img2img_image_editor_mode.change(
- change_image_editor_mode,
- [img2img_image_editor_mode, img2img_image_editor, img2img_resize, img2img_width, img2img_height],
- [img2img_image_editor, img2img_image_mask, img2img_btn_editor, img2img_btn_mask, img2img_painterro_btn, img2img_copy_from_painterro_btn, img2img_mask, img2img_mask_blur_strength]
- )
-
- img2img_image_editor.edit(
- update_image_mask,
- [img2img_image_editor, img2img_resize, img2img_width, img2img_height],
- img2img_image_mask
- )
-
- img2img_show_help_btn.click(
- show_help,
- None,
- [img2img_show_help_btn, img2img_hide_help_btn, img2img_help]
- )
-
- img2img_hide_help_btn.click(
- hide_help,
- None,
- [img2img_show_help_btn, img2img_hide_help_btn, img2img_help]
- )
-
- output_img2img_copy_to_input_btn.click(
- copy_img_to_input,
- [output_img2img_gallery],
- [img2img_image_editor, img2img_image_mask, tabs],
- _js=return_selected_img_js
- )
-
- output_txt2img_copy_to_input_btn.click(
- copy_img_to_input,
- [output_txt2img_gallery],
- [img2img_image_editor, img2img_image_mask, tabs],
- _js=return_selected_img_js
- )
-
- img2img_btn_mask.click(
- img2img,
- [img2img_prompt, img2img_image_editor_mode, img2img_image_mask, img2img_mask, img2img_mask_blur_strength, img2img_steps, img2img_sampling, img2img_toggles, img2img_realesrgan_model_name, img2img_batch_count, img2img_batch_size, img2img_cfg, 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,
- [img2img_prompt, img2img_image_editor_mode, img2img_image_editor, img2img_mask, img2img_mask_blur_strength, img2img_steps, img2img_sampling, img2img_toggles, img2img_realesrgan_model_name, img2img_batch_count, img2img_batch_size, img2img_cfg, 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_painterro_btn.click(None, [img2img_image_editor], None, _js="""(img) => {
- try {
- Painterro({
- hiddenTools: ['arrow'],
- saveHandler: function (image, done) {
- localStorage.setItem('painterro-image', image.asDataURL());
- done(true);
- },
- }).show(Array.isArray(img) ? img[0] : img);
- } catch(e) {
- const script = document.createElement('script');
- script.src = 'https://unpkg.com/painterro@1.2.78/build/painterro.min.js';
- document.head.appendChild(script);
- const style = document.createElement('style');
- style.appendChild(document.createTextNode('.ptro-holder-wrapper { z-index: 9999 !important; }'));
- document.head.appendChild(style);
- }
- return [];
- }""")
-
- img2img_copy_from_painterro_btn.click(None, None, [img2img_image_editor, img2img_image_mask], _js="""() => {
- const image = localStorage.getItem('painterro-image')
- return [image, image];
- }""")
-
- if GFPGAN is not None:
- gfpgan_defaults = {
- 'strength': 100,
- }
-
- if 'gfpgan' in user_defaults:
- gfpgan_defaults.update(user_defaults['gfpgan'])
-
- with gr.TabItem("GFPGAN", id='cfpgan_tab'):
- gr.Markdown("Fix faces on images")
- with gr.Row():
- with gr.Column():
- gfpgan_source = gr.Image(label="Source", source="upload", interactive=True, type="pil")
- gfpgan_strength = gr.Slider(minimum=0.0, maximum=1.0, step=0.001, label="Effect strength", value=gfpgan_defaults['strength'])
- gfpgan_btn = gr.Button("Generate", variant="primary")
- with gr.Column():
- gfpgan_output = gr.Image(label="Output")
- gfpgan_btn.click(
- run_GFPGAN,
- [gfpgan_source, gfpgan_strength],
- [gfpgan_output]
- )
- if RealESRGAN is not None:
- with gr.TabItem("RealESRGAN", id='realesrgan_tab'):
- gr.Markdown("Upscale images")
- with gr.Row():
- with gr.Column():
- realesrgan_source = gr.Image(label="Source", source="upload", interactive=True, type="pil")
- realesrgan_model_name = gr.Dropdown(label='RealESRGAN model', choices=['RealESRGAN_x4plus', 'RealESRGAN_x4plus_anime_6B'], value='RealESRGAN_x4plus')
- realesrgan_btn = gr.Button("Generate")
- with gr.Column():
- realesrgan_output = gr.Image(label="Output")
- realesrgan_btn.click(
- run_RealESRGAN,
- [realesrgan_source, realesrgan_model_name],
- [realesrgan_output]
- )
- output_txt2img_to_upscale_esrgan.click(
- copy_img_to_upscale_esrgan,
- output_txt2img_gallery,
- [realesrgan_source, tabs],
- _js=return_selected_img_js)
- with gr.TabItem("goBIG", id='gobig_tab'):
- gr.Markdown("Upscale and detail images")
- with gr.Row():
- with gr.Column():
- realesrganGoBig_source = gr.Image(source="upload", interactive=True, type="pil", tool="select")
- realesrganGoBig_model_name = gr.Dropdown(label='RealESRGAN model', choices=['RealESRGAN_x4plus', 'RealESRGAN_x4plus_anime_6B'], value='RealESRGAN_x4plus')
- realesrganGoBig_btn = gr.Button("Generate")
- with gr.Column():
- realesrganGoBig_output = gr.Image(label="Output")
- realesrganGoBig_btn.click(
- run_goBIG,
- [realesrganGoBig_source, realesrganGoBig_model_name],
- [realesrganGoBig_output]
- )
- output_txt2img_to_upscale_gobig.click(
- copy_img_to_upscale_gobig,
- output_txt2img_gallery,
- [realesrganGoBig_source, tabs],
- _js=return_selected_img_js)
-
- output_txt2img_copy_to_gobig_input_btn.click(
- copy_img_to_upscale_gobig,
- output_txt2img_gallery,
- [realesrganGoBig_source, tabs],
- _js=return_selected_img_js
- )
-
-
-demo.queue()
+# demo.queue()
demo.launch(share=False, debug=True)