From bf00580562dacfbfe913e2e1178fb52c8351b54f Mon Sep 17 00:00:00 2001 From: bennykok Date: Tue, 7 Jan 2025 21:03:52 +0800 Subject: [PATCH] feat: update external image node to have default value --- comfy-nodes/external_image.py | 65 ++++++----- web-plugin/index.js | 206 +++++++++++++++++++++++----------- 2 files changed, 182 insertions(+), 89 deletions(-) diff --git a/comfy-nodes/external_image.py b/comfy-nodes/external_image.py index 1fc2258..679ebee 100644 --- a/comfy-nodes/external_image.py +++ b/comfy-nodes/external_image.py @@ -21,8 +21,9 @@ class ComfyUIDeployExternalImage: ), "description": ( "STRING", - {"multiline": True, "default": ""}, + {"multiline": False, "default": ""}, ), + "default_value_url": ("STRING", {"image_preview": True, "default": ""}), } } @@ -33,32 +34,44 @@ class ComfyUIDeployExternalImage: CATEGORY = "image" - def run(self, input_id, default_value=None, display_name=None, description=None): + def run(self, input_id, default_value=None, display_name=None, description=None, default_value_url=None): image = default_value - try: - if input_id.startswith('http'): - import requests - from io import BytesIO - print("Fetching image from url: ", input_id) - response = requests.get(input_id) - image = Image.open(BytesIO(response.content)) - elif input_id.startswith('data:image/png;base64,') or input_id.startswith('data:image/jpeg;base64,') or input_id.startswith('data:image/jpg;base64,'): - import base64 - from io import BytesIO - print("Decoding base64 image") - base64_image = input_id[input_id.find(",")+1:] - decoded_image = base64.b64decode(base64_image) - image = Image.open(BytesIO(decoded_image)) - else: - raise ValueError("Invalid image url provided.") - - image = ImageOps.exif_transpose(image) - image = image.convert("RGB") - image = np.array(image).astype(np.float32) / 255.0 - image = torch.from_numpy(image)[None,] - return [image] - except: - return [image] + + # Try both input_id and default_value_url + urls_to_try = [url for url in [input_id, default_value_url] if url] + + print(default_value_url) + + for url in urls_to_try: + try: + if url.startswith('http'): + import requests + from io import BytesIO + print(f"Fetching image from url: {url}") + response = requests.get(url) + image = Image.open(BytesIO(response.content)) + break + elif url.startswith(('data:image/png;base64,', 'data:image/jpeg;base64,', 'data:image/jpg;base64,')): + import base64 + from io import BytesIO + print("Decoding base64 image") + base64_image = url[url.find(",")+1:] + decoded_image = base64.b64decode(base64_image) + image = Image.open(BytesIO(decoded_image)) + break + except: + continue + + if image is not None: + try: + image = ImageOps.exif_transpose(image) + image = image.convert("RGB") + image = np.array(image).astype(np.float32) / 255.0 + image = torch.from_numpy(image)[None,] + except: + pass + + return [image] NODE_CLASS_MAPPINGS = {"ComfyUIDeployExternalImage": ComfyUIDeployExternalImage} diff --git a/web-plugin/index.js b/web-plugin/index.js index cd4847c..873169c 100644 --- a/web-plugin/index.js +++ b/web-plugin/index.js @@ -53,9 +53,9 @@ function sendEventToCD(event, data) { function sendDirectEventToCD(event, data) { const message = { type: event, - data: data - } - window.parent.postMessage(message, '*') + data: data, + }; + window.parent.postMessage(message, "*"); } function dispatchAPIEventData(data) { @@ -494,6 +494,13 @@ const ext = { return r; }; + if ( + nodeData?.input?.optional?.default_value_url?.[1]?.image_preview === true + ) { + nodeData.input.optional.default_value_url = ["IMAGEPREVIEW"]; + console.log(nodeData.input.optional.default_value_url); + } + // const origonNodeCreated = nodeType.prototype.onNodeCreated; // nodeType.prototype.onNodeCreated = function () { // const r = origonNodeCreated @@ -620,6 +627,78 @@ const ext = { ComfyDeploy.category = "deploy"; }, + getCustomWidgets() { + return { + IMAGEPREVIEW(node, inputName, inputData) { + // Find or create the URL input widget + const urlWidget = node.addWidget( + "string", + inputName, + /* value=*/ "", + () => {}, + { serialize: true }, + ); + + const buttonWidget = node.addWidget( + "button", + "Open Assets Browser", + /* value=*/ "", + () => { + sendEventToCD("assets", { + node: node.id, + inputName: inputName, + }) + // console.log("load image"); + }, + { serialize: false }, + ); + + console.log(node.widgets); + + console.log("urlWidget", urlWidget); + + // Add image preview functionality + function showImage(url) { + const img = new Image(); + img.onload = () => { + node.imgs = [img]; + app.graph.setDirtyCanvas(true); + node.setSizeForImage?.(); + }; + img.onerror = () => { + node.imgs = []; + app.graph.setDirtyCanvas(true); + }; + img.src = url; + } + + // Set up URL widget value handling + let default_value = urlWidget.value; + Object.defineProperty(urlWidget, "value", { + set: function (value) { + this._real_value = value; + // Preview image when URL changes + if (value) { + showImage(value); + } + }, + get: function () { + return this._real_value || default_value; + }, + }); + + // Show initial image if URL exists + requestAnimationFrame(() => { + if (urlWidget.value) { + showImage(urlWidget.value); + } + }); + + return { widget: urlWidget }; + }, + }; + }, + async setup() { // const graphCanvas = document.getElementById("graph-canvas"); @@ -661,8 +740,8 @@ const ext = { console.warn("api.handlePromptGenerated is not a function"); } sendEventToCD("cd_plugin_onQueuePrompt", prompt); - } else if (message.type === 'configure_queue_buttons') { - addQueueButtons(message.data) + } else if (message.type === "configure_queue_buttons") { + addQueueButtons(message.data); } else if (message.type === "get_prompt") { const prompt = await app.graphToPrompt(); sendEventToCD("cd_plugin_onGetPrompt", prompt); @@ -1903,117 +1982,118 @@ api.fetchApi = async (route, options) => { return await orginal_fetch_api.call(api, route, options); }; - - // Intercept window drag and drop events -const originalDropHandler = document.ondrop +const originalDropHandler = document.ondrop; document.ondrop = async (e) => { - console.log('Drop event intercepted:', e) - + console.log("Drop event intercepted:", e); + // Prevent default browser behavior - e.preventDefault() - + e.preventDefault(); + // Handle files if present if (e.dataTransfer?.files?.length > 0) { - const files = Array.from(e.dataTransfer.files) - + const files = Array.from(e.dataTransfer.files); + // Send file data to parent directly as JSON - sendDirectEventToCD('file_drop', { + sendDirectEventToCD("file_drop", { files: files, x: e.clientX, y: e.clientY, - timestamp: Date.now() - }) + timestamp: Date.now(), + }); } // Call original handler if exists if (originalDropHandler) { - originalDropHandler(e) + originalDropHandler(e); } -} +}; -const originalDragEnterHandler = document.ondragenter +const originalDragEnterHandler = document.ondragenter; document.ondragenter = (e) => { // Prevent default to allow drop - e.preventDefault() - + e.preventDefault(); + // Send dragenter event to parent directly as JSON - sendDirectEventToCD('file_dragenter', { + sendDirectEventToCD("file_dragenter", { x: e.clientX, y: e.clientY, - timestamp: Date.now() - }) + timestamp: Date.now(), + }); if (originalDragEnterHandler) { - originalDragEnterHandler(e) + originalDragEnterHandler(e); } -} +}; -const originalDragLeaveHandler = document.ondragleave +const originalDragLeaveHandler = document.ondragleave; document.ondragleave = (e) => { // Prevent default to allow drop - e.preventDefault() - + e.preventDefault(); + // Send dragleave event to parent directly as JSON - sendDirectEventToCD('file_dragleave', { + sendDirectEventToCD("file_dragleave", { x: e.clientX, y: e.clientY, - timestamp: Date.now() - }) + timestamp: Date.now(), + }); if (originalDragLeaveHandler) { - originalDragLeaveHandler(e) + originalDragLeaveHandler(e); } -} +}; -const originalDragOverHandler = document.ondragover +const originalDragOverHandler = document.ondragover; document.ondragover = (e) => { // Prevent default to allow drop - e.preventDefault() - + e.preventDefault(); + // Send dragover event to parent directly as JSON - sendDirectEventToCD('file_dragover', { + sendDirectEventToCD("file_dragover", { x: e.clientX, y: e.clientY, - timestamp: Date.now() - }) + timestamp: Date.now(), + }); if (originalDragOverHandler) { - originalDragOverHandler(e) + originalDragOverHandler(e); } -} +}; // Function to create a single button function createQueueButton(config) { - const button = document.createElement('button') - button.id = `cd-button-${config.id}` - button.className = 'p-button p-component p-button-icon-only p-button-secondary p-button-text' + const button = document.createElement("button"); + button.id = `cd-button-${config.id}`; + button.className = + "p-button p-component p-button-icon-only p-button-secondary p-button-text"; button.innerHTML = `   - ` + `; button.onclick = () => { - const eventData = typeof config.eventData === 'function' ? - config.eventData() : - config.eventData || {} - sendEventToCD(config.event, eventData) - } - button.setAttribute('data-pd-tooltip', config.tooltip) - return button + const eventData = + typeof config.eventData === "function" + ? config.eventData() + : config.eventData || {}; + sendEventToCD(config.event, eventData); + }; + button.setAttribute("data-pd-tooltip", config.tooltip); + return button; } // Function to add buttons to queue group function addQueueButtons(buttonConfigs = DEFAULT_BUTTONS) { - const queueButtonGroup = document.querySelector('.queue-button-group.flex') - if (!queueButtonGroup) return + const queueButtonGroup = document.querySelector(".queue-button-group.flex"); + if (!queueButtonGroup) return; // Remove any existing CD buttons - const existingButtons = queueButtonGroup.querySelectorAll('[id^="cd-button-"]') - existingButtons.forEach(button => button.remove()) + const existingButtons = + queueButtonGroup.querySelectorAll('[id^="cd-button-"]'); + existingButtons.forEach((button) => button.remove()); // Add new buttons - buttonConfigs.forEach(config => { - const button = createQueueButton(config) - queueButtonGroup.appendChild(button) - }) -} \ No newline at end of file + buttonConfigs.forEach((config) => { + const button = createQueueButton(config); + queueButtonGroup.appendChild(button); + }); +}