From 36b9cb34fbeb1c94050e3a583b56a4e1881d29e9 Mon Sep 17 00:00:00 2001 From: Raykosan <71210053+Raykosan@users.noreply.github.com> Date: Thu, 4 Jun 2026 10:57:02 +0300 Subject: [PATCH] Fixed image loading by dragging in the RS Load Image node --- __init__.py | 2 +- package.json | 2 +- pyproject.toml | 2 +- web/spline_mask.js | 121 ++++++++++++++++++++++++++------------------- 4 files changed, 73 insertions(+), 54 deletions(-) diff --git a/__init__.py b/__init__.py index 92cf112..d71656a 100644 --- a/__init__.py +++ b/__init__.py @@ -29,7 +29,7 @@ IGNORED_MODULES: Set[str] = {'__init__.py'} REQUIRED_MODULES: Set[str] = set() PACKAGE_DISPLAY_NAME = "ComfyUI_RaykoStudio" -__version__ = "0.20.3" +__version__ = "0.20.4" def load_modules(): print(f"🦊 {PACKAGE_DISPLAY_NAME}: Version: {__version__}") diff --git a/package.json b/package.json index 976c4da..b13c780 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ComfyUI_RaykoStudio", - "version": "0.20.3", + "version": "0.20.4", "description": "Set of custom nodes for ComfyUI providing additional image processing capabilities", "author": "Raykosan", "license": "Apache License 2.0", diff --git a/pyproject.toml b/pyproject.toml index 10846a5..71b421c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "ComfyUI_RaykoStudio" description = "Set of custom nodes for ComfyUI providing additional image processing capabilities" -version = "0.20.3" +version = "0.20.4" license = { file = "LICENSE" } dependencies = ["torch>=1.7.0", "numpy>=1.19.0", "Pillow>=8.0.0", "freetype-py>=2.5.1"] diff --git a/web/spline_mask.js b/web/spline_mask.js index a7c54b0..e65769f 100644 --- a/web/spline_mask.js +++ b/web/spline_mask.js @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025-2026 Raykosan (RaykoStudio) console.log("[SPLINE 🦊] spline_mask.js LOADED!"); import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js"; @@ -13,7 +15,7 @@ app.registerExtension({ const result = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; const node = this; - // Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΡ Π΄Π°Π½Π½Ρ‹Ρ… (ΠΊΠ°ΠΊ Π² Styles Loader) + // Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΡ Π΄Π°Π½Π½Ρ‹Ρ… node.data = { selected_image: "", spline_coords: "[]" @@ -27,38 +29,26 @@ app.registerExtension({ const imageWidget = node.widgets?.find(w => w.name === "image"); const coordsWidget = node.widgets?.find(w => w.name === "coordinates"); - // Π‘ΠΊΡ€Ρ‹Π²Π°Π΅ΠΌ Π²ΠΈΠ΄ΠΆΠ΅Ρ‚Ρ‹ (ΠΊΠ°ΠΊ Π² Styles Loader) + // Π‘ΠΊΡ€Ρ‹Π²Π°Π΅ΠΌ Π²ΠΈΠ΄ΠΆΠ΅Ρ‚Ρ‹ if (imageWidget) { imageWidget.hidden = true; - if (imageWidget.element) { - imageWidget.element.style.display = "none"; - } + if (imageWidget.element) imageWidget.element.style.display = "none"; } if (coordsWidget) { coordsWidget.hidden = true; - if (coordsWidget.element) { - coordsWidget.element.style.display = "none"; - } + if (coordsWidget.element) coordsWidget.element.style.display = "none"; } // Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌ сохранСнныС Π΄Π°Π½Π½Ρ‹Π΅ ΠΈΠ· Π²ΠΈΠ΄ΠΆΠ΅Ρ‚ΠΎΠ² - if (imageWidget && imageWidget.value) { - node.data.selected_image = imageWidget.value; - } - if (coordsWidget && coordsWidget.value) { - node.data.spline_coords = coordsWidget.value; - } + if (imageWidget && imageWidget.value) node.data.selected_image = imageWidget.value; + if (coordsWidget && coordsWidget.value) node.data.spline_coords = coordsWidget.value; - // ΠŸΠ΅Ρ€Π΅ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ serializeValue для Π²ΠΈΠ΄ΠΆΠ΅Ρ‚ΠΎΠ² (ΠΊΠ°ΠΊ Π² Styles Loader) + // ΠŸΠ΅Ρ€Π΅ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅ΠΌ serializeValue для Π²ΠΈΠ΄ΠΆΠ΅Ρ‚ΠΎΠ² if (imageWidget) { - imageWidget.serializeValue = () => { - return node.data.selected_image; - }; + imageWidget.serializeValue = () => node.data.selected_image; } if (coordsWidget) { - coordsWidget.serializeValue = () => { - return node.data.spline_coords; - }; + coordsWidget.serializeValue = () => node.data.spline_coords; } // БостояниС @@ -85,29 +75,20 @@ app.registerExtension({ let _animationId = null; let _lastRect = null; - // Ѐункция синхронизации (ΠΊΠ°ΠΊ Π² Styles Loader) const syncData = () => { node.data.spline_coords = JSON.stringify(_points); - if (coordsWidget) { - coordsWidget.value = node.data.spline_coords; - } - // ΠžΡ‚ΠΌΠ΅Ρ‡Π°Π΅ΠΌ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ Π³Ρ€Π°Ρ„Π° - if (node.graph) { - node.graph.changeTracker?.dispatchEvent(new Event("change")); - } + if (coordsWidget) coordsWidget.value = node.data.spline_coords; + if (node.graph) node.graph.changeTracker?.dispatchEvent(new Event("change")); drawOverlay(); node.setDirtyCanvas(true, true); }; - // Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° изобраТСния node.loadImage = function(imagePath) { if (!imagePath || node.imageLoading) return; node.imageLoading = true; node.data.selected_image = imagePath; - if (imageWidget) { - imageWidget.value = imagePath; - } + if (imageWidget) imageWidget.value = imagePath; let filename = imagePath; let subfolder = ""; @@ -130,11 +111,7 @@ app.registerExtension({ _lastRect = null; syncPosition(); node.setDirtyCanvas(true, true); - - // ΠžΡ‚ΠΌΠ΅Ρ‡Π°Π΅ΠΌ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ Π³Ρ€Π°Ρ„Π° - if (node.graph) { - node.graph.changeTracker?.dispatchEvent(new Event("change")); - } + if (node.graph) node.graph.changeTracker?.dispatchEvent(new Event("change")); }; img.onerror = () => { node.imageLoaded = false; @@ -144,7 +121,6 @@ app.registerExtension({ img.src = imgUrl; }; - // DnD: Ѐункция Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΈΠ· Ρ„Π°ΠΉΠ»Π° (ΠΏΠ΅Ρ€Π΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ Π»ΠΎΠ³ΠΈΠΊΡƒ UPLOAD) const uploadFileAndLoad = async (file) => { if (!file || !file.type.startsWith('image/')) { console.log("[SPLINE 🦊] Not an image file"); @@ -171,8 +147,56 @@ app.registerExtension({ } return false; }; + + // βœ… ΠΠžΠ’ΠžΠ•: Π“Π»ΠΎΠ±Π°Π»ΡŒΠ½Ρ‹ΠΉ ΠΏΠ΅Ρ€Π΅Ρ…Π²Π°Ρ‚ Π΄Ρ€ΠΎΠΏΠ° с ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΎΠΉ ΠΊΠΎΠΎΡ€Π΄ΠΈΠ½Π°Ρ‚ Π½ΠΎΠ΄Ρ‹ + // Π­Ρ‚ΠΎ Ρ€Π΅ΡˆΠ°Π΅Ρ‚ ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΡƒ, ΠΊΠΎΠ³Π΄Π° _overlayCanvas скрыт (display: none) + const handleCanvasDrop = (e) => { + if (!e.dataTransfer || !e.dataTransfer.files.length) return; + + // ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅ΠΌ, находится Π»ΠΈ курсор Π½Π°Π΄ этой ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΎΠΉ Π½ΠΎΠ΄ΠΎΠΉ + const rect = app.canvas.canvas.getBoundingClientRect(); + const ds = app.canvas.ds; + const nodeX = rect.left + ((node.pos[0] + ds.offset[0]) * ds.scale); + const nodeY = rect.top + ((node.pos[1] + ds.offset[1]) * ds.scale); + const nodeW = node.size[0] * ds.scale; + const nodeH = node.size[1] * ds.scale; + + if (e.clientX >= nodeX && e.clientX <= nodeX + nodeW && + e.clientY >= nodeY && e.clientY <= nodeY + nodeH) { + + e.preventDefault(); + e.stopPropagation(); + + const file = e.dataTransfer.files[0]; + if (file && file.type.startsWith('image/')) { + console.log("[SPLINE 🦊] Canvas intercepted drop for node:", file.name); + uploadFileAndLoad(file); + } + } + }; + + const handleCanvasDragOver = (e) => { + if (!e.dataTransfer || !e.dataTransfer.files.length) return; + + const rect = app.canvas.canvas.getBoundingClientRect(); + const ds = app.canvas.ds; + const nodeX = rect.left + ((node.pos[0] + ds.offset[0]) * ds.scale); + const nodeY = rect.top + ((node.pos[1] + ds.offset[1]) * ds.scale); + const nodeW = node.size[0] * ds.scale; + const nodeH = node.size[1] * ds.scale; + + if (e.clientX >= nodeX && e.clientX <= nodeX + nodeW && + e.clientY >= nodeY && e.clientY <= nodeY + nodeH) { + e.preventDefault(); + e.stopPropagation(); + // Π’ΠΈΠ·ΡƒΠ°Π»ΡŒΠ½Ρ‹ΠΉ Ρ„ΠΈΠ΄Π±Π΅ΠΊ ΠΌΠΎΠΆΠ½ΠΎ Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ здСсь, Ссли захочСтся + } + }; + + // РСгистрируСм ΡΠ»ΡƒΡˆΠ°Ρ‚Π΅Π»ΠΈ с capture: true, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΏΠ΅Ρ€Π΅Ρ…Π²Π°Ρ‚ΠΈΡ‚ΡŒ событиС Π”Πž глобального ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠ° ComfyUI + app.canvas.canvas.addEventListener('dragover', handleCanvasDragOver, { capture: true }); + app.canvas.canvas.addEventListener('drop', handleCanvasDrop, { capture: true }); - // ΠžΡΡ‚Π°Π»ΡŒΠ½Ρ‹Π΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ node.showImageSelector = function() { const self = this; const existingMenu = document.querySelector('.spline-image-menu'); @@ -274,7 +298,6 @@ app.registerExtension({ syncData(); }; - // РасчСт ΠΏΠΎΠ·ΠΈΡ†ΠΈΠΈ изобраТСния const calculateImageRect = () => { if (!app.canvas) return null; @@ -401,7 +424,6 @@ app.registerExtension({ `; document.body.appendChild(_overlayCanvas); - // DnD: ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΈ Drag & Drop _overlayCanvas.addEventListener("dragover", (e) => { e.preventDefault(); e.stopPropagation(); @@ -424,7 +446,7 @@ app.registerExtension({ const file = e.dataTransfer.files[0]; if (file && file.type.startsWith('image/')) { - console.log("[SPLINE 🦊] DnD file:", file.name); + console.log("[SPLINE 🦊] Overlay DnD file:", file.name); await uploadFileAndLoad(file); } else if (file) { console.log("[SPLINE 🦊] Not an image file:", file.type); @@ -464,7 +486,6 @@ app.registerExtension({ } }); - // ΠŸΠΎΡΡ‚ΠΎΡΠ½Π½Ρ‹ΠΉ Ρ†ΠΈΠΊΠ» синхронизации ΠΏΠΎΠ·ΠΈΡ†ΠΈΠΈ const updateLoop = () => { if (!_overlayCanvas) return; syncPosition(); @@ -473,7 +494,6 @@ app.registerExtension({ updateLoop(); }; - // ΠžΡ‚Ρ€ΠΈΡΠΎΠ²ΠΊΠ° Π½ΠΎΠ΄Ρ‹ node.onDrawForeground = function(ctx) { if (this.flags.collapsed) return; @@ -496,7 +516,6 @@ app.registerExtension({ const availableHeight = h - startY - footerHeight - padding; const availableWidth = w - (padding * 2); - // РисуСм ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ if (this.imageLoaded && this.image) { const imgRatio = this.image.width / this.image.height; let drawW, drawH; @@ -524,7 +543,6 @@ app.registerExtension({ ctx.fillText("Select Image...", w / 2, startY + availableHeight / 2); } - // РисуСм ΠΊΠ½ΠΎΠΏΠΊΠΈ for (let i = 0; i < this.buttons.length; i++) { const btn = this.buttons[i]; btn.x = 15 + (i * (btnW + 5)); @@ -544,7 +562,6 @@ app.registerExtension({ } }; - // ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° ΠΊΠ»ΠΈΠΊΠΎΠ² ΠΏΠΎ ΠΊΠ½ΠΎΠΏΠΊΠ°ΠΌ node.onMouseMove = function(event, pos) { const [x, y] = pos; let changed = false; @@ -579,7 +596,6 @@ app.registerExtension({ syncPosition(); }; - // ВосстановлСниС ΠΏΡ€ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅ const onConfigure = node.onConfigure; node.onConfigure = function(o) { if (onConfigure) onConfigure.apply(this, arguments); @@ -595,7 +611,6 @@ app.registerExtension({ } }; - // ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° ΠΏΡ€ΠΈ ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠΈ Π½ΠΎΠ΄Ρ‹ const originalOnRemoved = node.onRemoved; node.onRemoved = function() { if (_animationId) { @@ -605,10 +620,14 @@ app.registerExtension({ if (_overlayCanvas) _overlayCanvas.remove(); const menu = document.querySelector('.spline-image-menu'); if (menu) menu.remove(); + + // βœ… ΠΠžΠ’ΠžΠ•: ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° Π³Π»ΠΎΠ±Π°Π»ΡŒΠ½Ρ‹Ρ… ΡΠ»ΡƒΡˆΠ°Ρ‚Π΅Π»Π΅ΠΉ ΠΏΡ€ΠΈ ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠΈ Π½ΠΎΠ΄Ρ‹ (ΠΏΡ€Π΅Π΄ΠΎΡ‚Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ ΡƒΡ‚Π΅Ρ‡ΠΊΠΈ памяти) + app.canvas.canvas.removeEventListener('dragover', handleCanvasDragOver, { capture: true }); + app.canvas.canvas.removeEventListener('drop', handleCanvasDrop, { capture: true }); + if (originalOnRemoved) originalOnRemoved.apply(this, arguments); }; - // Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ overlay ΠΈ Π·Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ createOverlayCanvas(); if (node.data.selected_image) { node.loadImage(node.data.selected_image);