diff --git a/javascript/hints.js b/javascript/hints.js index 9d3eecf3..f1ff645f 100644 --- a/javascript/hints.js +++ b/javascript/hints.js @@ -13,6 +13,8 @@ titles = { "Seed": "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result", "\u{1f3b2}\ufe0f": "Set seed to -1, which will cause a new random number to be used every time", "\u267b\ufe0f": "Reuse seed from last generation, mostly useful if it was randomed", + "\u{1f3a8}": "Add a random artist to the prompt.", + "\u2199\ufe0f": "Read generation parameters from prompt into user interface.", "Inpaint a part of image": "Draw a mask over an image, and the script will regenerate the masked area with content according to prompt", "SD upscale": "Upscale image normally, split result into tiles, improve each tile using img2img, merge whole image back", @@ -48,8 +50,6 @@ titles = { "Tiling": "Produce an image that can be tiled.", "Tile overlap": "For SD upscale, how much overlap in pixels should there be between tiles. Tiles overlap so that when they are merged back into one picture, there is no clearly visible seam.", - "Roll": "Add a random artist to the prompt.", - "Variation seed": "Seed of a different picture to be mixed into the generation.", "Variation strength": "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).", "Resize seed from height": "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution", diff --git a/javascript/ui.js b/javascript/ui.js index 22ef5508..076e9436 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -25,13 +25,57 @@ function extract_image_from_gallery(gallery){ return gallery[index]; } -function extract_image_from_gallery_img2img(gallery){ +function args_to_array(args){ + res = [] + for(var i=0;i

{message}

" - return '', '', info + return '', geninfo, info diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py new file mode 100644 index 00000000..97f14ad1 --- /dev/null +++ b/modules/generation_parameters_copypaste.py @@ -0,0 +1,88 @@ +from collections import namedtuple +import re +import gradio as gr + +re_param = re.compile(r"\s*([\w ]+):\s*([^,]+)(?:,|$)") +re_imagesize = re.compile(r"^(\d+)x(\d+)$") + + +def parse_generation_parameters(x: str): + """parses generation parameters string, the one you see in text field under the picture in UI: +``` +girl with an artist's beret, determined, blue eyes, desert scene, computer monitors, heavy makeup, by Alphonse Mucha and Charlie Bowater, ((eyeshadow)), (coquettish), detailed, intricate +Negative prompt: ugly, fat, obese, chubby, (((deformed))), [blurry], bad anatomy, disfigured, poorly drawn face, mutation, mutated, (extra_limb), (ugly), (poorly drawn hands), messy drawing +Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model hash: 45dee52b +``` + + returns a dict with field values + """ + + res = {} + + prompt = "" + negative_prompt = "" + + done_with_prompt = False + + *lines, lastline = x.strip().split("\n") + for i, line in enumerate(lines): + line = line.strip() + if line.startswith("Negative prompt:"): + done_with_prompt = True + line = line[16:].strip() + + if done_with_prompt: + negative_prompt += line + else: + prompt += line + + if len(prompt) > 0: + res["Prompt"] = prompt + + if len(negative_prompt) > 0: + res["Negative prompt"] = negative_prompt + + for k, v in re_param.findall(lastline): + m = re_imagesize.match(v) + if m is not None: + res[k+"-1"] = m.group(1) + res[k+"-2"] = m.group(2) + else: + res[k] = v + + return res + + +def connect_paste(button, d, input_comp, js=None): + items = [] + outputs = [] + + def paste_func(prompt): + params = parse_generation_parameters(prompt) + res = [] + + for key, output in zip(items, outputs): + v = params.get(key, None) + + if v is None: + res.append(gr.update()) + else: + try: + valtype = type(output.value) + val = valtype(v) + res.append(gr.update(value=val)) + except Exception: + res.append(gr.update()) + + return res + + for k, v in d.items(): + items.append(k) + outputs.append(v) + + button.click( + fn=paste_func, + _js=js, + inputs=[input_comp], + outputs=outputs, + ) diff --git a/modules/ui.py b/modules/ui.py index 35d80d3d..1458efa3 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -27,6 +27,7 @@ import modules.scripts import modules.gfpgan_model import modules.codeformer_model import modules.styles +import modules.generation_parameters_copypaste # this is a fix for Windows users. Without it, javascript files will be served with text/html content-type and the bowser will not show any UI mimetypes.init() @@ -57,6 +58,8 @@ css_hide_progressbar = """ # Important that they exactly match script.js for tooltip to work. random_symbol = '\U0001f3b2\ufe0f' # 🎲️ reuse_symbol = '\u267b\ufe0f' # ♻️ +art_symbol = '\U0001f3a8' # 🎨 +paste_symbol = '\u2199\ufe0f' # ↙ def plaintext_to_html(text): @@ -336,8 +339,10 @@ def create_toprow(is_img2img): with gr.Column(scale=80): with gr.Row(): prompt = gr.Textbox(label="Prompt", elem_id="prompt", show_label=False, placeholder="Prompt", lines=2) - roll = gr.Button('Roll', elem_id="roll", visible=len(shared.artist_db.artists) > 0) + with gr.Column(scale=1, elem_id="roll_col"): + roll = gr.Button(value=art_symbol, elem_id="roll", visible=len(shared.artist_db.artists) > 0) + paste = gr.Button(value=paste_symbol, elem_id="paste") with gr.Column(scale=10, elem_id="style_pos_col"): prompt_style = gr.Dropdown(label="Style 1", elem_id=f"{id_part}_style_index", choices=[k for k, v in shared.prompt_styles.styles.items()], value=next(iter(shared.prompt_styles.styles.keys())), visible=len(shared.prompt_styles.styles) > 1) @@ -368,7 +373,7 @@ def create_toprow(is_img2img): prompt_style_apply = gr.Button('Apply style', elem_id="style_apply") save_style = gr.Button('Create style', elem_id="style_create") - return prompt, roll, prompt_style, negative_prompt, prompt_style2, submit, interrogate, prompt_style_apply, save_style + return prompt, roll, prompt_style, negative_prompt, prompt_style2, submit, interrogate, prompt_style_apply, save_style, paste def setup_progressbar(progressbar, preview, id_part): @@ -391,7 +396,7 @@ def setup_progressbar(progressbar, preview, id_part): def create_ui(txt2img, img2img, run_extras, run_pnginfo): with gr.Blocks(analytics_enabled=False) as txt2img_interface: - txt2img_prompt, roll, txt2img_prompt_style, txt2img_negative_prompt, txt2img_prompt_style2, submit, _, txt2img_prompt_style_apply, txt2img_save_style = create_toprow(is_img2img=False) + txt2img_prompt, roll, txt2img_prompt_style, txt2img_negative_prompt, txt2img_prompt_style2, submit, _, txt2img_prompt_style_apply, txt2img_save_style, paste = create_toprow(is_img2img=False) dummy_component = gr.Label(visible=False) with gr.Row(elem_id='txt2img_progress_row'): @@ -517,8 +522,27 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo): ] ) + txt2img_paste_fields = { + "Prompt": txt2img_prompt, + "Negative prompt": txt2img_negative_prompt, + "Steps": steps, + "Sampler": sampler_index, + "Face restoration": restore_faces, + "CFG scale": cfg_scale, + "Seed": seed, + "Size-1": width, + "Size-2": height, + "Batch size": batch_size, + "Variation seed": subseed, + "Variation seed strength": subseed_strength, + "Seed resize from-1": seed_resize_from_w, + "Seed resize from-2": seed_resize_from_h, + "Denoising strength": denoising_strength, + } + modules.generation_parameters_copypaste.connect_paste(paste, txt2img_paste_fields, txt2img_prompt) + with gr.Blocks(analytics_enabled=False) as img2img_interface: - img2img_prompt, roll, img2img_prompt_style, img2img_negative_prompt, img2img_prompt_style2, submit, img2img_interrogate, img2img_prompt_style_apply, img2img_save_style = create_toprow(is_img2img=True) + img2img_prompt, roll, img2img_prompt_style, img2img_negative_prompt, img2img_prompt_style2, submit, img2img_interrogate, img2img_prompt_style_apply, img2img_save_style, paste = create_toprow(is_img2img=True) with gr.Row(elem_id='img2img_progress_row'): with gr.Column(scale=1): @@ -533,10 +557,10 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo): with gr.Column(variant='panel'): with gr.Tabs(elem_id="mode_img2img") as tabs_img2img_mode: - with gr.TabItem('img2img'): + with gr.TabItem('img2img', id='img2img'): init_img = gr.Image(label="Image for img2img", show_label=False, source="upload", interactive=True, type="pil") - with gr.TabItem('Inpaint'): + with gr.TabItem('Inpaint', id='inpaint'): init_img_with_mask = gr.Image(label="Image for inpainting with mask", show_label=False, elem_id="img2maskimg", source="upload", interactive=True, type="pil", tool="sketch", image_mode="RGBA") init_img_inpaint = gr.Image(label="Image for img2img", show_label=False, source="upload", interactive=True, type="pil", visible=False) @@ -554,7 +578,7 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo): inpaint_full_res = gr.Checkbox(label='Inpaint at full resolution', value=False) inpaint_full_res_padding = gr.Slider(label='Inpaint at full resolution padding, pixels', minimum=0, maximum=256, step=4, value=32) - with gr.TabItem('Batch img2img'): + with gr.TabItem('Batch img2img', id='batch'): gr.HTML("

Process images in a directory on the same machine where the server is running.

") img2img_batch_input_dir = gr.Textbox(label="Input directory") img2img_batch_output_dir = gr.Textbox(label="Output directory") @@ -717,12 +741,31 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo): outputs=[prompt, negative_prompt, style1, style2], ) + img2img_paste_fields = { + "Prompt": img2img_prompt, + "Negative prompt": img2img_negative_prompt, + "Steps": steps, + "Sampler": sampler_index, + "Face restoration": restore_faces, + "CFG scale": cfg_scale, + "Seed": seed, + "Size-1": width, + "Size-2": height, + "Batch size": batch_size, + "Variation seed": subseed, + "Variation seed strength": subseed_strength, + "Seed resize from-1": seed_resize_from_w, + "Seed resize from-2": seed_resize_from_h, + "Denoising strength": denoising_strength, + } + modules.generation_parameters_copypaste.connect_paste(paste, img2img_paste_fields, img2img_prompt) + with gr.Blocks(analytics_enabled=False) as extras_interface: with gr.Row().style(equal_height=False): with gr.Column(variant='panel'): with gr.Tabs(elem_id="mode_extras"): with gr.TabItem('Single Image'): - image = gr.Image(label="Source", source="upload", interactive=True, type="pil") + extras_image = gr.Image(label="Source", source="upload", interactive=True, type="pil") with gr.TabItem('Batch Process'): image_batch = gr.File(label="Batch Process", file_count="multiple", interactive=True, type="file") @@ -757,7 +800,7 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo): _js="get_extras_tab_index", inputs=[ dummy_component, - image, + extras_image, image_batch, gfpgan_visibility, codeformer_visibility, @@ -788,20 +831,25 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo): outputs=[init_img_with_mask], ) - pnginfo_interface = gr.Interface( - wrap_gradio_call(run_pnginfo), - inputs=[ - gr.Image(elem_id="pnginfo_image", label="Source", source="upload", interactive=True, type="pil"), - ], - outputs=[ - gr.HTML(), - gr.HTML(), - gr.HTML(), - ], - allow_flagging="never", - analytics_enabled=False, - live=True, - ) + with gr.Blocks(analytics_enabled=False) as pnginfo_interface: + with gr.Row().style(equal_height=False): + with gr.Column(variant='panel'): + image = gr.Image(elem_id="pnginfo_image", label="Source", source="upload", interactive=True, type="pil") + + with gr.Column(variant='panel'): + html = gr.HTML() + generation_info = gr.Textbox(visible=False) + html2 = gr.HTML() + + with gr.Row(): + pnginfo_send_to_txt2img = gr.Button('Send to txt2img') + pnginfo_send_to_img2img = gr.Button('Send to img2img') + + image.change( + fn=wrap_gradio_call(run_pnginfo), + inputs=[image], + outputs=[html, generation_info, html2], + ) def create_setting_component(key): def fun(): @@ -936,29 +984,29 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo): ) send_to_img2img.click( - fn=lambda x: image_from_url_text(x), + fn=lambda x: (image_from_url_text(x)), _js="extract_image_from_gallery_img2img", inputs=[txt2img_gallery], outputs=[init_img], ) send_to_inpaint.click( - fn=lambda x: image_from_url_text(x), - _js="extract_image_from_gallery_img2img", + fn=lambda x: (image_from_url_text(x)), + _js="extract_image_from_gallery_inpaint", inputs=[txt2img_gallery], outputs=[init_img_with_mask], ) img2img_send_to_img2img.click( fn=lambda x: image_from_url_text(x), - _js="extract_image_from_gallery", + _js="extract_image_from_gallery_img2img", inputs=[img2img_gallery], outputs=[init_img], ) img2img_send_to_inpaint.click( fn=lambda x: image_from_url_text(x), - _js="extract_image_from_gallery", + _js="extract_image_from_gallery_inpaint", inputs=[img2img_gallery], outputs=[init_img_with_mask], ) @@ -967,16 +1015,19 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo): fn=lambda x: image_from_url_text(x), _js="extract_image_from_gallery_extras", inputs=[txt2img_gallery], - outputs=[image], + outputs=[extras_image], ) img2img_send_to_extras.click( fn=lambda x: image_from_url_text(x), _js="extract_image_from_gallery_extras", inputs=[img2img_gallery], - outputs=[image], + outputs=[extras_image], ) + modules.generation_parameters_copypaste.connect_paste(pnginfo_send_to_txt2img, txt2img_paste_fields, generation_info, 'switch_to_txt2img') + modules.generation_parameters_copypaste.connect_paste(pnginfo_send_to_img2img, img2img_paste_fields, generation_info, 'switch_to_img2img_img2img') + ui_config_file = cmd_opts.ui_config_file ui_settings = {} settings_count = len(ui_settings) diff --git a/style.css b/style.css index 0746a659..e4fe5453 100644 --- a/style.css +++ b/style.css @@ -74,10 +74,21 @@ height: 100%; } -#roll{ - min-width: 1em; - max-width: 4em; - margin: 0.5em; +#roll_col{ + min-width: unset !important; + flex-grow: 0 !important; + padding: 0.4em 0; +} + +#roll, #paste{ + min-width: 2em; + min-height: 2em; + max-width: 2em; + max-height: 2em; + flex-grow: 0; + padding-left: 0.25em; + padding-right: 0.25em; + margin: 0.1em 0; } #style_apply, #style_create, #interrogate{