From ffe5e2628bd74a629e8f643a5ade00106a68e327 Mon Sep 17 00:00:00 2001 From: AlekPet Date: Thu, 5 Jun 2025 15:30:01 +0300 Subject: [PATCH 1/7] start create tools Crop. --- PainterNode/js/painter_node.js | 234 ++++++++++++++++++++++++++------- 1 file changed, 187 insertions(+), 47 deletions(-) diff --git a/PainterNode/js/painter_node.js b/PainterNode/js/painter_node.js index b16c751..6bca4ee 100644 --- a/PainterNode/js/painter_node.js +++ b/PainterNode/js/painter_node.js @@ -47,6 +47,12 @@ const removeIcon = const removeImg = makeElement("img"); removeImg.src = removeIcon; +const cropIcon = + "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.0' width='300.000000pt' height='300.000000pt' viewBox='0 0 300.000000 300.000000' preserveAspectRatio='xMidYMid meet'%3E%3Crect xmlns='http://www.w3.org/2000/svg' x='0' y='0' width='300' height='300' style='fill: rgb(0, 0, 0);' rx='58.194' ry='58.194'/%3E%3Cg transform='translate(0.000000,300.000000) scale(0.100000,-0.100000)' fill='%23ffffff' stroke='none'%3E%3Cpath d='M664 2836 l-34 -34 0 -1052 0 -1052 34 -34 34 -34 1052 0 1052 0 34 34 c30 30 34 40 34 86 0 46 -4 56 -34 86 l-34 34 -966 0 -966 0 0 966 0 966 -34 34 c-30 30 -40 34 -86 34 -46 0 -56 -4 -86 -34z m137 -25 l24 -19 5 -957 5 -957 28 -24 28 -24 945 0 945 0 24 -25 c33 -32 33 -78 0 -110 l-24 -25 -1031 0 -1031 0 -24 25 -25 24 0 1031 0 1031 25 24 c29 30 74 32 106 6z'/%3E%3Cpath d='M164 2336 c-50 -50 -50 -122 0 -172 l34 -34 106 0 c123 0 155 11 180 64 20 43 20 69 0 112 -25 53 -57 64 -180 64 l-106 0 -34 -34z m258 -20 c27 -20 39 -62 27 -93 -17 -45 -37 -52 -137 -53 -86 0 -95 2 -117 25 -16 15 -25 36 -25 55 0 19 9 40 25 55 22 23 31 25 117 25 60 -1 99 -5 110 -14z'/%3E%3Cpath d='M1059 2355 c-29 -15 -59 -70 -59 -105 0 -36 31 -90 61 -105 23 -13 112 -15 549 -15 l520 0 0 -520 c0 -576 -1 -563 64 -594 43 -20 69 -20 112 0 66 31 64 9 64 680 l0 606 -34 34 -34 34 -609 0 c-506 -1 -612 -3 -634 -15z m1246 -50 l25 -24 0 -591 c0 -359 -4 -598 -10 -609 -5 -10 -23 -24 -40 -31 -37 -15 -81 0 -99 33 -8 15 -12 175 -14 529 l-2 507 -23 23 -23 23 -507 2 c-354 2 -514 6 -529 14 -33 18 -48 62 -33 99 7 17 20 35 29 40 9 6 255 10 609 10 l593 0 24 -25z'/%3E%3Cpath d='M2192 483 c-51 -25 -62 -57 -62 -179 l0 -106 34 -34 c30 -30 40 -34 86 -34 46 0 56 4 86 34 l34 34 0 106 c0 123 -11 155 -64 180 -43 20 -72 20 -114 -1z m116 -56 c20 -18 22 -30 22 -114 0 -86 -2 -96 -25 -118 -32 -33 -78 -33 -110 0 -23 22 -25 31 -25 117 1 99 8 120 51 137 30 12 57 5 87 -22z'/%3E%3C/g%3E%3C/svg%3E"; + +const cropImg = makeElement("img"); +cropImg.src = cropIcon; + const convertIdClass = (text) => text.replaceAll(".", "_"); function renderIcon(icon) { @@ -68,6 +74,12 @@ function removeObject(eventData, transform) { this.viewListObjects(this.list_objects_panel__items); } +// [Crop] Crop icons +function cropIconClick(eventData, transform) { + this.cropCanvas(); + console.log("Crop"); +} + function resizeCanvas(node, sizes) { const { width, height } = sizes ?? @@ -140,6 +152,9 @@ class Painter { this.storageCls = this.node.storageCls; + // [Crop] Object + this.crop_object = null; + this.fonts = { Arial: { type: "default" }, "Times New Roman": { type: "default" }, @@ -292,6 +307,7 @@ class Painter { +
@@ -631,7 +647,7 @@ class Painter { this.canvas.requestRenderAll(); this.addToHistory(); - this.saveSettingsPainterNode(true); + this.saveSettingsPainterNode(); this.uploadPaintFile(this.node.name); } @@ -719,6 +735,9 @@ class Painter { nextElement = target.parentElement.nextElementSibling, panelListObjects = target.nextElementSibling; + // [Crop] Crop reset + this.cropReset(); + if (["Image", "Textbox"].includes(this.type)) { this.drawning = true; } @@ -922,16 +941,19 @@ class Painter { }) { let shape = null; - if (type == "Rect") { + if (type === "Rect") { shape = new fabric.Rect(); - } else if (type == "Circle") { + } else if (type === "Circle") { shape = new fabric.Circle(); - } else if (type == "Triangle") { + } else if (type === "Triangle") { shape = new fabric.Triangle(); - } else if (type == "Line") { + } else if (type === "Line") { shape = new fabric.Line(points); - } else if (type == "Path") { + } else if (type === "Path") { shape = new fabric.Path(path); + } else if (type === "Crop") { + // [Crop] Create crop object + return this.createCrop(left, top); } Object.assign(shape, { @@ -952,6 +974,72 @@ class Painter { return shape; } + // [Crop] Crop canvas + cropReset() { + if (this.crop_object) { + this.canvas.remove(this.crop_object); + this.crop_object = null; + } + } + + createCrop(left, top) { + this.crop_object = new fabric.Rect(); + Object.assign(this.crop_object, { + left: left, + top: top, + originX: "left", + originY: "top", + strokeWidth: 1, + stroke: "#ccc", + transparentCorners: false, + hasBorders: false, + hasControls: true, + fill: "transparent", + strokeDashArray: [5, 5], + absolutePositioned: true, + name: "crop", + }); + + return this.crop_object; + } + + cropCanvas() { + this.crop_object.visible = false; + const croppedImg = new Image(); + + croppedImg.onload = () => { + this.canvas.clear(); + this.canvas.backgroundColor = this.bgColor.value || "#000000"; + this.canvas.requestRenderAll(); + + const image = new fabric.Image(croppedImg); + image.left = this.crop_object.left; + image.top = this.crop_object.top; + + if (confirm("Resize canvas to new size clip?")) { + image.left = 0; + image.top = 0; + this.setCanvasSize(this.crop_object.width, this.crop_object.height); + } + + this.cropReset(); + + image.setCoords(); + this.canvas.add(image); + this.canvas.sendToBack(image); + this.canvas.renderAll(); + this.saveSettingsPainterNode(); + this.uploadPaintFile(this.node.name); + }; + + croppedImg.src = this.canvas.toDataURL({ + left: this.crop_object.left, + top: this.crop_object.top, + width: this.crop_object.width, + height: this.crop_object.height, + }); + } + // Toolbars createFontToolbar() { const property_textbox = makeElement("div", { @@ -1158,7 +1246,9 @@ class Painter { { width: new_width, height: new_height } ); - this.node.title = `${this.node.type} - ${new_width}x${new_height}`; + this.node.title = `${this.node.type} - ${Math.floor( + new_width + )}x${Math.floor(new_height)}`; this.canvas.renderAll(); app.graph.setDirtyCanvas(true, false); this.node.onResize(); @@ -1181,6 +1271,11 @@ class Painter { if (currentTarget) { this.type = currentTarget; + // [Crop] Crop reset + if (currentTarget !== "Crop") { + this.cropReset(); + } + // Set default brush width if width < 1 (for fabricjs) this.setDefaultValuesInputs(); @@ -1606,6 +1701,9 @@ class Painter { this.fillColor.onchange = this.fillColorTransparent.onchange = () => { + // [Crop] + if (this.type === "Crop") return; + if (this.canvas.getActiveObject()) { this.uploadPaintFile(this.node.name); } @@ -1619,6 +1717,9 @@ class Painter { }; this.strokeWidth.onchange = () => { + // [Crop] + if (this.type === "Crop") return; + if ( ["Brush", "BrushMyPaint", "BrushSymmetry", "Textbox", "Image"].includes( this.type @@ -1637,6 +1738,14 @@ class Painter { let targets = this.canvas.getActiveObjects(); if (!targets || targets.length == 0) return; + // [Crop] + if ( + this.type === "Crop" && + targets.filter((t) => t?.name === "crop").length + ) { + return; + } + // Selected tools const setProps = (style, value) => { const propEl = this.painter_drawning_box_property.querySelector( @@ -1732,30 +1841,43 @@ class Painter { ) return; - if (this.type != "Textbox") { - let { x: left, y: top } = this.canvas.getPointer(o.e), - colors = ["red", "blue", "green", "yellow", "purple", "orange"], - strokeWidth = +this.strokeWidth.value, - stroke = - strokeWidth == 0 - ? "transparent" - : toRGBA( - this.strokeColor.value, - this.strokeColorTransparent.value - ) || colors[Math.floor(Math.random() * colors.length)], - fill = toRGBA( - this.fillColor.value, - this.fillColorTransparent.value - ), - shape = this.shapeCreate({ - type: this.type, - left, - top, - stroke, - fill, - strokeWidth, - points: [left, top, left, top], - }); + if (this.type !== "Textbox") { + const { x: left, y: top } = this.canvas.getPointer(o.e); + + // [Crop] Mouse down + if (this.type === "Crop") { + if (this.crop_object) { + this.crop_object.set({ left, top }); + this.crop_object.visible = true; + this.crop_object.maxPosX = left; + this.crop_object.maxPosY = top; + this.canvas.setActiveObject(this.crop_object); + return; + } + } + + const colors = ["red", "blue", "green", "yellow", "purple", "orange"]; + const strokeWidth = +this.strokeWidth.value; + const stroke = + strokeWidth == 0 + ? "transparent" + : toRGBA( + this.strokeColor.value, + this.strokeColorTransparent.value + ) || colors[Math.floor(Math.random() * colors.length)]; + const fill = toRGBA( + this.fillColor.value, + this.fillColorTransparent.value + ); + const shape = this.shapeCreate({ + type: this.type, + left, + top, + stroke, + fill, + strokeWidth, + points: [left, top, left, top], + }); this.originX = left; this.originY = top; @@ -1805,7 +1927,7 @@ class Painter { activeObj.set({ top: pointer.y }); } - if (this.type == "Circle") { + if (this.type === "Circle") { let radius = Math.max( Math.abs(this.originY - pointer.y), @@ -1814,7 +1936,7 @@ class Painter { if (radius > activeObj.strokeWidth) radius -= activeObj.strokeWidth / 2; activeObj.set({ radius: radius }); - } else if (this.type == "Line") { + } else if (this.type === "Line") { activeObj.set({ x2: pointer.x, y2: pointer.y }); } else { activeObj.set({ width: Math.abs(this.originX - pointer.x) }); @@ -1828,19 +1950,36 @@ class Painter { "mouse:up": (o) => { this.canvas._objects.forEach((object) => { if (!object.hasOwnProperty("controls")) { - object.controls = { - ...object.controls, - removeControl: new fabric.Control({ - x: 0.5, - y: -0.5, - offsetY: -16, - offsetX: 16, - cursorStyle: "pointer", - mouseUpHandler: removeObject.bind(this), - render: renderIcon(removeImg), - cornerSize: 24, - }), - }; + // [Crop] Controls icon + if (this.type === "Crop") { + object.controls = { + ...object.controls, + removeControl: new fabric.Control({ + x: 0.5, + y: -0.5, + offsetY: -16, + offsetX: 16, + cursorStyle: "pointer", + mouseDownHandler: cropIconClick.bind(this), + render: renderIcon(cropImg), + cornerSize: 24, + }), + }; + } else { + object.controls = { + ...object.controls, + removeControl: new fabric.Control({ + x: 0.5, + y: -0.5, + offsetY: -16, + offsetX: 16, + cursorStyle: "pointer", + mouseUpHandler: removeObject.bind(this), + render: renderIcon(removeImg), + cornerSize: 24, + }), + }; + } } }); @@ -1860,7 +1999,8 @@ class Painter { this.canvas.isDrawingMode = false; // Skip BrushMyPaint mouseup is empty objects array, loading canvas as image, upload when object add to canvas - if (!["BrushMyPaint"].includes(this.type)) { + // [Crop] Skip + if (!["BrushMyPaint", "Crop"].includes(this.type)) { this.canvas.renderAll(); this.addToHistory(); this.uploadPaintFile(this.node.name); From 2163a8ab031950b9a4dc9f4b17c20aad2b8a3141 Mon Sep 17 00:00:00 2001 From: AlekPet Date: Sun, 22 Jun 2025 00:48:22 +0300 Subject: [PATCH 2/7] dev crop tools, fix draw rect crop... --- PainterNode/js/painter_node.js | 252 ++++++++++++++++++++++----------- 1 file changed, 171 insertions(+), 81 deletions(-) diff --git a/PainterNode/js/painter_node.js b/PainterNode/js/painter_node.js index 56b6bb4..810c163 100644 --- a/PainterNode/js/painter_node.js +++ b/PainterNode/js/painter_node.js @@ -48,7 +48,7 @@ const removeImg = makeElement("img"); removeImg.src = removeIcon; const cropIcon = - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.0' width='300.000000pt' height='300.000000pt' viewBox='0 0 300.000000 300.000000' preserveAspectRatio='xMidYMid meet'%3E%3Crect xmlns='http://www.w3.org/2000/svg' x='0' y='0' width='300' height='300' style='fill: rgb(0, 0, 0);' rx='58.194' ry='58.194'/%3E%3Cg transform='translate(0.000000,300.000000) scale(0.100000,-0.100000)' fill='%23ffffff' stroke='none'%3E%3Cpath d='M664 2836 l-34 -34 0 -1052 0 -1052 34 -34 34 -34 1052 0 1052 0 34 34 c30 30 34 40 34 86 0 46 -4 56 -34 86 l-34 34 -966 0 -966 0 0 966 0 966 -34 34 c-30 30 -40 34 -86 34 -46 0 -56 -4 -86 -34z m137 -25 l24 -19 5 -957 5 -957 28 -24 28 -24 945 0 945 0 24 -25 c33 -32 33 -78 0 -110 l-24 -25 -1031 0 -1031 0 -24 25 -25 24 0 1031 0 1031 25 24 c29 30 74 32 106 6z'/%3E%3Cpath d='M164 2336 c-50 -50 -50 -122 0 -172 l34 -34 106 0 c123 0 155 11 180 64 20 43 20 69 0 112 -25 53 -57 64 -180 64 l-106 0 -34 -34z m258 -20 c27 -20 39 -62 27 -93 -17 -45 -37 -52 -137 -53 -86 0 -95 2 -117 25 -16 15 -25 36 -25 55 0 19 9 40 25 55 22 23 31 25 117 25 60 -1 99 -5 110 -14z'/%3E%3Cpath d='M1059 2355 c-29 -15 -59 -70 -59 -105 0 -36 31 -90 61 -105 23 -13 112 -15 549 -15 l520 0 0 -520 c0 -576 -1 -563 64 -594 43 -20 69 -20 112 0 66 31 64 9 64 680 l0 606 -34 34 -34 34 -609 0 c-506 -1 -612 -3 -634 -15z m1246 -50 l25 -24 0 -591 c0 -359 -4 -598 -10 -609 -5 -10 -23 -24 -40 -31 -37 -15 -81 0 -99 33 -8 15 -12 175 -14 529 l-2 507 -23 23 -23 23 -507 2 c-354 2 -514 6 -529 14 -33 18 -48 62 -33 99 7 17 20 35 29 40 9 6 255 10 609 10 l593 0 24 -25z'/%3E%3Cpath d='M2192 483 c-51 -25 -62 -57 -62 -179 l0 -106 34 -34 c30 -30 40 -34 86 -34 46 0 56 4 86 34 l34 34 0 106 c0 123 -11 155 -64 180 -43 20 -72 20 -114 -1z m116 -56 c20 -18 22 -30 22 -114 0 -86 -2 -96 -25 -118 -32 -33 -78 -33 -110 0 -23 22 -25 31 -25 117 1 99 8 120 51 137 30 12 57 5 87 -22z'/%3E%3C/g%3E%3C/svg%3E"; + "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.0' width='300px' height='300px' viewBox='0 0 300 300'%3E%3Crect xmlns='http://www.w3.org/2000/svg' x='0' y='0' width='300' height='300' style='fill: rgb(0, 0, 0);' rx='58.194' ry='58.194'/%3E%3Cg transform='translate(0.000000,300.000000) scale(0.100000,-0.100000)' fill='%23ffffff' stroke='none'%3E%3Cpath d='M664 2836 l-34 -34 0 -1052 0 -1052 34 -34 34 -34 1052 0 1052 0 34 34 c30 30 34 40 34 86 0 46 -4 56 -34 86 l-34 34 -966 0 -966 0 0 966 0 966 -34 34 c-30 30 -40 34 -86 34 -46 0 -56 -4 -86 -34z m137 -25 l24 -19 5 -957 5 -957 28 -24 28 -24 945 0 945 0 24 -25 c33 -32 33 -78 0 -110 l-24 -25 -1031 0 -1031 0 -24 25 -25 24 0 1031 0 1031 25 24 c29 30 74 32 106 6z'/%3E%3Cpath d='M164 2336 c-50 -50 -50 -122 0 -172 l34 -34 106 0 c123 0 155 11 180 64 20 43 20 69 0 112 -25 53 -57 64 -180 64 l-106 0 -34 -34z m258 -20 c27 -20 39 -62 27 -93 -17 -45 -37 -52 -137 -53 -86 0 -95 2 -117 25 -16 15 -25 36 -25 55 0 19 9 40 25 55 22 23 31 25 117 25 60 -1 99 -5 110 -14z'/%3E%3Cpath d='M1059 2355 c-29 -15 -59 -70 -59 -105 0 -36 31 -90 61 -105 23 -13 112 -15 549 -15 l520 0 0 -520 c0 -576 -1 -563 64 -594 43 -20 69 -20 112 0 66 31 64 9 64 680 l0 606 -34 34 -34 34 -609 0 c-506 -1 -612 -3 -634 -15z m1246 -50 l25 -24 0 -591 c0 -359 -4 -598 -10 -609 -5 -10 -23 -24 -40 -31 -37 -15 -81 0 -99 33 -8 15 -12 175 -14 529 l-2 507 -23 23 -23 23 -507 2 c-354 2 -514 6 -529 14 -33 18 -48 62 -33 99 7 17 20 35 29 40 9 6 255 10 609 10 l593 0 24 -25z'/%3E%3Cpath d='M2192 483 c-51 -25 -62 -57 -62 -179 l0 -106 34 -34 c30 -30 40 -34 86 -34 46 0 56 4 86 34 l34 34 0 106 c0 123 -11 155 -64 180 -43 20 -72 20 -114 -1z m116 -56 c20 -18 22 -30 22 -114 0 -86 -2 -96 -25 -118 -32 -33 -78 -33 -110 0 -23 22 -25 31 -25 117 1 99 8 120 51 137 30 12 57 5 87 -22z'/%3E%3C/g%3E%3C/svg%3E"; const cropImg = makeElement("img"); cropImg.src = cropIcon; @@ -74,12 +74,6 @@ function removeObject(eventData, transform) { this.viewListObjects(this.list_objects_panel__items); } -// [Crop] Crop icons -function cropIconClick(eventData, transform) { - this.cropCanvas(); - console.log("Crop"); -} - function resizeCanvas(node, sizes) { const { width, height } = sizes ?? @@ -154,6 +148,8 @@ class Painter { // [Crop] Object this.crop_object = null; + this.crop_disabled = false; + this.mouseDown = null; this.fonts = { Arial: { type: "default" }, @@ -682,6 +678,8 @@ class Painter { if (await comfyuiDesktopConfirm("Reset canvas size by default 512x512?")) { this.setCanvasSize(512, 512); } + this.cropReset(); + this.canvas.clear(); this.canvas.backgroundColor = this.bgColor.value || "#000000"; this.canvas.requestRenderAll(); @@ -790,9 +788,15 @@ class Painter { this.canvas.isDrawingMode = this.drawning = true; if ( - !["Brush", "Erase", "BrushSymmetry", "Image", "Textbox"].includes( - this.type - ) + ![ + "Brush", + "Erase", + "BrushSymmetry", + "Image", + "Textbox", + "Crop", + "CropMove", + ].includes(this.type) ) this.canvas.isDrawingMode = false; } @@ -920,6 +924,7 @@ class Painter { changePropertyBrush(type = "Brush") { if (["Brush", "BrushSymmetry", "BrushMyPaint"].includes(type)) { if (type === "Brush" || type === "BrushSymmetry") { + // nope } if (type === "BrushMyPaint") { @@ -991,9 +996,6 @@ class Painter { shape = new fabric.Line(points); } else if (type === "Path") { shape = new fabric.Path(path); - } else if (type === "Crop") { - // [Crop] Create crop object - return this.createCrop(left, top); } Object.assign(shape, { @@ -1019,18 +1021,20 @@ class Painter { if (this.crop_object) { this.canvas.remove(this.crop_object); this.crop_object = null; + this.crop_disabled = false; + this.mouseDown = null; } } createCrop(left, top) { - this.crop_object = new fabric.Rect(); - Object.assign(this.crop_object, { - left: left, - top: top, + const crop_object = new fabric.Rect(); + Object.assign(crop_object, { originX: "left", originY: "top", strokeWidth: 1, stroke: "#ccc", + left: left, + top: top, transparentCorners: false, hasBorders: false, hasControls: true, @@ -1038,16 +1042,26 @@ class Painter { strokeDashArray: [5, 5], absolutePositioned: true, name: "crop", + visible: false, }); - return this.crop_object; + this.canvas.add(crop_object); + return crop_object; } cropCanvas() { + this.crop_disabled = true; this.crop_object.visible = false; const croppedImg = new Image(); - croppedImg.onload = () => { + croppedImg.src = this.canvas.toDataURL({ + left: this.crop_object.left, + top: this.crop_object.top, + width: this.crop_object.width, + height: this.crop_object.height, + }); + + croppedImg.onload = async () => { this.canvas.clear(); this.canvas.backgroundColor = this.bgColor.value || "#000000"; this.canvas.requestRenderAll(); @@ -1055,6 +1069,7 @@ class Painter { const image = new fabric.Image(croppedImg); image.left = this.crop_object.left; image.top = this.crop_object.top; + image.selectable = true; if (confirm("Resize canvas to new size clip?")) { image.left = 0; @@ -1062,6 +1077,9 @@ class Painter { this.setCanvasSize(this.crop_object.width, this.crop_object.height); } + this.canvas.isDrawingMode = false; + this.drawning = false; + this.type = "CropMove"; this.cropReset(); image.setCoords(); @@ -1071,13 +1089,6 @@ class Painter { this.saveSettingsPainterNode(); this.uploadPaintFile(this.node.name); }; - - croppedImg.src = this.canvas.toDataURL({ - left: this.crop_object.left, - top: this.crop_object.top, - width: this.crop_object.width, - height: this.crop_object.height, - }); } // Toolbars @@ -1311,9 +1322,20 @@ class Painter { if (currentTarget) { this.type = currentTarget; - // [Crop] Crop reset + // [Crop] Crop + if (currentTarget === "CropMove") { + this.type = "Crop"; + this.canvas.isDrawingMode = false; + this.drawning = true; + this.mouseDown = null; + } + if (currentTarget !== "Crop") { this.cropReset(); + } else { + // [Crop] Create crop object + this.crop_disabled = false; + this.crop_object = this.createCrop(0, 0); } // Set default brush width if width < 1 (for fabricjs) @@ -1779,10 +1801,7 @@ class Painter { if (!targets || targets.length == 0) return; // [Crop] - if ( - this.type === "Crop" && - targets.filter((t) => t?.name === "crop").length - ) { + if (["Crop", "CropMove"].includes(this.type)) { return; } @@ -1862,6 +1881,8 @@ class Painter { }, // Mouse button down event "mouse:down": (o) => { + const { x: left, y: top } = this.canvas.getPointer(o.e); + if (!this.canvas.isDrawingMode && this.bringFrontSelected) this.canvas.bringToFront(this.canvas.getActiveObject()); @@ -1878,24 +1899,29 @@ class Painter { ["Brush", "Erase", "BrushMyPaint", "BrushSymmetry"].includes( this.type ) - ) + ) { return; + } - if (this.type !== "Textbox") { - const { x: left, y: top } = this.canvas.getPointer(o.e); - - // [Crop] Mouse down - if (this.type === "Crop") { - if (this.crop_object) { - this.crop_object.set({ left, top }); - this.crop_object.visible = true; - this.crop_object.maxPosX = left; - this.crop_object.maxPosY = top; - this.canvas.setActiveObject(this.crop_object); - return; - } + // [Crop] Mouse down + if (this.type === "Crop") { + if (!this.crop_object) { + // [Crop] Create crop object + this.crop_object = this.createCrop(left, top); } + if (!this.crop_disabled) { + this.crop_object.set({ left, top }); + this.crop_object.visible = true; + this.crop_object.crop_startX = left; + this.crop_object.crop_startY = top; + this.mouseDown = o.e; + this.canvas.setActiveObject(this.crop_object); + } + return; + } + + if (this.type !== "Textbox") { const colors = ["red", "blue", "green", "yellow", "purple", "orange"]; const strokeWidth = +this.strokeWidth.value; const stroke = @@ -1909,6 +1935,7 @@ class Painter { this.fillColor.value, this.fillColorTransparent.value ); + const shape = this.shapeCreate({ type: this.type, left, @@ -1978,6 +2005,47 @@ class Painter { activeObj.set({ radius: radius }); } else if (this.type === "Line") { activeObj.set({ x2: pointer.x, y2: pointer.y }); + } else if ( + this.type === "Crop" && + this.mouseDown && + !this.crop_disabled + ) { + const newWidth = pointer.x - this.crop_object.crop_startX; + const newHeight = pointer.y - this.crop_object.crop_startY; + + const minSize = 10; + + const absWidth = Math.max(minSize, Math.abs(newWidth)); + const absHeight = Math.max(minSize, Math.abs(newHeight)); + + const finalLeft = + newWidth > 0 + ? this.crop_object.crop_startX + : this.crop_object.crop_startX + newWidth; + const finalTop = + newHeight > 0 + ? this.crop_object.crop_startY + : this.crop_object.crop_startY + newHeight; + + const boundedLeft = Math.max(0, finalLeft); + const boundedTop = Math.max(0, finalTop); + const boundedWidth = Math.min( + absWidth, + this.canvas.width - boundedLeft + ); + const boundedHeight = Math.min( + absHeight, + this.canvas.height - boundedTop + ); + + this.crop_object.set({ + left: boundedLeft, + top: boundedTop, + width: boundedWidth, + height: boundedHeight, + originX: "left", + originY: "top", + }); } else { activeObj.set({ width: Math.abs(this.originX - pointer.x) }); activeObj.set({ height: Math.abs(this.originY - pointer.y) }); @@ -1988,43 +2056,49 @@ class Painter { // Mouse button up event "mouse:up": (o) => { - this.canvas._objects.forEach((object) => { - if (!object.hasOwnProperty("controls")) { - // [Crop] Controls icon - if (this.type === "Crop") { - object.controls = { - ...object.controls, - removeControl: new fabric.Control({ - x: 0.5, - y: -0.5, - offsetY: -16, - offsetX: 16, - cursorStyle: "pointer", - mouseDownHandler: cropIconClick.bind(this), - render: renderIcon(cropImg), - cornerSize: 24, - }), - }; - } else { - object.controls = { - ...object.controls, - removeControl: new fabric.Control({ - x: 0.5, - y: -0.5, - offsetY: -16, - offsetX: 16, - cursorStyle: "pointer", - mouseUpHandler: removeObject.bind(this), - render: renderIcon(removeImg), - cornerSize: 24, - }), - }; - } - } - }); + this.mouseDown = null; - this.canvas.getActiveObject()?.setCoords(); - this.canvas.getActiveObjects()?.forEach((a) => a.setCoords()); + const activeObject = this.canvas.getActiveObject(); + + if (activeObject) { + if (this.type === "Crop" && !this.mode) { + activeObject.controls = { + ...activeObject.controls, + removeControl: new fabric.Control({ + x: 0.5, + y: -0.5, + offsetY: -16, + offsetX: 16, + cursorStyle: "pointer", + mouseUpHandler: (e) => e.stopPropagation(), + mouseDownHandler: (eventData, transform) => { + eventData.stopPropagation(); + this.cropCanvas(); + return true; + }, + render: renderIcon(cropImg), + cornerSize: 24, + }), + }; + } else { + activeObject.controls = { + ...activeObject.controls, + removeControl: new fabric.Control({ + x: 0.5, + y: -0.5, + offsetY: -16, + offsetX: 16, + cursorStyle: "pointer", + mouseUpHandler: removeObject.bind(this), + render: renderIcon(removeImg), + cornerSize: 24, + }), + }; + } + + activeObject.setCoords(); + this.canvas.requestRenderAll(); + } if ( ![ @@ -2356,6 +2430,10 @@ class Painter { this.canvas.getActiveObjects().forEach((a_obs) => { a_obs.hasControls = false; a_obs.hasBorders = false; + + if (a_obs?.name === "crop") { + a_obs.visible = false; + } }); } } @@ -2391,9 +2469,21 @@ class Painter { this.canvas.getActiveObjects().forEach((a_obs) => { a_obs.hasControls = true; a_obs.hasBorders = true; + if (a_obs?.name === "crop") { + activeObj.visible = true; + activeObj.hasBorders = false; + } }); this.canvas.renderAll(); + } else { + if (activeObj?.name === "crop") { + activeObj.visible = true; + activeObj.hasControls = true; + activeObj.hasBorders = false; + } } + this.canvas.renderAll(); + this.saveSettingsPainterNode(); res(true); }) From 9181c54045a647cf73621ae3761d6243381394e5 Mon Sep 17 00:00:00 2001 From: AlekPet Date: Wed, 23 Jul 2025 13:22:34 +0300 Subject: [PATCH 3/7] update crop, add two actions to toolbar crop panel --- PainterNode/css/painter_node_styles.css | 6 +- PainterNode/js/painter_node.js | 234 ++++++++++++++++++++---- 2 files changed, 199 insertions(+), 41 deletions(-) diff --git a/PainterNode/css/painter_node_styles.css b/PainterNode/css/painter_node_styles.css index 787d0b5..6ae099f 100644 --- a/PainterNode/css/painter_node_styles.css +++ b/PainterNode/css/painter_node_styles.css @@ -48,8 +48,12 @@ min-height: 55px; } -.property_brushesBox > button { +.property_brushesBox > button, +.property_cropBox > button { width: auto; + display: flex; + align-items: center; + justify-content: center; } .painter_drawning_box_property select { diff --git a/PainterNode/js/painter_node.js b/PainterNode/js/painter_node.js index 810c163..c2049c9 100644 --- a/PainterNode/js/painter_node.js +++ b/PainterNode/js/painter_node.js @@ -32,7 +32,7 @@ import { MyPaintManager } from "./lib/painternode/manager_mypaint.js"; // ================= FUNCTIONS ================ -const DEBUG = !false; +const DEBUG = false; const extensionName = "alekpet.PainterNode"; // Save settings in JSON file on the extension folder [big data settings includes images] if true else localStorage @@ -47,12 +47,6 @@ const removeIcon = const removeImg = makeElement("img"); removeImg.src = removeIcon; -const cropIcon = - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.0' width='300px' height='300px' viewBox='0 0 300 300'%3E%3Crect xmlns='http://www.w3.org/2000/svg' x='0' y='0' width='300' height='300' style='fill: rgb(0, 0, 0);' rx='58.194' ry='58.194'/%3E%3Cg transform='translate(0.000000,300.000000) scale(0.100000,-0.100000)' fill='%23ffffff' stroke='none'%3E%3Cpath d='M664 2836 l-34 -34 0 -1052 0 -1052 34 -34 34 -34 1052 0 1052 0 34 34 c30 30 34 40 34 86 0 46 -4 56 -34 86 l-34 34 -966 0 -966 0 0 966 0 966 -34 34 c-30 30 -40 34 -86 34 -46 0 -56 -4 -86 -34z m137 -25 l24 -19 5 -957 5 -957 28 -24 28 -24 945 0 945 0 24 -25 c33 -32 33 -78 0 -110 l-24 -25 -1031 0 -1031 0 -24 25 -25 24 0 1031 0 1031 25 24 c29 30 74 32 106 6z'/%3E%3Cpath d='M164 2336 c-50 -50 -50 -122 0 -172 l34 -34 106 0 c123 0 155 11 180 64 20 43 20 69 0 112 -25 53 -57 64 -180 64 l-106 0 -34 -34z m258 -20 c27 -20 39 -62 27 -93 -17 -45 -37 -52 -137 -53 -86 0 -95 2 -117 25 -16 15 -25 36 -25 55 0 19 9 40 25 55 22 23 31 25 117 25 60 -1 99 -5 110 -14z'/%3E%3Cpath d='M1059 2355 c-29 -15 -59 -70 -59 -105 0 -36 31 -90 61 -105 23 -13 112 -15 549 -15 l520 0 0 -520 c0 -576 -1 -563 64 -594 43 -20 69 -20 112 0 66 31 64 9 64 680 l0 606 -34 34 -34 34 -609 0 c-506 -1 -612 -3 -634 -15z m1246 -50 l25 -24 0 -591 c0 -359 -4 -598 -10 -609 -5 -10 -23 -24 -40 -31 -37 -15 -81 0 -99 33 -8 15 -12 175 -14 529 l-2 507 -23 23 -23 23 -507 2 c-354 2 -514 6 -529 14 -33 18 -48 62 -33 99 7 17 20 35 29 40 9 6 255 10 609 10 l593 0 24 -25z'/%3E%3Cpath d='M2192 483 c-51 -25 -62 -57 -62 -179 l0 -106 34 -34 c30 -30 40 -34 86 -34 46 0 56 4 86 34 l34 34 0 106 c0 123 -11 155 -64 180 -43 20 -72 20 -114 -1z m116 -56 c20 -18 22 -30 22 -114 0 -86 -2 -96 -25 -118 -32 -33 -78 -33 -110 0 -23 22 -25 31 -25 117 1 99 8 120 51 137 30 12 57 5 87 -22z'/%3E%3C/g%3E%3C/svg%3E"; - -const cropImg = makeElement("img"); -cropImg.src = cropIcon; - const convertIdClass = (text) => text.replaceAll(".", "_"); function renderIcon(icon) { @@ -298,12 +292,12 @@ class Painter { - + - +
@@ -681,6 +675,13 @@ class Painter { this.cropReset(); this.canvas.clear(); + + const image = new fabric.Image(""); + this.canvas.setBackgroundImage( + image, + this.canvas.renderAll.bind(this.canvas) + ); + this.canvas.backgroundColor = this.bgColor.value || "#000000"; this.canvas.requestRenderAll(); @@ -703,9 +704,12 @@ class Painter { }), obEl = makeElement("button"), countType = objectNames.filter((t) => t == type).length + 1, - text_value = !o.hasOwnProperty("mypaintlib") - ? type + `_${countType}` - : `mypaint_${countType}`; + addTypeCheck = o.hasOwnProperty("mypaintlib") + ? "mypaint" + : o.hasOwnProperty("cropimage") + ? "cropimage" + : type, + text_value = addTypeCheck + `_${countType}`; obEl.setAttribute("painter_object", text_value); obEl.textContent = text_value; @@ -773,13 +777,20 @@ class Painter { nextElement = target.parentElement.nextElementSibling, panelListObjects = target.nextElementSibling; - // [Crop] Crop reset - this.cropReset(); - if (["Image", "Textbox"].includes(this.type)) { this.drawning = true; } + // [Crop] Crop + if (this.type === "CropMove" || this.type === "Crop") { + this.type = "Crop"; + this.canvas.isDrawingMode = false; + this.drawning = true; + this.mouseDown = null; + } + + this.cropReset(); + if (this.drawning) { this.canvas.isDrawingMode = false; this.drawning = false; @@ -949,6 +960,8 @@ class Painter { let a_obs = this.canvas.getActiveObjects(); if (a_obs) { a_obs.forEach((a_o) => { + if (a_o.hasOwnProperty("cropimage") && !this.mode) return; + this.setActiveStyle( "strokeWidth", parseInt(this.strokeWidth.value, 10), @@ -1071,7 +1084,7 @@ class Painter { image.top = this.crop_object.top; image.selectable = true; - if (confirm("Resize canvas to new size clip?")) { + if (await comfyuiDesktopConfirm("Resize canvas to new size clip?")) { image.left = 0; image.top = 0; this.setCanvasSize(this.crop_object.width, this.crop_object.height); @@ -1091,6 +1104,47 @@ class Painter { }; } + cropCanvasPath() { + const croppedImg = new Image(); + + this.crop_object.visible = false; + croppedImg.src = this.canvas.toDataURL(); + + croppedImg.onload = async () => { + this.crop_object.visible = true; + this.canvas.clear(); + this.canvas.backgroundColor = this.bgColor.value || "#000000"; + + this.canvas.requestRenderAll(); + + const image = new fabric.Image(croppedImg); + image.set({ + selectable: true, + evented: true, + cropimage: true, + strokeWidth: 0, + stroke: null, + }); + + image.setCoords(); + this.canvas.add(image); + + this.crop_object.set({ fill: this.canvas.backgroundColor }); + image.clipPath = this.crop_object; + + this.canvas.isDrawingMode = false; + this.drawning = false; + this.type = "CropMove"; + this.cropReset(); + + this.canvas.sendToBack(image); + this.canvas.renderAll(); + + this.saveSettingsPainterNode(); + this.uploadPaintFile(this.node.name); + }; + } + // Toolbars createFontToolbar() { const property_textbox = makeElement("div", { @@ -1185,6 +1239,7 @@ class Painter { this.painter_drawning_box_property.append(property_textbox); } + // Brush Toolbar createBrushesToolbar() { // First panel const property_brushesBox = makeElement("div", { @@ -1244,11 +1299,83 @@ class Painter { app.graph.setDirtyCanvas(true, false); } + + // Crop Toolbar + createCropToolbar() { + const property_crop = makeElement("div", { + style: { + display: "flex", + gap: "5px", + }, + class: ["property_cropBox", "comfy-menu-btns"], + }); + + const cropImg = makeElement("img"); + cropImg.src = + "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.0' width='21px' height='24px' viewBox='0 0 300 300'%3E%3Cg transform='translate(0.000000,300.000000) scale(0.100000,-0.100000)' fill='%23ffffff' stroke='none'%3E%3Cpath d='M664 2836 l-34 -34 0 -1052 0 -1052 34 -34 34 -34 1052 0 1052 0 34 34 c30 30 34 40 34 86 0 46 -4 56 -34 86 l-34 34 -966 0 -966 0 0 966 0 966 -34 34 c-30 30 -40 34 -86 34 -46 0 -56 -4 -86 -34z m137 -25 l24 -19 5 -957 5 -957 28 -24 28 -24 945 0 945 0 24 -25 c33 -32 33 -78 0 -110 l-24 -25 -1031 0 -1031 0 -24 25 -25 24 0 1031 0 1031 25 24 c29 30 74 32 106 6z'/%3E%3Cpath d='M164 2336 c-50 -50 -50 -122 0 -172 l34 -34 106 0 c123 0 155 11 180 64 20 43 20 69 0 112 -25 53 -57 64 -180 64 l-106 0 -34 -34z m258 -20 c27 -20 39 -62 27 -93 -17 -45 -37 -52 -137 -53 -86 0 -95 2 -117 25 -16 15 -25 36 -25 55 0 19 9 40 25 55 22 23 31 25 117 25 60 -1 99 -5 110 -14z'/%3E%3Cpath d='M1059 2355 c-29 -15 -59 -70 -59 -105 0 -36 31 -90 61 -105 23 -13 112 -15 549 -15 l520 0 0 -520 c0 -576 -1 -563 64 -594 43 -20 69 -20 112 0 66 31 64 9 64 680 l0 606 -34 34 -34 34 -609 0 c-506 -1 -612 -3 -634 -15z m1246 -50 l25 -24 0 -591 c0 -359 -4 -598 -10 -609 -5 -10 -23 -24 -40 -31 -37 -15 -81 0 -99 33 -8 15 -12 175 -14 529 l-2 507 -23 23 -23 23 -507 2 c-354 2 -514 6 -529 14 -33 18 -48 62 -33 99 7 17 20 35 29 40 9 6 255 10 609 10 l593 0 24 -25z'/%3E%3Cpath d='M2192 483 c-51 -25 -62 -57 -62 -179 l0 -106 34 -34 c30 -30 40 -34 86 -34 46 0 56 4 86 34 l34 34 0 106 c0 123 -11 155 -64 180 -43 20 -72 20 -114 -1z m116 -56 c20 -18 22 -30 22 -114 0 -86 -2 -96 -25 -118 -32 -33 -78 -33 -110 0 -23 22 -25 31 -25 117 1 99 8 120 51 137 30 12 57 5 87 -22z'/%3E%3C/g%3E%3C/svg%3E"; + + const cropAction1 = makeElement("button", { + dataset: { prop: "prop_cropAction1" }, + title: "Crop", + children: [ + makeElement("span", { + styke: { padding: "5px" }, + textContent: "Crop", + }), + cropImg, + ], + onclick: (e) => { + if ( + this.crop_object && + this.canvas.getActiveObject() === this.crop_object + ) { + this.cropCanvas(); + } else { + app.extensionManager.toast.add({ + severity: "warn", + summary: "Warning", + detail: "No area selected for canvas cropping!", + life: 5000, + }); + } + }, + }); + + const cropAction2 = makeElement("button", { + dataset: { prop: "prop_cropAction2" }, + title: "Crop 2", + children: [ + makeElement("span", { + styke: { padding: "5px" }, + textContent: "Crop path", + }), + cropImg.cloneNode(true), + ], + onclick: (e) => { + if ( + this.crop_object && + this.canvas.getActiveObject() === this.crop_object + ) { + this.cropCanvasPath(); + } else { + app.extensionManager.toast.add({ + severity: "warn", + summary: "Warning", + detail: "No area selected for canvas cropping!", + life: 5000, + }); + } + }, + }); + + property_crop.append(cropAction1, cropAction2); + this.painter_drawning_box_property.append(property_crop); + } // end - Toolbars selectPropertyToolbar(type) { this.painter_drawning_box_property.innerHTML = ""; - if (["Textbox", "Brush"].includes(this.type)) { + if (["Textbox", "Brush", "Crop"].includes(this.type)) { this.painter_drawning_box_property.style.display = "flex"; switch (this.type) { @@ -1258,6 +1385,9 @@ class Painter { case "Brush": this.createBrushesToolbar(); break; + case "Crop": + this.createCropToolbar(); + break; } } else { this.painter_drawning_box_property.style.display = ""; @@ -1323,7 +1453,7 @@ class Painter { this.type = currentTarget; // [Crop] Crop - if (currentTarget === "CropMove") { + if (this.type === "CropMove") { this.type = "Crop"; this.canvas.isDrawingMode = false; this.drawning = true; @@ -1608,9 +1738,14 @@ class Painter { } } }; + // Event input bgcolor this.reset_set_bg = () => { - this.canvas.setBackgroundImage(null); + const image = new fabric.Image(""); + this.canvas.setBackgroundImage( + image, + this.canvas.renderAll.bind(this.canvas) + ); this.canvas.backgroundColor = this.bgColor.value; this.canvas.renderAll(); this.uploadPaintFile(this.node.name); @@ -1882,9 +2017,23 @@ class Painter { // Mouse button down event "mouse:down": (o) => { const { x: left, y: top } = this.canvas.getPointer(o.e); + const activeObject = this.canvas.getActiveObject(); - if (!this.canvas.isDrawingMode && this.bringFrontSelected) - this.canvas.bringToFront(this.canvas.getActiveObject()); + if ( + !this.canvas.isDrawingMode && + this.bringFrontSelected && + !["Crop", "CropMove"].includes(this.type) + ) { + if ( + activeObject && + activeObject?.hasOwnProperty("cropimage") && + !this.mode + ) { + this.canvas.bringToFront(activeObject); + } else { + this.canvas.bringToFront(activeObject); + } + } this.canvas.isDrawingMode = this.drawning; if (!this.canvas.isDrawingMode) { @@ -2062,24 +2211,24 @@ class Painter { if (activeObject) { if (this.type === "Crop" && !this.mode) { - activeObject.controls = { - ...activeObject.controls, - removeControl: new fabric.Control({ - x: 0.5, - y: -0.5, - offsetY: -16, - offsetX: 16, - cursorStyle: "pointer", - mouseUpHandler: (e) => e.stopPropagation(), - mouseDownHandler: (eventData, transform) => { - eventData.stopPropagation(); - this.cropCanvas(); - return true; - }, - render: renderIcon(cropImg), - cornerSize: 24, - }), - }; + // activeObject.controls = { + // ...activeObject.controls, + // removeControl: new fabric.Control({ + // x: 0.5, + // y: -0.5, + // offsetY: -16, + // offsetX: 16, + // cursorStyle: "pointer", + // mouseUpHandler: (e) => e.stopPropagation(), + // mouseDownHandler: (eventData, transform) => { + // eventData.stopPropagation(); + // this.cropCanvas(); + // return true; + // }, + // render: renderIcon(cropImg), + // cornerSize: 24, + // }), + // }; } else { activeObject.controls = { ...activeObject.controls, @@ -2501,7 +2650,12 @@ class Painter { // ================= CREATE PAINTER WIDGET ============ function PainterWidget(node, inputName, inputData, app) { - node.addWidget("button", "Clear Canvas", "clear_painer", () => { + node.addWidget("button", "Clear Canvas", "clear_painer", async () => { + if ( + !(await comfyuiDesktopConfirm("Do you really want to clear the canvas?")) + ) { + return; + } node.painter.list_objects_panel__items.innerHTML = ""; node.painter.clearCanvas(); }); From 9b549cdf9d7a416b4a4b7c690bd9f85c828c5519 Mon Sep 17 00:00:00 2001 From: AlekPet Date: Tue, 29 Jul 2025 17:42:09 +0300 Subject: [PATCH 4/7] fix PainterNode --- PainterNode/painter_node.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PainterNode/painter_node.py b/PainterNode/painter_node.py index 26e008f..443a82f 100644 --- a/PainterNode/painter_node.py +++ b/PainterNode/painter_node.py @@ -372,7 +372,7 @@ class PainterNode(object): DESCRIPTION = "PainterNode allows you to draw in the node window, for later use in the ControlNet or in any other node." CATEGORY = "AlekPet Nodes/image" - def painter_execute(self, image, unique_id, update_node=True, images=None): + async def painter_execute(self, image, unique_id, update_node=True, images=None): # Piping image input if unique_id not in PAINTER_DICT: PAINTER_DICT[unique_id] = self @@ -391,7 +391,7 @@ class PainterNode(object): PromptServer.instance.send_sync( "alekpet_get_image", {"unique_id": unique_id, "images": input_images} ) - if not asyncio.run(wait_canvas_change(unique_id)): + if await wait_canvas_change(unique_id): print(f"Painter_{unique_id}: Failed to get image!") else: print(f"Painter_{unique_id}: Image received, canvas changed!") From 1774bd3a9733dd8fd152018c0c5b3ff1da144688 Mon Sep 17 00:00:00 2001 From: AlekPet Date: Tue, 29 Jul 2025 17:59:12 +0300 Subject: [PATCH 5/7] small fixes - Fix long float canvas size in title node - Update version --- PainterNode/js/painter_node.js | 14 ++++++++++++-- pyproject.toml | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/PainterNode/js/painter_node.js b/PainterNode/js/painter_node.js index c2049c9..606b7a4 100644 --- a/PainterNode/js/painter_node.js +++ b/PainterNode/js/painter_node.js @@ -1084,7 +1084,11 @@ class Painter { image.top = this.crop_object.top; image.selectable = true; - if (await comfyuiDesktopConfirm("Resize canvas to new size clip?")) { + if ( + await comfyuiDesktopConfirm( + "Resize the canvas to fit the updated clip size?" + ) + ) { image.left = 0; image.top = 0; this.setCanvasSize(this.crop_object.width, this.crop_object.height); @@ -2803,7 +2807,13 @@ function PainterWidget(node, inputName, inputData, app) { ) { node.painter.setCanvasSize(w, h); } else { - node.title = `${node.type} - ${node.painter.storageCls.settings_painter_node.settings.currentCanvasSize.width}x${node.painter.storageCls.settings_painter_node.settings.currentCanvasSize.height}`; + node.title = `${node.type} - ${Math.floor( + node.painter.storageCls.settings_painter_node.settings + .currentCanvasSize.width + )}x${Math.floor( + node.painter.storageCls.settings_painter_node.settings + .currentCanvasSize.height + )}`; } const img_ = new fabric.Image(img, { diff --git a/pyproject.toml b/pyproject.toml index 9e3ba5e..02ea437 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "comfyui_custom_nodes_alekpet" description = "Nodes: PoseNode, PainterNode, TranslateTextNode, TranslateCLIPTextEncodeNode, DeepTranslatorTextNode, DeepTranslatorCLIPTextEncodeNode, ArgosTranslateTextNode, ArgosTranslateCLIPTextEncodeNode, ChatGLM4TranslateCLIPTextEncodeNode, ChatGLM4TranslateTextNode, ChatGLM4InstructNode, ChatGLM4InstructMediaNode, PreviewTextNode, HexToHueNode, ColorsCorrectNode, IDENode." -version = "1.0.74" +version = "1.0.75" license = { file = "LICENSE" } [project.urls] From 3ea0f2b4b2678ad13a1505056f46a8cb223ff072 Mon Sep 17 00:00:00 2001 From: AlekPet Date: Tue, 29 Jul 2025 18:17:34 +0300 Subject: [PATCH 6/7] add Crop tool --- PainterNode/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PainterNode/README.md b/PainterNode/README.md index 9b2d168..9de7dd5 100644 --- a/PainterNode/README.md +++ b/PainterNode/README.md @@ -4,6 +4,8 @@ ## Changelog: +> 2025.07.2025 - Add tool Crop canvas + > 2024.05.10 - Implement piping in an image ([issue in an image](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/issues/24)) ([example Piping in an image](#piping)) > 2024.04.11 - Implement **MyPaint** brush tool ([issue MyPaint Brush make](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/issues/36)) From 360f32690870051a46d13847c6a7cc49a96a6429 Mon Sep 17 00:00:00 2001 From: AlekPet Date: Tue, 29 Jul 2025 18:17:34 +0300 Subject: [PATCH 7/7] add Crop tool --- PainterNode/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PainterNode/README.md b/PainterNode/README.md index 9b2d168..9572293 100644 --- a/PainterNode/README.md +++ b/PainterNode/README.md @@ -4,6 +4,8 @@ ## Changelog: +> 2025.07.29 - Add tool Crop canvas + > 2024.05.10 - Implement piping in an image ([issue in an image](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/issues/24)) ([example Piping in an image](#piping)) > 2024.04.11 - Implement **MyPaint** brush tool ([issue MyPaint Brush make](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/issues/36))