Fix image upload
This commit is contained in:
@@ -48,16 +48,12 @@ export default class ComfyPickImageNode extends ComfyGraphNode {
|
|||||||
this._path = comfyFileToAnnotatedFilepath(this._image.comfyUIFile);
|
this._path = comfyFileToAnnotatedFilepath(this._image.comfyUIFile);
|
||||||
this.filepathWidget.value = this._image.comfyUIFile.filename
|
this.filepathWidget.value = this._image.comfyUIFile.filename
|
||||||
this.folderWidget.value = this._image.comfyUIFile.type
|
this.folderWidget.value = this._image.comfyUIFile.type
|
||||||
this.widthWidget.value = this._image.width
|
|
||||||
this.heightWidget.value = this._image.height
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this._image = null;
|
this._image = null;
|
||||||
this._path = null;
|
this._path = null;
|
||||||
this.filepathWidget.value = "(None)"
|
this.filepathWidget.value = "(None)"
|
||||||
this.folderWidget.value = ""
|
this.folderWidget.value = ""
|
||||||
this.widthWidget.value = 0
|
|
||||||
this.heightWidget.value = 0
|
|
||||||
}
|
}
|
||||||
console.log("SET", value, this._image, this._path)
|
console.log("SET", value, this._image, this._path)
|
||||||
}
|
}
|
||||||
@@ -72,12 +68,18 @@ export default class ComfyPickImageNode extends ComfyGraphNode {
|
|||||||
this.setOutputData(1, null)
|
this.setOutputData(1, null)
|
||||||
this.setOutputData(2, 0)
|
this.setOutputData(2, 0)
|
||||||
this.setOutputData(3, 0)
|
this.setOutputData(3, 0)
|
||||||
|
|
||||||
|
this.widthWidget.value = 0
|
||||||
|
this.heightWidget.value = 0
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.setOutputData(0, this._image);
|
this.setOutputData(0, this._image);
|
||||||
this.setOutputData(1, this._path);
|
this.setOutputData(1, this._path);
|
||||||
this.setOutputData(2, this._image.width);
|
this.setOutputData(2, this._image.width);
|
||||||
this.setOutputData(3, this._image.height);
|
this.setOutputData(3, this._image.height);
|
||||||
|
|
||||||
|
this.widthWidget.value = this._image.width
|
||||||
|
this.heightWidget.value = this._image.height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { BuiltInSlotType, LiteGraph, type SlotLayout } from "@litegraph-ts/core"
|
|||||||
import ImageUploadWidget from "$lib/widgets/ImageUploadWidget.svelte";
|
import ImageUploadWidget from "$lib/widgets/ImageUploadWidget.svelte";
|
||||||
import type { ComfyWidgetProperties } from "./ComfyWidgetNode";
|
import type { ComfyWidgetProperties } from "./ComfyWidgetNode";
|
||||||
import ComfyWidgetNode from "./ComfyWidgetNode";
|
import ComfyWidgetNode from "./ComfyWidgetNode";
|
||||||
|
import { get, writable, type Writable } from "svelte/store";
|
||||||
|
|
||||||
export interface ComfyImageUploadNodeProperties extends ComfyWidgetProperties {
|
export interface ComfyImageUploadNodeProperties extends ComfyWidgetProperties {
|
||||||
}
|
}
|
||||||
@@ -31,10 +32,22 @@ export default class ComfyImageUploadNode extends ComfyWidgetNode<ComfyBoxImageM
|
|||||||
override storeActionName = "store";
|
override storeActionName = "store";
|
||||||
override saveUserState = false;
|
override saveUserState = false;
|
||||||
|
|
||||||
|
imgWidth: Writable<number> = writable(0);
|
||||||
|
imgHeight: Writable<number> = writable(0);
|
||||||
|
|
||||||
constructor(name?: string) {
|
constructor(name?: string) {
|
||||||
super(name, [])
|
super(name, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override onExecute() {
|
||||||
|
// TODO better way of getting image size?
|
||||||
|
const value = get(this.value)
|
||||||
|
if (value && value.length > 0) {
|
||||||
|
value[0].width = get(this.imgWidth)
|
||||||
|
value[0].height = get(this.imgHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override parseValue(value: any): ComfyBoxImageMetadata[] {
|
override parseValue(value: any): ComfyBoxImageMetadata[] {
|
||||||
return parseWhateverIntoImageMetadata(value) || [];
|
return parseWhateverIntoImageMetadata(value) || [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,344 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { type WidgetLayout } from "$lib/stores/layoutState";
|
|
||||||
import { Block } from "@gradio/atoms";
|
|
||||||
import { TextBox } from "@gradio/form";
|
|
||||||
import Row from "$lib/components/gradio/app/Row.svelte";
|
|
||||||
import { get, type Writable } from "svelte/store";
|
|
||||||
import Modal from "$lib/components/Modal.svelte";
|
|
||||||
import { Button } from "@gradio/button";
|
|
||||||
import type { ComfyImageLocation } from "$lib/utils";
|
|
||||||
import { Embed as Klecks } from "klecks";
|
|
||||||
|
|
||||||
import "klecks/style/style.scss";
|
|
||||||
import ImageUpload from "$lib/components/ImageUpload.svelte";
|
|
||||||
import NumberInput from "$lib/components/NumberInput.svelte";
|
|
||||||
import { uploadImageToComfyUI, type ComfyBoxImageMetadata, comfyFileToComfyBoxMetadata, comfyBoxImageToComfyURL, comfyBoxImageToComfyFile, type ComfyUploadImageType } from "$lib/utils";
|
|
||||||
import notify from "$lib/notify";
|
|
||||||
import type { ComfyImageEditorNode } from "$lib/nodes/widgets";
|
|
||||||
|
|
||||||
export let widget: WidgetLayout | null = null;
|
|
||||||
export let isMobile: boolean = false;
|
|
||||||
let node: ComfyImageEditorNode | null = null;
|
|
||||||
let nodeValue: Writable<ComfyBoxImageMetadata[]> | null = null;
|
|
||||||
let attrsChanged: Writable<number> | null = null;
|
|
||||||
|
|
||||||
let imgWidth: number = 0;
|
|
||||||
let imgHeight: number = 0;
|
|
||||||
|
|
||||||
$: widget && setNodeValue(widget);
|
|
||||||
|
|
||||||
$: if ($nodeValue && $nodeValue.length > 0) {
|
|
||||||
// TODO improve
|
|
||||||
if (imgWidth > 0 && imgHeight > 0) {
|
|
||||||
$nodeValue[0].width = imgWidth
|
|
||||||
$nodeValue[0].height = imgHeight
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$nodeValue[0].width = 0
|
|
||||||
$nodeValue[0].height = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setNodeValue(widget: WidgetLayout) {
|
|
||||||
if (widget) {
|
|
||||||
node = widget.node as ComfyImageEditorNode
|
|
||||||
nodeValue = node.value;
|
|
||||||
attrsChanged = widget.attrsChanged;
|
|
||||||
status = $nodeValue && $nodeValue.length > 0 ? "uploaded" : "empty"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let editorRoot: HTMLDivElement | null = null;
|
|
||||||
let showModal = false;
|
|
||||||
let kl: Klecks | null = null;
|
|
||||||
|
|
||||||
function disposeEditor() {
|
|
||||||
console.warn("[ImageEditorWidget] CLOSING", widget, $nodeValue)
|
|
||||||
|
|
||||||
if (editorRoot) {
|
|
||||||
while (editorRoot.firstChild) {
|
|
||||||
editorRoot.removeChild(editorRoot.firstChild);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kl = null;
|
|
||||||
showModal = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateBlankCanvas(width: number, height: number, fill: string = "#fff"): HTMLCanvasElement {
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
ctx.save();
|
|
||||||
ctx.fillStyle = fill,
|
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
||||||
ctx.restore();
|
|
||||||
return canvas;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadImage(imageURL: string): Promise<HTMLImageElement> {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const e = new Image();
|
|
||||||
e.setAttribute('crossorigin', 'anonymous'); // Don't taint the canvas from loading files on-disk
|
|
||||||
e.addEventListener("load", () => { resolve(e); });
|
|
||||||
e.src = imageURL;
|
|
||||||
return e;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generateImageCanvas(imageURL: string): Promise<[HTMLCanvasElement, number, number]> {
|
|
||||||
const image = await loadImage(imageURL);
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
canvas.width = image.width;
|
|
||||||
canvas.height = image.height;
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
ctx.save();
|
|
||||||
ctx.fillStyle = "rgba(255, 255, 255, 0.0)";
|
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
||||||
ctx.drawImage(image, 0, 0);
|
|
||||||
ctx.restore();
|
|
||||||
return [canvas, image.width, image.height];
|
|
||||||
}
|
|
||||||
|
|
||||||
const FILENAME: string = "ComfyUITemp.png";
|
|
||||||
const SUBFOLDER: string = "ComfyBox_Editor";
|
|
||||||
const DIRECTORY: ComfyUploadImageType = "input";
|
|
||||||
|
|
||||||
async function submitKlecksToComfyUI(onSuccess: () => void, onError: () => void) {
|
|
||||||
const blob = kl.getPNG();
|
|
||||||
|
|
||||||
status = "uploading"
|
|
||||||
|
|
||||||
await uploadImageToComfyUI(blob, FILENAME, DIRECTORY, SUBFOLDER)
|
|
||||||
.then((entry: ComfyImageLocation) => {
|
|
||||||
const meta: ComfyBoxImageMetadata = comfyFileToComfyBoxMetadata(entry);
|
|
||||||
$nodeValue = [meta] // TODO more than one image
|
|
||||||
status = "uploaded"
|
|
||||||
notify("Saved image to ComfyUI!", { type: "success" })
|
|
||||||
onSuccess();
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
notify(`Failed to upload image from editor: ${err}`, { type: "error", timeout: 10000 })
|
|
||||||
status = "error"
|
|
||||||
uploadError = err;
|
|
||||||
$nodeValue = []
|
|
||||||
onError();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let closeDialog = null;
|
|
||||||
|
|
||||||
async function saveAndClose() {
|
|
||||||
console.log(closeDialog, kl)
|
|
||||||
if (!closeDialog || !kl)
|
|
||||||
return;
|
|
||||||
|
|
||||||
submitKlecksToComfyUI(() => {}, () => {});
|
|
||||||
closeDialog()
|
|
||||||
}
|
|
||||||
|
|
||||||
let blankImageWidth = 512;
|
|
||||||
let blankImageHeight = 512;
|
|
||||||
|
|
||||||
async function openImageEditor() {
|
|
||||||
if (!editorRoot)
|
|
||||||
return;
|
|
||||||
|
|
||||||
showModal = true;
|
|
||||||
|
|
||||||
const url = `http://${location.hostname}:8188` // TODO make configurable
|
|
||||||
|
|
||||||
kl = new Klecks({
|
|
||||||
embedUrl: url,
|
|
||||||
onSubmit: submitKlecksToComfyUI,
|
|
||||||
targetEl: editorRoot,
|
|
||||||
warnOnPageClose: false
|
|
||||||
});
|
|
||||||
|
|
||||||
console.warn("[ImageEditorWidget] OPENING", widget, $nodeValue)
|
|
||||||
|
|
||||||
let canvas = null;
|
|
||||||
let width = blankImageWidth;
|
|
||||||
let height = blankImageHeight;
|
|
||||||
|
|
||||||
if ($nodeValue && $nodeValue.length > 0) {
|
|
||||||
const comfyImage = $nodeValue[0];
|
|
||||||
const comfyURL = comfyBoxImageToComfyURL(comfyImage);
|
|
||||||
[canvas, width, height] = await generateImageCanvas(comfyURL);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
canvas = generateBlankCanvas(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
kl.openProject({
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
layers: [{
|
|
||||||
name: 'Image',
|
|
||||||
opacity: 1,
|
|
||||||
mixModeStr: 'source-over',
|
|
||||||
image: canvas
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(function () {
|
|
||||||
kl.klApp?.out("yo");
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
let status = "empty";
|
|
||||||
let uploadError = null;
|
|
||||||
|
|
||||||
function onUploading() {
|
|
||||||
console.warn("UPLOADING!!!")
|
|
||||||
uploadError = null;
|
|
||||||
status = "uploading"
|
|
||||||
}
|
|
||||||
|
|
||||||
function onUploaded(e: CustomEvent<ComfyImageLocation[]>) {
|
|
||||||
console.warn("UPLOADED!!!")
|
|
||||||
uploadError = null;
|
|
||||||
status = "uploaded"
|
|
||||||
$nodeValue = e.detail.map(comfyFileToComfyBoxMetadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onClear() {
|
|
||||||
console.warn("CLEAR!!!")
|
|
||||||
uploadError = null;
|
|
||||||
status = "empty"
|
|
||||||
$nodeValue = []
|
|
||||||
}
|
|
||||||
|
|
||||||
function onUploadError(e: CustomEvent<any>) {
|
|
||||||
console.warn("ERROR!!!")
|
|
||||||
status = "error"
|
|
||||||
uploadError = e.detail
|
|
||||||
$nodeValue = []
|
|
||||||
notify(`Failed to upload image to ComfyUI: ${uploadError}`, { type: "error", timeout: 10000 })
|
|
||||||
}
|
|
||||||
|
|
||||||
function onChange(e: CustomEvent<ComfyImageLocation[]>) {
|
|
||||||
}
|
|
||||||
|
|
||||||
let _value: ComfyImageLocation[] = []
|
|
||||||
$: if ($nodeValue)
|
|
||||||
_value = $nodeValue.map(comfyBoxImageToComfyFile)
|
|
||||||
else
|
|
||||||
_value = []
|
|
||||||
|
|
||||||
$: canEdit = status === "empty" || status === "uploaded";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="wrapper comfy-image-editor">
|
|
||||||
{#if widget.attrs.variant === "fileUpload" || isMobile}
|
|
||||||
<ImageUpload value={_value}
|
|
||||||
bind:imgWidth
|
|
||||||
bind:imgHeight
|
|
||||||
fileCount={"single"}
|
|
||||||
elem_classes={[]}
|
|
||||||
style={""}
|
|
||||||
label={widget.attrs.title}
|
|
||||||
on:uploading={onUploading}
|
|
||||||
on:uploaded={onUploaded}
|
|
||||||
on:upload_error={onUploadError}
|
|
||||||
on:clear={onClear}
|
|
||||||
on:change={onChange}
|
|
||||||
on:image_clicked={openImageEditor}
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<div class="comfy-image-editor-panel">
|
|
||||||
<ImageUpload value={_value}
|
|
||||||
bind:imgWidth
|
|
||||||
bind:imgHeight
|
|
||||||
fileCount={"single"}
|
|
||||||
elem_classes={[]}
|
|
||||||
style={""}
|
|
||||||
label={widget.attrs.title}
|
|
||||||
on:uploading={onUploading}
|
|
||||||
on:uploaded={onUploaded}
|
|
||||||
on:upload_error={onUploadError}
|
|
||||||
on:clear={onClear}
|
|
||||||
on:change={onChange}
|
|
||||||
on:image_clicked={openImageEditor}
|
|
||||||
/>
|
|
||||||
<Modal bind:showModal closeOnClick={false} on:close={disposeEditor} bind:closeDialog>
|
|
||||||
<div>
|
|
||||||
<div id="klecks-loading-screen">
|
|
||||||
<span id="klecks-loading-screen-text"></span>
|
|
||||||
</div>
|
|
||||||
<div class="image-editor-root" bind:this={editorRoot} />
|
|
||||||
</div>
|
|
||||||
<div slot="buttons">
|
|
||||||
<Button variant="primary" on:click={saveAndClose}>
|
|
||||||
Save and Close
|
|
||||||
</Button>
|
|
||||||
<Button variant="secondary" on:click={closeDialog}>
|
|
||||||
Discard Edits
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
<Block>
|
|
||||||
{#if !$nodeValue || $nodeValue.length === 0}
|
|
||||||
<Row>
|
|
||||||
<Row>
|
|
||||||
<Button variant="secondary" disabled={!canEdit} on:click={openImageEditor}>
|
|
||||||
Create Image
|
|
||||||
</Button>
|
|
||||||
<div>
|
|
||||||
<TextBox show_label={false} disabled={true} value="Status: {status}"/>
|
|
||||||
</div>
|
|
||||||
{#if uploadError}
|
|
||||||
<div>
|
|
||||||
Upload error: {uploadError}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</Row>
|
|
||||||
<Row>
|
|
||||||
<NumberInput label={"Width"} min={64} max={2048} step={64} bind:value={blankImageWidth} />
|
|
||||||
<NumberInput label={"Height"} min={64} max={2048} step={64} bind:value={blankImageHeight} />
|
|
||||||
</Row>
|
|
||||||
</Row>
|
|
||||||
{:else}
|
|
||||||
<Row>
|
|
||||||
<Button variant="secondary" disabled={!canEdit} on:click={openImageEditor}>
|
|
||||||
Edit Image
|
|
||||||
</Button>
|
|
||||||
<div>
|
|
||||||
<TextBox show_label={false} disabled={true} value="Status: {status}"/>
|
|
||||||
</div>
|
|
||||||
{#if uploadError}
|
|
||||||
<div>
|
|
||||||
Upload error: {uploadError}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</Row>
|
|
||||||
{/if}
|
|
||||||
</Block>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.image-editor-root {
|
|
||||||
width: 75vw;
|
|
||||||
height: 75vh;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
color: black;
|
|
||||||
|
|
||||||
:global(> .g-root) {
|
|
||||||
height: calc(100% - 59px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.comfy-image-editor {
|
|
||||||
:global(> dialog) {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.kl-popup) {
|
|
||||||
z-index: 999999999999;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -28,23 +28,13 @@
|
|||||||
|
|
||||||
$: console.warn("IMGSIZE2!!!", $imgWidth, $imgHeight)
|
$: console.warn("IMGSIZE2!!!", $imgWidth, $imgHeight)
|
||||||
|
|
||||||
$: if ($nodeValue && $nodeValue.length > 0) {
|
|
||||||
// TODO improve
|
|
||||||
if ($imgWidth > 0 && $imgHeight > 0) {
|
|
||||||
$nodeValue[0].width = $imgWidth
|
|
||||||
$nodeValue[0].height = $imgHeight
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$nodeValue[0].width = 0
|
|
||||||
$nodeValue[0].height = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setNodeValue(widget: WidgetLayout) {
|
function setNodeValue(widget: WidgetLayout) {
|
||||||
if (widget) {
|
if (widget) {
|
||||||
node = widget.node as ComfyImageEditorNode
|
node = widget.node as ComfyImageEditorNode
|
||||||
nodeValue = node.value;
|
nodeValue = node.value;
|
||||||
attrsChanged = widget.attrsChanged;
|
attrsChanged = widget.attrsChanged;
|
||||||
|
imgWidth = node.imgWidth
|
||||||
|
imgHeight = node.imgHeight
|
||||||
status = $nodeValue && $nodeValue.length > 0 ? "uploaded" : "empty"
|
status = $nodeValue && $nodeValue.length > 0 ? "uploaded" : "empty"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user