From 23b63098000a444297f52bd20b0eca9b3610e930 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 25 Nov 2024 14:37:25 -0500 Subject: [PATCH] ui updates Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 4 ++++ javascript/base.css | 1 - javascript/black-teal.css | 3 ++- javascript/extraNetworks.js | 16 ++++++++++++++ javascript/progressBar.js | 38 ++++++++++++++++++-------------- javascript/sdnext.css | 18 +++++++-------- modules/call_queue.py | 11 ++++----- modules/processing.py | 9 +++----- modules/processing_callbacks.py | 14 ++++++------ modules/processing_correction.py | 13 +++++++++-- modules/processing_diffusers.py | 1 - modules/shared_state.py | 4 ++++ modules/timer.py | 6 +++++ modules/txt2img.py | 2 +- modules/ui_common.py | 13 ++++++++--- modules/ui_extra_networks.py | 2 +- modules/ui_img2img.py | 8 +++---- 17 files changed, 106 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9733dfa2b..170c780a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,10 @@ - Sampler improvements - update DPM FlowMatch samplers - UI: + - improved stats on generate completion + - improved live preview display and performance + - improved accordion behavior + - auto-size networks height for sidebar - browser->server logging framework - Fixes: - update `diffusers` diff --git a/javascript/base.css b/javascript/base.css index 7daa8b2bd..6c18ad7c5 100644 --- a/javascript/base.css +++ b/javascript/base.css @@ -25,7 +25,6 @@ .progressDiv .progress { width: 0%; height: 20px; background: #0060df; color: white; font-weight: bold; line-height: 20px; padding: 0 8px 0 0; text-align: right; overflow: visible; white-space: nowrap; padding: 0 0.5em; } .livePreview { position: absolute; z-index: 50; background-color: transparent; width: -moz-available; width: -webkit-fill-available; } .livePreview img { position: absolute; object-fit: contain; width: 100%; height: 100%; } -.dark .livePreview { background-color: rgb(17 24 39 / var(--tw-bg-opacity)); } .popup-metadata { color: white; background: #0000; display: inline-block; white-space: pre-wrap; font-size: 0.75em; } /* fullpage image viewer */ diff --git a/javascript/black-teal.css b/javascript/black-teal.css index c6f266c54..b73f9fdc7 100644 --- a/javascript/black-teal.css +++ b/javascript/black-teal.css @@ -108,6 +108,7 @@ fieldset .gr-block.gr-box, label.block span { padding: 0; margin-top: -4px; } .eta-bar { display: none !important } .gradio-slider { max-width: 200px; } .gradio-slider input[type="number"] { background: var(--neutral-950); margin-top: 2px; } +.gradio-image { height: unset !important; } svg.feather.feather-image, .feather .feather-image { display: none } .gap-2 { padding-top: 8px; } .gr-box > div > div > input.gr-text-input { right: 0; width: 4em; padding: 0; top: -12px; border: none; max-height: 20px; } @@ -134,7 +135,7 @@ svg.feather.feather-image, .feather .feather-image { display: none } .gallery-item { box-shadow: none !important; } .performance { color: #888; } .extra-networks { border-left: 2px solid var(--highlight-color) !important; padding-left: 4px; } -.image-buttons { gap: 10px !important; justify-content: center; } +.image-buttons { justify-content: center; gap: 0 !important; } .image-buttons > button { max-width: 160px; } .tooltip { background: var(--primary-300); color: black; border: none; border-radius: var(--radius-lg) } #system_row > button, #settings_row > button, #config_row > button { max-width: 10em; } diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 622e40faf..1d1bcfb24 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -434,6 +434,22 @@ function setupExtraNetworksForTab(tabname) { }; } + // auto-resize networks sidebar + const resizeObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + for (const el of Array.from(gradioApp().getElementById(`${tabname}_extra_tabs`).querySelectorAll('.extra-networks-page'))) { + const h = Math.trunc(entry.contentRect.height); + if (h <= 0) return; + if (window.opts.extra_networks_card_cover === 'sidebar' && window.opts.theme_type === 'Standard') el.style.height = `max(55vh, ${h - 90}px)`; + // log(`${tabname} height: ${entry.target.id}=${h} ${el.id}=${el.clientHeight}`); + } + } + }); + const settingsEl = gradioApp().getElementById(`${tabname}_settings`); + const interfaceEl = gradioApp().getElementById(`${tabname}_interface`); + if (settingsEl) resizeObserver.observe(settingsEl); + if (interfaceEl) resizeObserver.observe(interfaceEl); + // en style if (!en) return; let lastView; diff --git a/javascript/progressBar.js b/javascript/progressBar.js index 52f666b3a..ff9be4666 100644 --- a/javascript/progressBar.js +++ b/javascript/progressBar.js @@ -68,29 +68,33 @@ function requestProgress(id_task, progressEl, galleryEl, atEnd = null, onProgres let img; const initLivePreview = () => { + if (!parentGallery) return; + const footers = Array.from(gradioApp().querySelectorAll('.gallery_footer')); + for (const footer of footers) footer.style.display = 'none'; // remove all footers + + livePreview = document.createElement('div'); + livePreview.className = 'livePreview'; + parentGallery.insertBefore(livePreview, galleryEl); img = new Image(); - if (parentGallery) { - livePreview = document.createElement('div'); - livePreview.className = 'livePreview'; - parentGallery.insertBefore(livePreview, galleryEl); - const rect = galleryEl.getBoundingClientRect(); - if (rect.width) { - livePreview.style.width = `${rect.width}px`; - livePreview.style.height = `${rect.height}px`; - } - img.onload = () => { - livePreview.appendChild(img); - if (livePreview.childElementCount > 2) livePreview.removeChild(livePreview.firstElementChild); - }; - } + img.id = 'livePreviewImage'; + livePreview.appendChild(img); + img.onload = () => { + img.style.width = `min(100%, max(${img.naturalWidth}px, 512px))`; + parentGallery.style.minHeight = `${img.height}px`; + }; }; const done = () => { debug('taskEnd:', id_task); localStorage.removeItem('task'); setProgress(); + const footers = Array.from(gradioApp().querySelectorAll('.gallery_footer')); + for (const footer of footers) footer.style.display = 'flex'; // remove all footers try { - if (parentGallery && livePreview) parentGallery.removeChild(livePreview); + if (parentGallery && livePreview) { + parentGallery.removeChild(livePreview); + parentGallery.style.minHeight = 'unset'; + } } catch { /* ignore */ } checkPaused(true); sendNotification(); @@ -112,7 +116,9 @@ function requestProgress(id_task, progressEl, galleryEl, atEnd = null, onProgres } setProgress(res); if (res.live_preview && !livePreview) initLivePreview(); - if (res.live_preview && galleryEl) img.src = res.live_preview; + if (res.live_preview && galleryEl) { + if (img.src !== res.live_preview) img.src = res.live_preview; + } if (onProgress) onProgress(res); setTimeout(() => start(id_task, id_live_preview), opts.live_preview_refresh_period || 500); }; diff --git a/javascript/sdnext.css b/javascript/sdnext.css index 08fae2eb8..240b7492f 100644 --- a/javascript/sdnext.css +++ b/javascript/sdnext.css @@ -16,7 +16,7 @@ tr { border-bottom: none !important; padding: 0 0.5em !important; } td > div > span { overflow-y: auto; max-height: 3em; overflow-x: hidden; } textarea { overflow-y: auto !important; } span { font-size: var(--text-md) !important; } -button { font-size: var(--text-lg) !important; } +button { font-size: var(--text-lg) !important; min-width: unset !important; } input[type='color'] { width: 64px; height: 32px; } input::-webkit-outer-spin-button, input::-webkit-inner-spin-button { margin-left: 4px; } @@ -83,13 +83,14 @@ button.custom-button { border-radius: var(--button-large-radius); padding: var(- .block.token-counter div{ display: inline; } .block.token-counter span{ padding: 0.1em 0.75em; } .performance { font-size: var(--text-xs); color: #444; } -.performance p { display: inline-block; color: var(--body-text-color-subdued) !important } +.performance p { display: inline-block; color: var(--primary-500) !important } .performance .time { margin-right: 0; } .thumbnails { background: var(--body-background-fill); } -.control-image { height: calc(100vw/3) !important; } .prompt textarea { resize: vertical; } +.image-container { height: unset !important; } +.control-image { height: unset !important; } +.grid-wrap { overflow-y: auto !important; } #control_results { margin: 0; padding: 0; } -#control_gallery { height: calc(100vw/3 + 60px); } #txt2img_gallery, #img2img_gallery { height: 50vh; } #control-result { background: var(--button-secondary-background-fill); padding: 0.2em; } #control-inputs { margin-top: 1em; } @@ -122,7 +123,7 @@ div#extras_scale_to_tab div.form { flex-direction: row; } #img2img_sketch, #img2maskimg, #inpaint_sketch { overflow: overlay !important; resize: auto; background: var(--panel-background-fill); z-index: 5; } .image-buttons button { min-width: auto; } .infotext { overflow-wrap: break-word; line-height: 1.5em; font-size: 0.95em !important; } -.infotext > p { padding-left: 1em; text-indent: -1em; white-space: pre-wrap; color: var(--block-info-text-color) !important; } +.infotext > p { white-space: pre-wrap; color: var(--block-info-text-color) !important; } .tooltip { display: block; position: fixed; top: 1em; right: 1em; padding: 0.5em; background: var(--input-background-fill); color: var(--body-text-color); border: 1pt solid var(--button-primary-border-color); width: 22em; min-height: 1.3em; font-size: var(--text-xs); transition: opacity 0.2s ease-in; pointer-events: none; opacity: 0; z-index: 999; } .tooltip-show { opacity: 0.9; } @@ -158,11 +159,10 @@ div#extras_scale_to_tab div.form { flex-direction: row; } .progressDiv { position: relative; height: 20px; background: #b4c0cc; margin-bottom: -3px; } .dark .progressDiv { background: #424c5b; } .progressDiv .progress { width: 0%; height: 20px; background: #0060df; color: white; font-weight: bold; line-height: 20px; padding: 0 8px 0 0; text-align: right; overflow: visible; white-space: nowrap; padding: 0 0.5em; } -.livePreview { position: absolute; z-index: 50; background-color: transparent; width: -moz-available; width: -webkit-fill-available; } -.livePreview img { position: absolute; object-fit: contain; width: 100%; height: 100%; } -.dark .livePreview { background-color: rgb(17 24 39 / var(--tw-bg-opacity)); } +.livePreview { position: absolute; z-index: 50; width: -moz-available; width: -webkit-fill-available; height: 100%; background-color: var(--background-color); } +.livePreview img { object-fit: contain; width: 100%; justify-self: center; } .popup-metadata { color: white; background: #0000; display: inline-block; white-space: pre-wrap; font-size: var(--text-xxs); } - +.generating { animation: unset !important; border: unset !important; } /* fullpage image viewer */ #lightboxModal { display: none; position: fixed; z-index: 1001; left: 0; top: 0; width: 100%; height: 100%; overflow: hidden; background-color: rgba(20, 20, 20, 0.75); backdrop-filter: blur(6px); user-select: none; -webkit-user-select: none; flex-direction: row; font-family: 'NotoSans';} diff --git a/modules/call_queue.py b/modules/call_queue.py index 4065d13d9..cdc2fe1f7 100644 --- a/modules/call_queue.py +++ b/modules/call_queue.py @@ -2,7 +2,7 @@ import html import threading import time import cProfile -from modules import shared, progress, errors +from modules import shared, progress, errors, timer queue_lock = threading.Lock() @@ -73,15 +73,16 @@ def wrap_gradio_call(func, extra_outputs=None, add_stats=False, name=None): elapsed_m = int(elapsed // 60) elapsed_s = elapsed % 60 elapsed_text = f"{elapsed_m}m {elapsed_s:.2f}s" if elapsed_m > 0 else f"{elapsed_s:.2f}s" + summary = timer.process.summary(min_time=0.1, total=False).replace('=', ' ') vram_html = '' if not shared.mem_mon.disabled: vram = {k: -(v//-(1024*1024)) for k, v in shared.mem_mon.read().items()} + used = round(100 * vram['used'] / (vram['total'] + 0.001)) if vram.get('active_peak', 0) > 0: - vram_html = " |

" - vram_html += f"GPU active {max(vram['active_peak'], vram['reserved_peak'])} MB reserved {vram['reserved']} | used {vram['used']} MB free {vram['free']} MB total {vram['total']} MB" + vram_html = " | " + vram_html += f"GPU {max(vram['active_peak'], vram['reserved_peak'])} MB {used}%" vram_html += f" | retries {vram['retries']} oom {vram['oom']}" if vram.get('retries', 0) > 0 or vram.get('oom', 0) > 0 else '' - vram_html += "

" if isinstance(res, list): - res[-1] += f"

Time: {elapsed_text}

{vram_html}
" + res[-1] += f"

Time: {elapsed_text} | {summary}{vram_html}

" return tuple(res) return f diff --git a/modules/processing.py b/modules/processing.py index 0d557e64e..16e7a9213 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -323,7 +323,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: processed = p.scripts.process_images(p) if processed is not None: samples = processed.images - infotexts = processed.infotexts + infotexts += processed.infotexts if samples is None: if not shared.native: from modules.processing_original import process_original @@ -393,11 +393,8 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if shared.opts.mask_apply_overlay: image = apply_overlay(image, p.paste_to, i, p.overlay_images) - if len(infotexts) > i: - info = infotexts[i] - else: - info = create_infotext(p, p.prompts, p.seeds, p.subseeds, index=i, all_negative_prompts=p.negative_prompts) - infotexts.append(info) + info = create_infotext(p, p.prompts, p.seeds, p.subseeds, index=i, all_negative_prompts=p.negative_prompts) + infotexts.append(info) image.info["parameters"] = info output_images.append(image) if shared.opts.samples_save and not p.do_not_save_samples and p.outpath_samples is not None: diff --git a/modules/processing_callbacks.py b/modules/processing_callbacks.py index 52ea3e575..7d6c8ec04 100644 --- a/modules/processing_callbacks.py +++ b/modules/processing_callbacks.py @@ -6,7 +6,8 @@ import numpy as np from modules import shared, processing_correction, extra_networks, timer, prompt_parser_diffusers p = None -debug_callback = shared.log.trace if os.environ.get('SD_CALLBACK_DEBUG', None) is not None else lambda *args, **kwargs: None +debug = os.environ.get('SD_CALLBACK_DEBUG', None) is not None +debug_callback = shared.log.trace if debug else lambda *args, **kwargs: None def set_callbacks_p(processing): @@ -50,7 +51,8 @@ def diffusers_callback(pipe, step: int = 0, timestep: int = 0, kwargs: dict = {} if p is None: return kwargs latents = kwargs.get('latents', None) - debug_callback(f'Callback: step={step} timestep={timestep} latents={latents.shape if latents is not None else None} kwargs={list(kwargs)}') + if debug: + debug_callback(f'Callback: step={step} timestep={timestep} latents={latents.shape if latents is not None else None} kwargs={list(kwargs)}') order = getattr(pipe.scheduler, "order", 1) if hasattr(pipe, 'scheduler') else 1 shared.state.sampling_step = step // order if shared.state.interrupted or shared.state.skipped: @@ -67,7 +69,7 @@ def diffusers_callback(pipe, step: int = 0, timestep: int = 0, kwargs: dict = {} return kwargs elif shared.opts.nan_skip: assert not torch.isnan(latents[..., 0, 0]).all(), f'NaN detected at step {step}: Skipping...' - if len(getattr(p, 'ip_adapter_names', [])) > 0: + if len(getattr(p, 'ip_adapter_names', [])) > 0 and p.ip_adapter_names[0] != 'None': ip_adapter_scales = list(p.ip_adapter_scales) ip_adapter_starts = list(p.ip_adapter_starts) ip_adapter_ends = list(p.ip_adapter_ends) @@ -78,7 +80,7 @@ def diffusers_callback(pipe, step: int = 0, timestep: int = 0, kwargs: dict = {} debug_callback(f"Callback: IP Adapter scales={ip_adapter_scales}") pipe.set_ip_adapter_scale(ip_adapter_scales) if step != getattr(pipe, 'num_timesteps', 0): - kwargs = processing_correction.correction_callback(p, timestep, kwargs) + kwargs = processing_correction.correction_callback(p, timestep, kwargs, initial=step == 0) kwargs = prompt_callback(step, kwargs) # monkey patch for diffusers callback issues if step == int(getattr(pipe, 'num_timesteps', 100) * p.cfg_end) and 'prompt_embeds' in kwargs and 'negative_prompt_embeds' in kwargs: if "PAG" in shared.sd_model.__class__.__name__: @@ -105,7 +107,5 @@ def diffusers_callback(pipe, step: int = 0, timestep: int = 0, kwargs: dict = {} if shared.cmd_opts.profile and shared.profiler is not None: shared.profiler.step() t1 = time.time() - if 'callback' not in timer.process.records: - timer.process.records['callback'] = 0 - timer.process.records['callback'] += t1 - t0 + timer.process.add('callback', t1 - t0) return kwargs diff --git a/modules/processing_correction.py b/modules/processing_correction.py index e715d8c49..050fae889 100644 --- a/modules/processing_correction.py +++ b/modules/processing_correction.py @@ -7,9 +7,11 @@ import os import torch from modules import shared, sd_vae_taesd, devices + debug_enabled = os.environ.get('SD_HDR_DEBUG', None) is not None debug = shared.log.trace if debug_enabled else lambda *args, **kwargs: None debug('Trace: HDR') +skip_correction = False def sharpen_tensor(tensor, ratio=0): @@ -116,8 +118,15 @@ def correction(p, timestep, latent): return latent -def correction_callback(p, timestep, kwargs): - if not any([p.hdr_clamp, p.hdr_mode, p.hdr_maximize, p.hdr_sharpen, p.hdr_color, p.hdr_brightness, p.hdr_tint_ratio]): +def correction_callback(p, timestep, kwargs, initial: bool = False): + global skip_correction # pylint: disable=global-statement + if initial: + if not any([p.hdr_clamp, p.hdr_mode, p.hdr_maximize, p.hdr_sharpen, p.hdr_color, p.hdr_brightness, p.hdr_tint_ratio]): + skip_correction = True + return kwargs + else: + skip_correction = False + elif skip_correction: return kwargs latents = kwargs["latents"] if debug_enabled: diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 44dff811b..7b91fcd42 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -75,7 +75,6 @@ def process_base(p: processing.StableDiffusionProcessing): clip_skip=p.clip_skip, desc='Base', ) - timer.process.record('args') shared.state.sampling_steps = base_args.get('prior_num_inference_steps', None) or p.steps or base_args.get('num_inference_steps', None) if shared.opts.scheduler_eta is not None and shared.opts.scheduler_eta > 0 and shared.opts.scheduler_eta < 1: p.extra_generation_params["Sampler Eta"] = shared.opts.scheduler_eta diff --git a/modules/shared_state.py b/modules/shared_state.py index 51d33f9ed..0173b540c 100644 --- a/modules/shared_state.py +++ b/modules/shared_state.py @@ -2,6 +2,7 @@ import os import time import datetime from modules.errors import log +from modules import timer class State: @@ -150,6 +151,7 @@ class State: def do_set_current_image(self): if self.current_latent is None: return + t0 = time.time() from modules.shared import opts import modules.sd_samplers # pylint: disable=W0621 try: @@ -159,6 +161,8 @@ class State: except Exception: # log.error(f'Error setting current image: step={self.sampling_step} {e}') pass + t1 = time.time() + timer.process.add('preview', t1 - t0) def assign_current_image(self, image): self.current_image = image diff --git a/modules/timer.py b/modules/timer.py index 8a5db726d..7657ac8e8 100644 --- a/modules/timer.py +++ b/modules/timer.py @@ -15,6 +15,12 @@ class Timer: self.start = end return res + def add(self, name, t): + if name not in self.records: + self.records[name] = t + else: + self.records[name] += t + def record(self, category=None, extra_time=0, reset=True): e = self.elapsed(reset) if category is None: diff --git a/modules/txt2img.py b/modules/txt2img.py index 2f0e2f4b3..e82c744a2 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -88,7 +88,7 @@ def txt2img(id_task, state, p.scripts = scripts.scripts_txt2img p.script_args = args p.state = state - processed = scripts.scripts_txt2img.run(p, *args) + processed: processing.Processed = scripts.scripts_txt2img.run(p, *args) if processed is None: processed = processing.process_images(p) processed = scripts.scripts_txt2img.after(p, processed, *args) diff --git a/modules/ui_common.py b/modules/ui_common.py index 9c4bb5cdc..e21033718 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -245,10 +245,17 @@ def create_output_panel(tabname, preview=True, prompt=None, height=None): gr.HTML(value="", elem_id="main_info", visible=False, elem_classes=["main-info"]) # columns are for <576px, <768px, <992px, <1200px, <1400px, >1400px result_gallery = gr.Gallery(value=[], - label='Output', show_label=False, show_download_button=True, allow_preview=True, container=False, preview=preview, - columns=4, object_fit='scale-down', height=height, + label='Output', + show_label=False, + show_download_button=True, + allow_preview=True, + container=False, + preview=preview, + columns=4, + object_fit='scale-down', + height=height, elem_id=f"{tabname}_gallery", - ) + ) if prompt is not None: interrogate_clip_btn, interrogate_booru_btn = ui_sections.create_interrogate_buttons('control') interrogate_clip_btn.click(fn=interrogate_clip, inputs=[result_gallery], outputs=[prompt]) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index e152dc74b..c326219df 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -16,7 +16,7 @@ from collections import OrderedDict import gradio as gr from PIL import Image from starlette.responses import FileResponse, JSONResponse -from modules import paths, shared, scripts, files_cache, errors, infotext +from modules import paths, shared, files_cache, errors, infotext from modules.ui_components import ToolButton import modules.ui_symbols as symbols diff --git a/modules/ui_img2img.py b/modules/ui_img2img.py index 22c89dac8..046c181ce 100644 --- a/modules/ui_img2img.py +++ b/modules/ui_img2img.py @@ -68,20 +68,20 @@ def create_ui(): img2img_selected_tab = gr.State(0) # pylint: disable=abstract-class-instantiated state = gr.Textbox(value='', visible=False) with gr.TabItem('Image', id='img2img', elem_id="img2img_img2img_tab") as tab_img2img: - init_img = gr.Image(label="Image for img2img", elem_id="img2img_image", show_label=False, source="upload", interactive=True, type="pil", tool="editor", image_mode="RGBA", height=512) + init_img = gr.Image(label="Image for img2img", elem_id="img2img_image", show_label=False, source="upload", interactive=True, type="pil", tool="editor", image_mode="RGBA") interrogate_clip, interrogate_booru = ui_sections.create_interrogate_buttons('img2img') add_copy_image_controls('img2img', init_img) with gr.TabItem('Sketch', id='img2img_sketch', elem_id="img2img_img2img_sketch_tab") as tab_sketch: - sketch = gr.Image(label="Image for img2img", elem_id="img2img_sketch", show_label=False, source="upload", interactive=True, type="pil", tool="color-sketch", image_mode="RGBA", height=512) + sketch = gr.Image(label="Image for img2img", elem_id="img2img_sketch", show_label=False, source="upload", interactive=True, type="pil", tool="color-sketch", image_mode="RGBA") add_copy_image_controls('sketch', sketch) with gr.TabItem('Inpaint', id='inpaint', elem_id="img2img_inpaint_tab") as tab_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", height=512) + 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") add_copy_image_controls('inpaint', init_img_with_mask) with gr.TabItem('Composite', id='inpaint_sketch', elem_id="img2img_inpaint_sketch_tab") as tab_inpaint_color: - inpaint_color_sketch = gr.Image(label="Color sketch inpainting", show_label=False, elem_id="inpaint_sketch", source="upload", interactive=True, type="pil", tool="color-sketch", image_mode="RGBA", height=512) + inpaint_color_sketch = gr.Image(label="Color sketch inpainting", show_label=False, elem_id="inpaint_sketch", source="upload", interactive=True, type="pil", tool="color-sketch", image_mode="RGBA") inpaint_color_sketch_orig = gr.State(None) # pylint: disable=abstract-class-instantiated add_copy_image_controls('inpaint_sketch', inpaint_color_sketch)