improve: lightweight preview to reduce network traffic (#733)
* To reduce bandwidth traffic in a remote environment, a lossy compression-based preview mode is provided for displaying simple visualizations in node-based widgets. * Added 'preview=[image format]' option to the '/view' API. * Updated node to use preview for displaying images as widgets. * Excluded preview usage in the open image, save image, mask editor where the original data is required. * Made preview_format parameterizable for extensibility. * default preview format changed: jpeg -> webp * Support advanced preview_format option. - grayscale option for visual debugging - quality option for aggressive reducing L?;format;quality? ex) jpeg => rgb, jpeg, quality 90 L;webp;80 => grayscale, webp, quality 80 L;png => grayscale, png, quality 90 webp;50 => rgb, webp, quality 50 * move comment * * add settings for preview_format * default value is ''(= don't reencode) --------- Co-authored-by: Lt.Dr.Data <lt.dr.data@gmail.com>
This commit is contained in:
parent
fed0a4dd29
commit
9f3a19b728
22
server.py
22
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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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?.();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue