import { api } from "../../../scripts/api.js"; import { app } from "../../../scripts/app.js"; import { allow_debug } from "./js_shared.js"; import { Shapes } from "./utils.js"; import { BaseSmartWidget, BaseSmartWidgetManager } from "./makadi/BaseSmartWidget.js"; export class LiteInfo extends BaseSmartWidget { constructor(x, y, width, height, node, text, options = {}) { super(node); this.myX = x; this.myY = y; this.width = width; this.height = height; this.originalWidth = width; this.originalHeight = height; this.originalY = y; this.radius = width / 2; this._shape = Shapes.ROUND; this.color = LiteGraph.WIDGET_BGCOLOR || "crimson"; this.outline = true; this.outlineColor = "#434343"; this.outlineWidth = 0.8; this.text = text; this.textWidth = null; this.textColor = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR || "white"; this.textYoffset = 0.7; this.textXoffset = 0.0; this.textAlign = "center"; this.textBaseline = "middle"; this.font = "14px Arial Bold"; this.previewDuration = 2000; this.isVisible = false; // Apply options if provided Object.assign(this, options); this.originalColor = this.color; // Store original color this.originalTextColor = this.textColor; // add self to the node //node.addCustomWidget(this); } handleDown() {} handleClick() {} handleMove() {} draw(ctx) { if (!this.isVisible || !this.text) return; // Draw rounded rectangle if (this.shape === Shapes.ROUND) { const radius = Math.min(this.width, this.height) / 5; // Adjust rounding level ctx.beginPath(); ctx.moveTo(this.myX + radius, this.myY); ctx.lineTo(this.myX + this.width - radius, this.myY); ctx.arcTo(this.myX + this.width, this.myY, this.myX + this.width, this.myY + radius, radius); ctx.lineTo(this.myX + this.width, this.myY + this.height - radius); ctx.arcTo( this.myX + this.width, this.myY + this.height, this.myX + this.width - radius, this.myY + this.height, radius ); ctx.lineTo(this.myX + radius, this.myY + this.height); ctx.arcTo(this.myX, this.myY + this.height, this.myX, this.myY + this.height - radius, radius); ctx.lineTo(this.myX, this.myY + radius); ctx.arcTo(this.myX, this.myY, this.myX + radius, this.myY, radius); ctx.closePath(); ctx.fillStyle = this.color; ctx.fill(); // Draw outline if enabled if (this.outline) { ctx.strokeStyle = this.outlineColor; ctx.lineWidth = this.outlineWidth; ctx.stroke(); } } // Draw text if (this.text) { ctx.fillStyle = this.textColor; ctx.font = this.font; ctx.textAlign = this.textAlign; ctx.textBaseline = this.textBaseline; ctx.fillText( this.text, this.myX + this.width / 2 + this.textXoffset, this.myY + this.height / 2 + this.textYoffset ); } } updateText(newText) { this.text = newText; } set shape(value) { this._shape = value; if (value === Shapes.CIRCLE) this.height = this.width; } get shape() { return this._shape; } } class CropWidget { constructor(node) { this.node = node; this.value = {}; this.myX = 0; this.myY = 80 + 20 + 20; this.yOffset = this.myY + 30; this.width = 50; this.height = 50; this._markDelete = false; this._mousePos = { x: 0, y: 0 }; this.color = "crimson"; this.isVisible = true; this.img = null; this.imgOffsetX = 0; this.imgOffsetY = 0; this.cropFillColor = "rgba(255, 100, 0, 0.2)"; //"rgba(255, 81, 0, 0.2)"; // NODE SETTINGS this.nodeSize = 384; node.setSize([this.nodeSize, this.nodeSize + this.myY]); node.resizable = false; this.resizeRatio = null; this.multipleFactor = 64; this.info = new LiteInfo(this.nodeSize / 2 - 40, this.myY + 5, 80, 15, node, ""); this.cropping = false; this.isCroppingDone = false; this.drawCropPreview = false; this.startX = null; this.startY = null; this.endX = null; this.endY = null; this.resizing = null; // Track which handle is being resized this.resizeThreshold = 10; // Sensitivity for detecting resize handles if (node.widgets_values && node.widgets_values[4]) { if (allow_debug) console.log("node has crop data value"); this.value = node.widgets_values[4]; this.node.properties.value = node.widgets_values[4]; } else { if (allow_debug) console.log("node does not has crop data value"); } if (node.properties.value) { this.loadCropData(); this.cropNewImage(); } else { node.properties.value = {}; } this.adjustNewImageSize(); // init this.setResizeRatio(); // init ratio // ON Ratio Changed this.node.widgets[0].callback = () => { this.setResizeRatio(); }; // ON Grid Step Changed this.node.widgets[1].callback = () => { this.adjustMultipleFactor(); }; // ON Image Changed const originalOnChanged = this.node.widgets[1].callback; this.node.widgets[1].callback = async (img) => { originalOnChanged.apply(this); this.img = null; //this.resetCroppingData(); // disabled for (let i = 0; i < 30 && this.img !== this.node.imgs[0]; i++) { await new Promise((resolve) => setTimeout(resolve, 100)); } if (this.img !== this.node.imgs[0]) { if (allow_debug) console.log("Timeout waiting for image sync"); return; } this.cropNewImage(); }; this.node.onMouseUp = (e) => { this.hideInfo(); }; this.node.onMouseLeave = (e) => {}; } loadCropData() { this.startX = this.node.properties.value.startX; this.startY = this.node.properties.value.startY; this.endX = this.node.properties.value.endX; this.endY = this.node.properties.value.endY; //if(allow_debug) console.log('loaded',this.node.properties.value); this.isCroppingDone = true; this.node.setDirtyCanvas(true, true); } saveCropData() { if (this.startX && this.endX && this.startY && this.endY) { this.value.startX = this.startX; this.value.startY = this.startY; this.value.endX = this.endX; this.value.endY = this.endY; this.node.properties.value.startX = this.startX; this.node.properties.value.startY = this.startY; this.node.properties.value.endX = this.endX; this.node.properties.value.endY = this.endY; if (allow_debug) console.log("crop data saved"); } } autoPinNode() { const safeZone = 50; const { x, y } = this.mousePos; if (y > 50 && y < this.node.height + safeZone) { if (x > this.node.width + safeZone || x < -safeZone) { this.node.flags.pinned = false; } else { this.node.flags.pinned = true; } } else { this.node.flags.pinned = false; } } resetCroppingData() { this.croppedImage = null; this.value = {}; this.value.data = null; this.cropping = false; this.isCroppingDone = false; this.startX = null; this.startY = null; this.endX = null; this.endY = null; this.resizing = null; } getResizeHandle(x, y) { const handleSize = 10; const cropX = Math.min(this.startX, this.endX); const cropY = Math.min(this.startY, this.endY); const cropW = Math.abs(this.endX - this.startX); const cropH = Math.abs(this.endY - this.startY); // Check corners if (x >= cropX - handleSize && x <= cropX + handleSize && y >= cropY - handleSize && y <= cropY + handleSize) { return "top-left"; } if ( x >= cropX + cropW - handleSize && x <= cropX + cropW + handleSize && y >= cropY - handleSize && y <= cropY + handleSize ) { return "top-right"; } if ( x >= cropX - handleSize && x <= cropX + handleSize && y >= cropY + cropH - handleSize && y <= cropY + cropH + handleSize ) { return "bottom-left"; } if ( x >= cropX + cropW - handleSize && x <= cropX + cropW + handleSize && y >= cropY + cropH - handleSize && y <= cropY + cropH + handleSize ) { return "bottom-right"; } // Only check midpoints if the ratio is set to "free" if (this.resizeRatio === null) { if ( x >= cropX + cropW / 2 - handleSize && x <= cropX + cropW / 2 + handleSize && y >= cropY - handleSize && y <= cropY + handleSize ) { return "top-mid"; } if ( x >= cropX + cropW / 2 - handleSize && x <= cropX + cropW / 2 + handleSize && y >= cropY + cropH - handleSize && y <= cropY + cropH + handleSize ) { return "bottom-mid"; } if ( x >= cropX - handleSize && x <= cropX + handleSize && y >= cropY + cropH / 2 - handleSize && y <= cropY + cropH / 2 + handleSize ) { return "left-mid"; } if ( x >= cropX + cropW - handleSize && x <= cropX + cropW + handleSize && y >= cropY + cropH / 2 - handleSize && y <= cropY + cropH / 2 + handleSize ) { return "right-mid"; } } return null; } adjustNewImageSize() { const img = this.node.imgs[0]; if (this.img === img) return; // Avoid redundant assignment this.img = img; const longestSide = Math.max(img.width, img.height); const scale = this.nodeSize / longestSide; this.width = img.width * scale; this.height = img.height * scale; // Center the image correctly this.imgOffsetX = (this.node.width - this.width) / 2; this.imgOffsetY = this.myY + (this.nodeSize - this.height) / 2; this.node.setDirtyCanvas(true, true); } setResizeRatio() { function ratioToFraction(ratio) { if (ratio === "free") return null; if (ratio === "grid") return null; let [num, den] = ratio.split(":").map(Number); return num / den; } const ratio = ratioToFraction(this.node.widgets[0].value); this.resizeRatio = ratio; } draw(ctx) { if (!this.isVisible) return; // // Clear original image // ctx.clearRect(this.myX, this.myY, this.node.width, this.node.height - this.yOffset); // // Fill with background color // ctx.fillStyle = this.node.bgcolor; // ctx.fillRect(this.myX, this.myY, this.node.width, this.node.height - this.yOffset); // Draw loaded image this.adjustNewImageSize(); ctx.drawImage(this.img, this.imgOffsetX, this.imgOffsetY, this.width, this.height); // Draw crop rect fill area if (this.cropping || this.isCroppingDone) { // Calculate the crop area boundaries const cropX = Math.min(this.startX, this.endX); const cropY = Math.min(this.startY, this.endY); const cropW = Math.abs(this.endX - this.startX); const cropH = Math.abs(this.endY - this.startY); ctx.fillStyle = this.cropFillColor; ctx.fillRect(cropX, cropY, cropW, cropH); // Draw a dashed outline around the crop rect ctx.strokeStyle = "#333"; ctx.lineWidth = 1.5; ctx.setLineDash([5, 5]); ctx.strokeRect(cropX, cropY, cropW, cropH); // Draw handles at corners and midpoints const handleSize = 10; ctx.strokeStyle = "#333"; ctx.lineWidth = 1.5; ctx.setLineDash([]); // Reset dash pattern for solid lines // Corner handles ctx.strokeRect(cropX - handleSize / 2, cropY - handleSize / 2, handleSize, handleSize); // Top-left ctx.strokeRect(cropX + cropW - handleSize / 2, cropY - handleSize / 2, handleSize, handleSize); // Top-right ctx.strokeRect(cropX - handleSize / 2, cropY + cropH - handleSize / 2, handleSize, handleSize); // Bottom-left ctx.strokeRect(cropX + cropW - handleSize / 2, cropY + cropH - handleSize / 2, handleSize, handleSize); // Bottom-right // Only draw midpoint handles if the ratio is set to "free" if (this.resizeRatio === null) { ctx.strokeRect(cropX + cropW / 2 - handleSize / 2, cropY - handleSize / 2, handleSize, handleSize); // Top-mid ctx.strokeRect(cropX + cropW / 2 - handleSize / 2, cropY + cropH - handleSize / 2, handleSize, handleSize); // Bottom-mid ctx.strokeRect(cropX - handleSize / 2, cropY + cropH / 2 - handleSize / 2, handleSize, handleSize); // Left-mid ctx.strokeRect(cropX + cropW - handleSize / 2, cropY + cropH / 2 - handleSize / 2, handleSize, handleSize); // Right-mid } } if (this.croppedImage && this.drawCropPreview && !this.cropping && !this.resizing) { // Calculate the crop area dimensions const cropWidth = Math.abs(this.endX - this.startX); const cropHeight = Math.abs(this.endY - this.startY); // Define a maximum size for the preview (e.g., 100x100 pixels) const maxPreviewSize = 100; // Calculate the aspect ratio of the crop area const aspectRatio = cropWidth / cropHeight; // Determine the preview dimensions while maintaining the aspect ratio let croppedImageWidth, croppedImageHeight; if (cropWidth > cropHeight) { croppedImageWidth = maxPreviewSize; croppedImageHeight = maxPreviewSize / aspectRatio; } else { croppedImageHeight = maxPreviewSize; croppedImageWidth = maxPreviewSize * aspectRatio; } // Define the position for the cropped image in the left corner const croppedImageX = this.myX + 10; // 10 pixels from the left edge of the node const croppedImageY = this.myY + 10; // 10 pixels from the top edge of the node try { // Draw the cropped image ctx.drawImage(this.croppedImage, croppedImageX, croppedImageY, croppedImageWidth, croppedImageHeight); } catch (error) {} } this.info.draw(ctx); } cropNewImage() { if (!this.img) { this.adjustNewImageSize(); } // Calculate the scale factors for the image const scaleX = this.img.naturalWidth / this.width; const scaleY = this.img.naturalHeight / this.height; // Calculate the crop area in the original image coordinates const cropX = (Math.min(this.startX, this.endX) - this.imgOffsetX) * scaleX; const cropY = (Math.min(this.startY, this.endY) - this.imgOffsetY) * scaleY; const cropWidth = Math.abs(this.endX - this.startX) * scaleX; const cropHeight = Math.abs(this.endY - this.startY) * scaleY; // Create a canvas to hold the cropped image const canvas = document.createElement("canvas"); canvas.width = cropWidth; canvas.height = cropHeight; const ctx = canvas.getContext("2d"); // Cash values this.cropWidth = cropWidth; this.cropHeight = cropHeight; // Draw the cropped portion of the image onto the canvas ctx.drawImage( this.img, cropX, cropY, cropWidth, cropHeight, // Source rectangle (cropped area) 0, 0, cropWidth, cropHeight // Destination rectangle (canvas) ); // Create a new image from the canvas and store it this.croppedImage = new Image(); this.croppedImage.src = canvas.toDataURL(); // for python if (this.croppedImage) { if ( this.croppedImage.src === "data:;" || this.croppedImage.src.startsWith("data:,") || this.croppedImage.src.length < 50 ) { if (allow_debug) console.log("Image has small or empty data."); this.name = "crop"; this.value.data = null; this.isCroppingDone = false; } else { this.name = "crop"; this.value.data = this.croppedImage.src; } } if (allow_debug) console.log("cropped image"); } handleDown(e) { const { x, y } = this.mousePos; this.adjustMultipleFactor(); // if (this.isMouseOnImage()) { // if (allow_debug) console.log("mouse in"); // } else { // if (allow_debug)console.log("mouse out"); // } // Swap startX/endX and startY/endY if necessary during drawing if (this.startX > this.endX) { [this.startX, this.endX] = [this.endX, this.startX]; } if (this.startY > this.endY) { [this.startY, this.endY] = [this.endY, this.startY]; } if (this.isMouseInCropArea()) { const cropX = Math.min(this.startX, this.endX); const cropY = Math.min(this.startY, this.endY); const cropW = Math.abs(this.endX - this.startX); const cropH = Math.abs(this.endY - this.startY); if (allow_debug) { console.log("mouse in crop area"); } this.dragging = true; this.dragOffsetX = x - cropX; this.dragOffsetY = y - cropY; return; } // Check if the mouse is over a resize handle if (this.isCroppingDone && this.getResizeHandle(x, y)) { this.resizing = this.getResizeHandle(x, y); // Start resizing } else if (!this.cropping && this.isMouseOnImage()) { this.cropping = true; this.isCroppingDone = false; this.startX = x; this.startY = y; this.endX = x; this.endY = y; } } handleMove(ctx) { this.autoPinNode(); const { x, y } = this.mousePos; // Helper function to round a number to the nearest multiple of const roundToMultipleOf = (value) => { return Math.round(value / this.multipleFactor) * this.multipleFactor; }; if (this.cropping || this.resizing) { if (this.resizing) { let newWidth, newHeight; const isCornerResizing = this.resizing === "top-left" || this.resizing === "top-right" || this.resizing === "bottom-left" || this.resizing === "bottom-right"; const shouldMaintainRatio = !["free", "grid"].includes(this.node.widgets[0].value); // Calculate new dimensions based on mouse movement if (this.resizing.includes("left") || this.resizing.includes("right")) { newWidth = Math.abs(this.resizing.includes("left") ? this.endX - x : x - this.startX); if (shouldMaintainRatio) { // For corners with aspect ratio, calculate height based on width newHeight = newWidth / this.resizeRatio; } } if (this.resizing.includes("top") || this.resizing.includes("bottom")) { newHeight = Math.abs(this.resizing.includes("top") ? this.endY - y : y - this.startY); if (shouldMaintainRatio) { // For corners with aspect ratio, calculate width based on height newWidth = newHeight * this.resizeRatio; } } // Round width and height to the nearest multiple of this.multipleFactor newWidth = roundToMultipleOf(newWidth); newHeight = roundToMultipleOf(newHeight); // Ensure the new dimensions respect the image boundaries if (this.resizing.includes("left")) { newWidth = Math.min(newWidth, this.endX - this.imgOffsetX); } else if (this.resizing.includes("right")) { newWidth = Math.min(newWidth, this.imgOffsetX + this.width - this.startX); } if (this.resizing.includes("top")) { newHeight = Math.min(newHeight, this.endY - this.imgOffsetY); } else if (this.resizing.includes("bottom")) { newHeight = Math.min(newHeight, this.imgOffsetY + this.height - this.startY); } // For corner resizing with ratio maintenance, choose the constraining dimension if (shouldMaintainRatio) { // Calculate which dimension is more constrained const widthRatio = newWidth / (newHeight * this.resizeRatio); if (widthRatio < 1) { // Width is more constrained, adjust height accordingly newHeight = newWidth / this.resizeRatio; } else { // Height is more constrained, adjust width accordingly newWidth = newHeight * this.resizeRatio; } // Re-round after aspect ratio adjustment newWidth = roundToMultipleOf(newWidth); newHeight = roundToMultipleOf(newHeight); } // Apply the new dimensions for the resizing handle switch (this.resizing) { case "top-left": this.startX = this.endX - newWidth; this.startY = this.endY - newHeight; break; case "top-right": this.endX = this.startX + newWidth; this.startY = this.endY - newHeight; break; case "bottom-left": this.startX = this.endX - newWidth; this.endY = this.startY + newHeight; break; case "bottom-right": this.endX = this.startX + newWidth; this.endY = this.startY + newHeight; break; case "top-mid": this.startY = this.endY - newHeight; break; case "bottom-mid": this.endY = this.startY + newHeight; break; case "left-mid": this.startX = this.endX - newWidth; break; case "right-mid": this.endX = this.startX + newWidth; break; } // Ensure the crop area stays within the image boundaries this.startX = Math.max(this.imgOffsetX, this.startX); this.startY = Math.max(this.imgOffsetY, this.startY); this.endX = Math.min(this.imgOffsetX + this.width, this.endX); this.endY = Math.min(this.imgOffsetY + this.height, this.endY); this.showInfo(); } else { this.adjustMultipleFactor(); // Calculate the new endX and endY based on the mouse position let newEndX = roundToMultipleOf(Math.max(this.imgOffsetX, Math.min(this.imgOffsetX + this.width, x))); let newEndY = roundToMultipleOf(Math.max(this.imgOffsetY, Math.min(this.imgOffsetY + this.height, y))); // Calculate the width and height of the crop area let cropWidth = newEndX - this.startX; let cropHeight = newEndY - this.startY; // Round the width and height to the nearest multiple of this.multipleFactor cropWidth = roundToMultipleOf(cropWidth); cropHeight = roundToMultipleOf(cropHeight); // Adjust the endX and endY to maintain the rounded width and height this.endX = this.startX + cropWidth; this.endY = this.startY + cropHeight; // Ensure the crop area stays within the image boundaries this.endX = Math.min(this.endX, this.imgOffsetX + this.width); this.endY = Math.min(this.endY, this.imgOffsetY + this.height); this.showInfo(); } } if (this.dragging) { const cropW = Math.round(Math.abs(this.endX - this.startX)); const cropH = Math.round(Math.abs(this.endY - this.startY)); let newStartX = Math.round(Math.max(this.imgOffsetX, x - this.dragOffsetX)); let newStartY = Math.round(Math.max(this.imgOffsetY, y - this.dragOffsetY)); // Clamp to ensure the crop box stays within image boundaries newStartX = Math.round(Math.min(newStartX, this.imgOffsetX + this.width - cropW)); newStartY = Math.round(Math.min(newStartY, this.imgOffsetY + this.height - cropH)); this.startX = newStartX; this.startY = newStartY; this.endX = this.startX + cropW; this.endY = this.startY + cropH; return; } } showInfo() { this.info.isVisible = true; // Calculate the scale factors for the image const scaleX = this.img.naturalWidth / this.width; const scaleY = this.img.naturalHeight / this.height; // Calculate the crop area in the original image coordinates const cropWidth = Math.round(Math.abs(this.endX - this.startX) * scaleX); const cropHeight = Math.round(Math.abs(this.endY - this.startY) * scaleY); this.info.text = `${cropWidth} x ${cropHeight}`; } adjustMultipleFactor() { const mode = this.node.widgets[0].value; if (allow_debug) console.log("mode", mode); if (mode === "grid") { const value = this.node.widgets[1].value; // Calculate the scale factors for the image const scaleX = this.img.naturalWidth / this.width; const scaleY = this.img.naturalHeight / this.height; // Calculate the crop area in the original image coordinates const cropWidth = Math.floor(Math.abs(this.endX - this.startX) * scaleX); const cropHeight = Math.floor(Math.abs(this.endY - this.startY) * scaleY); // Adjust the multipleFactor based on the user's input and the scaling factors // The multipleFactor should be such that when multiplied by the scaling factor, // it results in the desired multiple in the true output image. this.multipleFactor = value / Math.min(scaleX, scaleY); } else { this.multipleFactor = 1; } } handleClick(e) { if (this.isMouseOnImage()) { if (this.startX && this.endX && this.startY && this.endY) { this.isCroppingDone = true; this.cropNewImage(); this.saveCropData(); } // Reset resizing state this.cropping = false; this.resizing = null; this.dragging = false; } } isMouseDown() { return app.canvas.pointer.isDown; } hideInfo() { this.info.isVisible = true; setTimeout(() => { this.info.isVisible = false; }, 2000); } isMouseInCropArea(margin = 5) { const { x, y } = this.mousePos; const cropX = Math.min(this.startX, this.endX) + margin; const cropY = Math.min(this.startY, this.endY) + margin; const cropW = Math.abs(this.endX - this.startX) - 2 * margin; const cropH = Math.abs(this.endY - this.startY) - 2 * margin; return x >= cropX && x <= cropX + cropW && y >= cropY && y <= cropY + cropH; } isMouseOnImage() { const { x, y } = this.mousePos; return ( x >= this.imgOffsetX && x <= this.imgOffsetX + this.width && y >= this.imgOffsetY && y <= this.imgOffsetY + this.height ); } isMouseInResizeArea() { const { x: mouseX, y: mouseY } = this.mousePos; const threshold = this.resizeThreshold; const cropX = Math.min(this.startX, this.endX); const cropY = Math.min(this.startY, this.endY); const cropW = Math.abs(this.endX - this.startX); const cropH = Math.abs(this.endY - this.startY); const dx = mouseX - (cropX + cropW / 2); const dy = mouseY - (cropY + cropH / 2); const radianAngle = -(this.rotationAngle * Math.PI) / 180; const cosA = Math.cos(radianAngle); const sinA = Math.sin(radianAngle); const rotatedX = dx * cosA - dy * sinA; const rotatedY = dx * sinA + dy * cosA; const rx = rotatedX + cropW / 2; const ry = rotatedY + cropH / 2; const topLeft = rx >= -threshold && rx <= threshold && ry >= -threshold && ry <= threshold; const topRight = rx >= cropW - threshold && rx <= cropW + threshold && ry >= -threshold && ry <= threshold; const bottomLeft = rx >= -threshold && rx <= threshold && ry >= cropH - threshold && ry <= cropH + threshold; const bottomRight = rx >= cropW - threshold && rx <= cropW + threshold && ry >= cropH - threshold && ry <= cropH + threshold; const top = rx >= 0 && rx <= cropW && ry >= -threshold && ry <= threshold; const bottom = rx >= 0 && rx <= cropW && ry >= cropH - threshold && ry <= cropH + threshold; const left = rx >= -threshold && rx <= threshold && ry >= 0 && ry <= cropH; const right = rx >= cropW - threshold && rx <= cropW + threshold && ry >= 0 && ry <= cropH; return topLeft || topRight || bottomLeft || bottomRight || top || bottom || left || right; } isLowQuality() { const canvas = app.canvas; const scale = canvas?.ds?.scale ?? 1; // Get scale, default to 1 if undefined return scale <= 0.5; } delete() { this.node.widgets = this.node.widgets.filter((widget) => widget !== this); this.node.setDirtyCanvas(true, true); } get markDelete() { return this._markDelete; } set markDelete(value) { this._markDelete = Boolean(value); } get mousePos() { const graphMouse = app.canvas.graph_mouse; return { x: graphMouse[0] - this.node.pos[0], y: graphMouse[1] - this.node.pos[1], }; } } app.registerExtension({ name: "iTools.cropImage", async beforeRegisterNodeDef(nodeType, nodeData, app) { if (nodeData.name !== "iToolsCropImage") { return; } // const originalOnExecuted = nodeType.prototype.onExecuted; // nodeType.prototype.onExecuted = async function (message) { // originalOnExecuted?.apply(this, arguments); // if(allow_debug) console.log('iToolsCropImage executed',message); // } }, async nodeCreated(node) { if (node.comfyClass !== "iToolsCropImage") { return; } // Wait for init for (let i = 0; i < 30 && node.graph === null; i++) { await new Promise((resolve) => setTimeout(resolve, 100)); } if (node.graph === null) { if (allow_debug) console.log("Timeout waiting for graph initialization"); return; } // Await for ImagePreviewWidget const previewWidget = node.widgets.find((widget) => (widget.type == "custom")); for (let i = 0; i < 30 && !previewWidget; i++) { if (allow_debug) console.log("wait...", i); await new Promise((resolve) => setTimeout(resolve, 100)); } if (!previewWidget) { if (allow_debug) console.log("ImagePreviewWidget not found"); } if(allow_debug) console.log('previewWidget',previewWidget); // Override draw function in ImagePreviewWidget const originalDraw = previewWidget.draw; if(allow_debug) console.log('originalDraw',originalDraw); previewWidget.draw = function (ctx, node, widget_width, y, widget_height, ...args) {}; node.setDirtyCanvas(true, true); //START POINT const cropPreview = new CropWidget(node); node.addCustomWidget(cropPreview); // if (allow_debug) console.log("cropPreviewNode", node); const originalOnExecuted = node.onExecuted; node.onExecuted = async function (message) { originalOnExecuted?.apply(this, arguments); if (allow_debug) console.log("iToolsCropImage executed", message); }; node.clone = () => { console.warn("Cloning is disabled for this node."); return null; }; node.onRemoved = () => { node.widgets = [] }; // Store the original onMouseDown handler const originalOnMouseDown = app.canvas.onMouseDown; app.canvas.onMouseDown = (e) => { // Call original handler if it exists if (originalOnMouseDown) { originalOnMouseDown.call(app.canvas, e); } const nodes = app.graph.nodes; nodes.forEach((n) => { if (n.type === "iToolsCropImage") { n.widgets.forEach((w) => { w.handleDown?.(e); }); } }); }; // Store the original onDrawForeground handler const originalOnDrawForeground = app.canvas.onDrawForeground; app.canvas.onDrawForeground = (ctx) => { // Call original handler if it exists if (originalOnDrawForeground) { originalOnDrawForeground.call(app.canvas, ctx); } const nodes = app.graph.nodes; nodes.forEach((n) => { if (n.type === "iToolsCropImage") { n.widgets.forEach((w) => { w.handleMove?.(ctx); }); } }); }; // Store the original onclick handler const originalOnClick = app.canvas.canvas.onclick; app.canvas.canvas.onclick = (e) => { // Call original handler if it exists if (originalOnClick) { originalOnClick.call(app.canvas.canvas, e); } const nodes = app.graph.nodes; nodes.forEach((n) => { if (n.type === "iToolsCropImage") { n.widgets.forEach((w) => { w.handleClick?.(e); }); } }); }; app.canvas.onMouseUp = (e) => { if (allow_debug) { console.log("mouse up"); } }; }, });