diff --git a/server.py b/server.py index 5be822a6..b0dd3382 100644 --- a/server.py +++ b/server.py @@ -217,6 +217,28 @@ class PromptServer(): file = os.path.join(output_dir, filename) if os.path.isfile(file): + if 'preview' in request.rel_url.query: + with Image.open(file) as img: + preview_info = request.rel_url.query['preview'].split(';') + + if preview_info[0] == "L" or preview_info[0] == "l": + img = img.convert("L") + image_format = preview_info[1] + else: + img = img.convert("RGB") # jpeg doesn't support RGBA + image_format = preview_info[0] + + quality = 90 + if preview_info[-1].isdigit(): + quality = int(preview_info[-1]) + + buffer = BytesIO() + img.save(buffer, format=image_format, optimize=True, quality=quality) + buffer.seek(0) + + return web.Response(body=buffer.read(), content_type=f'image/{image_format}', + headers={"Content-Disposition": f"filename=\"{filename}\""}) + if 'channel' not in request.rel_url.query: channel = 'rgba' else: diff --git a/web/extensions/core/maskeditor.js b/web/extensions/core/maskeditor.js index 6cb3a538..764164d5 100644 --- a/web/extensions/core/maskeditor.js +++ b/web/extensions/core/maskeditor.js @@ -41,7 +41,7 @@ async function uploadMask(filepath, formData) { }); ComfyApp.clipspace.imgs[ComfyApp.clipspace['selectedIndex']] = new Image(); - ComfyApp.clipspace.imgs[ComfyApp.clipspace['selectedIndex']].src = "/view?" + new URLSearchParams(filepath).toString(); + ComfyApp.clipspace.imgs[ComfyApp.clipspace['selectedIndex']].src = "/view?" + new URLSearchParams(filepath).toString() + app.getPreviewFormatParam(); if(ComfyApp.clipspace.images) ComfyApp.clipspace.images[ComfyApp.clipspace['selectedIndex']] = filepath; @@ -335,6 +335,7 @@ class MaskEditorDialog extends ComfyDialog { const alpha_url = new URL(ComfyApp.clipspace.imgs[ComfyApp.clipspace['selectedIndex']].src) alpha_url.searchParams.delete('channel'); + alpha_url.searchParams.delete('preview'); alpha_url.searchParams.set('channel', 'a'); touched_image.src = alpha_url; @@ -345,6 +346,7 @@ class MaskEditorDialog extends ComfyDialog { const rgb_url = new URL(ComfyApp.clipspace.imgs[ComfyApp.clipspace['selectedIndex']].src); rgb_url.searchParams.delete('channel'); + rgb_url.searchParams.delete('preview'); rgb_url.searchParams.set('channel', 'rgb'); orig_image.src = rgb_url; this.image = orig_image; diff --git a/web/scripts/app.js b/web/scripts/app.js index 8a9c7ca4..95447ffa 100644 --- a/web/scripts/app.js +++ b/web/scripts/app.js @@ -51,6 +51,14 @@ export class ComfyApp { this.shiftDown = false; } + getPreviewFormatParam() { + let preview_format = this.ui.settings.getSettingValue("Comfy.PreviewFormat"); + if(preview_format) + return `&preview=${preview_format}`; + else + return ""; + } + static isImageNode(node) { return node.imgs || (node && node.widgets && node.widgets.findIndex(obj => obj.name === 'image') >= 0); } @@ -231,14 +239,20 @@ export class ComfyApp { options.unshift( { content: "Open Image", - callback: () => window.open(img.src, "_blank"), + callback: () => { + let url = new URL(img.src); + url.searchParams.delete('preview'); + window.open(url, "_blank") + }, }, { content: "Save Image", callback: () => { const a = document.createElement("a"); - a.href = img.src; - a.setAttribute("download", new URLSearchParams(new URL(img.src).search).get("filename")); + let url = new URL(img.src); + url.searchParams.delete('preview'); + a.href = url; + a.setAttribute("download", new URLSearchParams(url.search).get("filename")); document.body.append(a); a.click(); requestAnimationFrame(() => a.remove()); @@ -365,7 +379,7 @@ export class ComfyApp { const img = new Image(); img.onload = () => r(img); img.onerror = () => r(null); - img.src = "/view?" + new URLSearchParams(src).toString(); + img.src = "/view?" + new URLSearchParams(src).toString() + app.getPreviewFormatParam(); }); }) ).then((imgs) => { diff --git a/web/scripts/ui.js b/web/scripts/ui.js index 2c9043d0..6b764d43 100644 --- a/web/scripts/ui.js +++ b/web/scripts/ui.js @@ -462,6 +462,25 @@ export class ComfyUI { defaultValue: true, }); + /** + * file format for preview + * + * L?;format;quality + * + * ex) + * L;webp;50 -> grayscale, webp, quality 50 + * jpeg;80 -> rgb, jpeg, quality 80 + * png -> rgb, png, default quality(=90) + * + * @type {string} + */ + const previewImage = this.settings.addSetting({ + id: "Comfy.PreviewFormat", + name: "When displaying a preview in the image widget, convert it to a lightweight image. (webp, jpeg, webp;50, ...)", + type: "string", + defaultValue: "", + }); + const fileInput = $el("input", { id: "comfy-file-input", type: "file", diff --git a/web/scripts/widgets.js b/web/scripts/widgets.js index 82168b08..d6faaddb 100644 --- a/web/scripts/widgets.js +++ b/web/scripts/widgets.js @@ -303,7 +303,7 @@ export const ComfyWidgets = { subfolder = name.substring(0, folder_separator); name = name.substring(folder_separator + 1); } - img.src = `/view?filename=${name}&type=input&subfolder=${subfolder}`; + img.src = `/view?filename=${name}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}`; node.setSizeForImage?.(); }