From 185ab3cbd1330f6dbc87e5dd72cf115b3edfd153 Mon Sep 17 00:00:00 2001 From: cat Date: Sat, 26 Nov 2022 20:07:51 +0500 Subject: [PATCH 1/5] Atomically rename saved image to avoid race condition with other processes. --- modules/images.py | 59 ++++++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/modules/images.py b/modules/images.py index b968d6a6..51b1ff9c 100644 --- a/modules/images.py +++ b/modules/images.py @@ -500,30 +500,39 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i image = params.image fullfn = params.filename info = params.pnginfo.get(pnginfo_section_name, None) + + def _atomically_save_image(image_to_save, filename_without_extension, extension): + # save image with .tmp extension to avoid race condition when another process detects new image in the directory + temp_file_path = filename_without_extension + ".tmp" + image_format = Image.registered_extensions()[extension] + + if extension.lower() == '.png': + pnginfo_data = PngImagePlugin.PngInfo() + if opts.enable_pnginfo: + for k, v in params.pnginfo.items(): + pnginfo_data.add_text(k, str(v)) + + image_to_save.save(temp_file_path, format=image_format, quality=opts.jpeg_quality, pnginfo=pnginfo_data) + + elif extension.lower() in (".jpg", ".jpeg", ".webp"): + image_to_save.save(temp_file_path, format=image_format, quality=opts.jpeg_quality) + + if opts.enable_pnginfo and info is not None: + exif_bytes = piexif.dump({ + "Exif": { + piexif.ExifIFD.UserComment: piexif.helper.UserComment.dump(info or "", encoding="unicode") + }, + }) + + piexif.insert(exif_bytes, temp_file_path) + else: + image_to_save.save(temp_file_path, format=image_format, quality=opts.jpeg_quality) + + # atomically rename the file with correct extension + os.replace(temp_file_path, filename_without_extension + extension) + fullfn_without_extension, extension = os.path.splitext(params.filename) - - def exif_bytes(): - return piexif.dump({ - "Exif": { - piexif.ExifIFD.UserComment: piexif.helper.UserComment.dump(info or "", encoding="unicode") - }, - }) - - if extension.lower() == '.png': - pnginfo_data = PngImagePlugin.PngInfo() - if opts.enable_pnginfo: - for k, v in params.pnginfo.items(): - pnginfo_data.add_text(k, str(v)) - - image.save(fullfn, quality=opts.jpeg_quality, pnginfo=pnginfo_data) - - elif extension.lower() in (".jpg", ".jpeg", ".webp"): - image.save(fullfn, quality=opts.jpeg_quality) - - if opts.enable_pnginfo and info is not None: - piexif.insert(exif_bytes(), fullfn) - else: - image.save(fullfn, quality=opts.jpeg_quality) + _atomically_save_image(image, fullfn_without_extension, extension) image.already_saved_as = fullfn @@ -537,9 +546,7 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i elif oversize: image = image.resize((image.width * target_side_length // image.height, target_side_length), LANCZOS) - image.save(fullfn_without_extension + ".jpg", quality=opts.jpeg_quality) - if opts.enable_pnginfo and info is not None: - piexif.insert(exif_bytes(), fullfn_without_extension + ".jpg") + _atomically_save_image(image, fullfn_without_extension, ".jpg") if opts.save_txt and info is not None: txt_fullfn = f"{fullfn_without_extension}.txt" From 0202547696a2c8cbe91efbeb30a7b2c689fbe276 Mon Sep 17 00:00:00 2001 From: kaneda2004 Date: Mon, 28 Nov 2022 12:24:53 -0800 Subject: [PATCH 2/5] Update SD Upscaler to include user selectable Scale Factor --- scripts/sd_upscale.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/scripts/sd_upscale.py b/scripts/sd_upscale.py index 01074291..4370abe1 100644 --- a/scripts/sd_upscale.py +++ b/scripts/sd_upscale.py @@ -17,13 +17,16 @@ class Script(scripts.Script): return is_img2img def ui(self, is_img2img): - info = gr.HTML("

Will upscale the image to twice the dimensions; use width and height sliders to set tile size

") + info = gr.HTML( + "

Will upscale the image by the selected scale factor; use width and height sliders to set tile size

") overlap = gr.Slider(minimum=0, maximum=256, step=16, label='Tile overlap', value=64) - upscaler_index = gr.Radio(label='Upscaler', choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name, type="index") + scale_factor = gr.Slider(minimum=0, maximum=4, step=1, label='Scale Factor', value=2) + upscaler_index = gr.Radio(label='Upscaler', choices=[x.name for x in shared.sd_upscalers], + value=shared.sd_upscalers[0].name, type="index") - return [info, overlap, upscaler_index] + return [info, overlap, upscaler_index, scale_factor] - def run(self, p, _, overlap, upscaler_index): + def run(self, p, _, overlap, upscaler_index, scale_factor): processing.fix_seed(p) upscaler = shared.sd_upscalers[upscaler_index] @@ -34,9 +37,9 @@ class Script(scripts.Script): seed = p.seed init_img = p.init_images[0] - - if(upscaler.name != "None"): - img = upscaler.scaler.upscale(init_img, 2, upscaler.data_path) + + if (upscaler.name != "None"): + img = upscaler.scaler.upscale(init_img, scale_factor, upscaler.data_path) else: img = init_img @@ -59,7 +62,8 @@ class Script(scripts.Script): batch_count = math.ceil(len(work) / batch_size) state.job_count = batch_count * upscale_count - print(f"SD upscaling will process a total of {len(work)} images tiled as {len(grid.tiles[0][2])}x{len(grid.tiles)} per upscale in a total of {state.job_count} batches.") + print( + f"SD upscaling will process a total of {len(work)} images tiled as {len(grid.tiles[0][2])}x{len(grid.tiles)} per upscale in a total of {state.job_count} batches.") result_images = [] for n in range(upscale_count): @@ -69,7 +73,7 @@ class Script(scripts.Script): work_results = [] for i in range(batch_count): p.batch_size = batch_size - p.init_images = work[i*batch_size:(i+1)*batch_size] + p.init_images = work[i * batch_size:(i + 1) * batch_size] state.job = f"Batch {i + 1 + n * batch_count} out of {state.job_count}" processed = processing.process_images(p) @@ -83,15 +87,17 @@ class Script(scripts.Script): image_index = 0 for y, h, row in grid.tiles: for tiledata in row: - tiledata[2] = work_results[image_index] if image_index < len(work_results) else Image.new("RGB", (p.width, p.height)) + tiledata[2] = work_results[image_index] if image_index < len(work_results) else Image.new("RGB", ( + p.width, p.height)) image_index += 1 combined_image = images.combine_grid(grid) result_images.append(combined_image) if opts.samples_save: - images.save_image(combined_image, p.outpath_samples, "", start_seed, p.prompt, opts.samples_format, info=initial_info, p=p) + images.save_image(combined_image, p.outpath_samples, "", start_seed, p.prompt, opts.samples_format, + info=initial_info, p=p) processed = Processed(p, result_images, seed, initial_info) - return processed + return processed \ No newline at end of file From 91226829f8cef93fd3ec5ae979ec2e694f741a27 Mon Sep 17 00:00:00 2001 From: kaneda2004 Date: Mon, 28 Nov 2022 12:28:22 -0800 Subject: [PATCH 3/5] Update SD Upscaler to include user selectable Scale Factor --- scripts/sd_upscale.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/scripts/sd_upscale.py b/scripts/sd_upscale.py index 4370abe1..dff54f0a 100644 --- a/scripts/sd_upscale.py +++ b/scripts/sd_upscale.py @@ -17,12 +17,10 @@ class Script(scripts.Script): return is_img2img def ui(self, is_img2img): - info = gr.HTML( - "

Will upscale the image by the selected scale factor; use width and height sliders to set tile size

") + info = gr.HTML("

Will upscale the image by the selected scale factor; use width and height sliders to set tile size

") overlap = gr.Slider(minimum=0, maximum=256, step=16, label='Tile overlap', value=64) scale_factor = gr.Slider(minimum=0, maximum=4, step=1, label='Scale Factor', value=2) - upscaler_index = gr.Radio(label='Upscaler', choices=[x.name for x in shared.sd_upscalers], - value=shared.sd_upscalers[0].name, type="index") + upscaler_index = gr.Radio(label='Upscaler', choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name, type="index") return [info, overlap, upscaler_index, scale_factor] @@ -62,8 +60,7 @@ class Script(scripts.Script): batch_count = math.ceil(len(work) / batch_size) state.job_count = batch_count * upscale_count - print( - f"SD upscaling will process a total of {len(work)} images tiled as {len(grid.tiles[0][2])}x{len(grid.tiles)} per upscale in a total of {state.job_count} batches.") + print(f"SD upscaling will process a total of {len(work)} images tiled as {len(grid.tiles[0][2])}x{len(grid.tiles)} per upscale in a total of {state.job_count} batches.") result_images = [] for n in range(upscale_count): @@ -87,17 +84,15 @@ class Script(scripts.Script): image_index = 0 for y, h, row in grid.tiles: for tiledata in row: - tiledata[2] = work_results[image_index] if image_index < len(work_results) else Image.new("RGB", ( - p.width, p.height)) + tiledata[2] = work_results[image_index] if image_index < len(work_results) else Image.new("RGB", (p.width, p.height)) image_index += 1 combined_image = images.combine_grid(grid) result_images.append(combined_image) if opts.samples_save: - images.save_image(combined_image, p.outpath_samples, "", start_seed, p.prompt, opts.samples_format, - info=initial_info, p=p) + images.save_image(combined_image, p.outpath_samples, "", start_seed, p.prompt, opts.samples_format, info=initial_info, p=p) processed = Processed(p, result_images, seed, initial_info) - return processed \ No newline at end of file + return processed From 950d9c739ea523652c653e32c09a37c64074c38f Mon Sep 17 00:00:00 2001 From: kaneda2004 Date: Mon, 28 Nov 2022 12:29:49 -0800 Subject: [PATCH 4/5] Update SD Upscaler to include user selectable Scale Factor --- scripts/sd_upscale.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/sd_upscale.py b/scripts/sd_upscale.py index dff54f0a..28bd96b3 100644 --- a/scripts/sd_upscale.py +++ b/scripts/sd_upscale.py @@ -19,7 +19,7 @@ class Script(scripts.Script): def ui(self, is_img2img): info = gr.HTML("

Will upscale the image by the selected scale factor; use width and height sliders to set tile size

") overlap = gr.Slider(minimum=0, maximum=256, step=16, label='Tile overlap', value=64) - scale_factor = gr.Slider(minimum=0, maximum=4, step=1, label='Scale Factor', value=2) + scale_factor = gr.Slider(minimum=1, maximum=4, step=1, label='Scale Factor', value=2) upscaler_index = gr.Radio(label='Upscaler', choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name, type="index") return [info, overlap, upscaler_index, scale_factor] From d06592267c745b4732026c4e0c499c9a4b3900a1 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 10 Dec 2022 13:46:18 +0300 Subject: [PATCH 5/5] use less javascript for this non-js-only implementation of the clear prompt button. --- javascript/ui.js | 41 +++++------------------------------------ modules/shared.py | 1 - modules/ui.py | 23 +++++++++++------------ 3 files changed, 16 insertions(+), 49 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index 951e8381..2cb280e5 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -131,44 +131,13 @@ function ask_for_style_name(_, prompt_text, negative_prompt_text) { return [name_, prompt_text, negative_prompt_text] } -// returns css id for currently selected tab in ui -function selected_tab_id() { - tabs = gradioApp().querySelectorAll('#tabs div.tabitem') - - for(var tab = 0; tab < tabs.length; tab++) { - if (tabs[tab].style.display != "none") return tabs[tab].id - +function confirm_clear_prompt(prompt, negative_prompt) { + if(confirm("Delete prompt?")) { + prompt = "" + negative_prompt = "" } -} - -function clear_prompt() { - -if(confirm("Delete prompt?")) { - - let pos_prompt = gradioApp().querySelector("#txt2img_prompt > label > textarea"); - let neg_prompt = gradioApp().querySelector("#txt2img_neg_prompt > label > textarea"); - - if (selected_tab_id() == "tab_txt2img") { - } else { - pos_prompt = gradioApp().querySelector("#img2img_prompt > label > textarea"); - neg_prompt = gradioApp().querySelector("#img2img_neg_prompt > label > textarea"); - } - - pos_prompt.value = "" - neg_prompt.value = "" - - //update prompt values on server-side - pos_prompt.dispatchEvent( - new Event("input", {bubbles: true}) - ) - neg_prompt.dispatchEvent( - new Event("input", {bubbles: true}) - ) - - return true -} else return false - + return [prompt, negative_prompt] } diff --git a/modules/shared.py b/modules/shared.py index 4223c017..44922c91 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -401,7 +401,6 @@ options_templates.update(options_section(('ui', "User interface"), { "js_modal_lightbox": OptionInfo(True, "Enable full page image viewer"), "js_modal_lightbox_initially_zoomed": OptionInfo(True, "Show images zoomed in by default in full page image viewer"), "show_progress_in_title": OptionInfo(True, "Show generation progress in window title."), - "clear_prompt_visible": OptionInfo(True, "Show clear prompt button"), 'quicksettings': OptionInfo("sd_model_checkpoint", "Quicksettings list"), 'localization': OptionInfo("None", "Localization (requires restart)", gr.Dropdown, lambda: {"choices": ["None"] + list(localization.localizations.keys())}, refresh=lambda: localization.list_localizations(cmd_opts.localizations_dir)), })) diff --git a/modules/ui.py b/modules/ui.py index c0a7ca8b..28481e33 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -403,16 +403,17 @@ def create_toprow(is_img2img): paste = gr.Button(value=paste_symbol, elem_id="paste") save_style = gr.Button(value=save_style_symbol, elem_id="style_create") prompt_style_apply = gr.Button(value=apply_style_symbol, elem_id="style_apply") - - clear_prompt_button = gr.Button( - value=clear_prompt_symbol, - elem_id="clear_prompt", - visible=opts.clear_prompt_visible - ) - + clear_prompt_button = gr.Button(value=clear_prompt_symbol, elem_id=f"{id_part}_clear_prompt") token_counter = gr.HTML(value="", elem_id=f"{id_part}_token_counter") token_button = gr.Button(visible=False, elem_id=f"{id_part}_token_button") + clear_prompt_button.click( + fn=lambda *x: x, + _js="confirm_clear_prompt", + inputs=[prompt, negative_prompt], + outputs=[prompt, negative_prompt], + ) + button_interrogate = None button_deepbooru = None if is_img2img: @@ -447,7 +448,7 @@ def create_toprow(is_img2img): prompt_style2 = gr.Dropdown(label="Style 2", elem_id=f"{id_part}_style2_index", choices=[k for k, v in shared.prompt_styles.styles.items()], value=next(iter(shared.prompt_styles.styles.keys()))) prompt_style2.save_to_config = True - return prompt, roll, prompt_style, negative_prompt, prompt_style2, submit, button_interrogate, button_deepbooru, prompt_style_apply, save_style, paste, token_counter, token_button, clear_prompt_button + return prompt, roll, prompt_style, negative_prompt, prompt_style2, submit, button_interrogate, button_deepbooru, prompt_style_apply, save_style, paste, token_counter, token_button def setup_progressbar(progressbar, preview, id_part, textinfo=None): @@ -634,7 +635,7 @@ def create_ui(): modules.scripts.scripts_txt2img.initialize_scripts(is_img2img=False) 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, txt2img_paste, token_counter, token_button, clear_prompt_button = 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, txt2img_paste, token_counter, token_button = create_toprow(is_img2img=False) dummy_component = gr.Label(visible=False) txt_prompt_img = gr.File(label="", elem_id="txt2img_prompt_image", file_count="single", type="bytes", visible=False) @@ -686,7 +687,6 @@ def create_ui(): connect_reuse_seed(seed, reuse_seed, generation_info, dummy_component, is_subseed=False) connect_reuse_seed(subseed, reuse_subseed, generation_info, dummy_component, is_subseed=True) - connect_clear_prompt(clear_prompt_button) txt2img_args = dict( fn=wrap_gradio_gpu_call(modules.txt2img.txt2img), @@ -793,7 +793,7 @@ def create_ui(): modules.scripts.scripts_img2img.initialize_scripts(is_img2img=True) 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_deepbooru, img2img_prompt_style_apply, img2img_save_style, img2img_paste,token_counter, token_button, clear_prompt_button = create_toprow(is_img2img=True) + img2img_prompt, roll, img2img_prompt_style, img2img_negative_prompt, img2img_prompt_style2, submit, img2img_interrogate, img2img_deepbooru, img2img_prompt_style_apply, img2img_save_style, img2img_paste,token_counter, token_button = create_toprow(is_img2img=True) with gr.Row(elem_id='img2img_progress_row'): @@ -884,7 +884,6 @@ def create_ui(): connect_reuse_seed(seed, reuse_seed, generation_info, dummy_component, is_subseed=False) connect_reuse_seed(subseed, reuse_subseed, generation_info, dummy_component, is_subseed=True) - connect_clear_prompt(clear_prompt_button) img2img_prompt_img.change( fn=modules.images.image_data,