File size: 8,863 Bytes
b4760b6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | import { app } from "/scripts/app.js";
app.registerExtension({
name: "SBCODE.ImageCompareNode",
async beforeRegisterNodeDef(nodeType, nodeData) {
if (nodeData.name !== "ImageCompareNode") return;
// Helper for aspect-correct "contain" fit inside a box
function fitContain(srcW, srcH, maxW, maxH) {
if (!srcW || !srcH || !maxW || !maxH) {
return { x: 0, y: 0, w: 0, h: 0 };
}
const s = Math.min(maxW / srcW, maxH / srcH);
const w = Math.max(1, Math.floor(srcW * s));
const h = Math.max(1, Math.floor(srcH * s));
const x = Math.floor((maxW - w) / 2);
const y = Math.floor((maxH - h) / 2);
return { x, y, w, h };
}
const origOnNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
if (origOnNodeCreated) origOnNodeCreated.apply(this, arguments);
console.log("[SBCODE.ImageCompareNode] Node created:", this.title);
if (!this.size || this.size[0] < 100 || this.size[1] < 100) {
this.size = [532, 582];
}
this.sliderPos = 0.5;
this.dragging = false;
this.hovered = false;
// Common geometry used across mouse + draw
const getDrawGeom = () => {
const margin = 10;
const topOffset = 40; // leave space for title/widgets
const drawX = margin;
const drawY = margin + topOffset;
const drawW = this.size[0] - margin * 2;
const drawH = (this.size[1] - margin * 2) - topOffset;
return { drawX, drawY, drawW, drawH };
};
this.onMouseDown = function (_, pos) {
const { drawX, drawY, drawW, drawH } = getDrawGeom();
const x = pos[0] - drawX;
const y = pos[1] - drawY;
// Only react if inside draw area
if (x < 0 || x > drawW || y < 0 || y > drawH) return false;
const splitX = drawX + Math.floor(drawW * this.sliderPos);
const handleY = drawY + Math.floor(drawH / 2);
const dist = Math.hypot(pos[0] - splitX, pos[1] - handleY);
// Grab handle if close; otherwise jump slider to clicked x
if (dist < 15) {
this.dragging = true;
return true;
}
this.dragging = true;
this.sliderPos = Math.max(0, Math.min(1, x / drawW));
return true;
};
this.onMouseMove = function (e, pos) {
const { drawX, drawY, drawW, drawH } = getDrawGeom();
const splitX = drawX + Math.floor(drawW * this.sliderPos);
const handleY = drawY + Math.floor(drawH / 2);
const dist = Math.hypot(pos[0] - splitX, pos[1] - handleY);
this.hovered = dist < 15;
// Check if mouse button is no longer pressed (detect mouse up from event)
if (this.dragging && e && e.buttons !== undefined && e.buttons === 0) {
this.dragging = false;
}
if (this.dragging) {
let x = pos[0] - drawX;
x = Math.max(0, Math.min(drawW, x));
const newSliderPos = x / drawW;
// Only update canvas if slider position actually changed
if (Math.abs(newSliderPos - this.sliderPos) > 0.001) {
this.sliderPos = newSliderPos;
}
}
};
this.onDrawForeground = function (ctx) {
ctx.save();
const { drawX, drawY, drawW, drawH } = getDrawGeom();
// Background for preview area
ctx.fillStyle = "#111";
ctx.fillRect(drawX, drawY, drawW, drawH);
// Calculate aspect-correct positioning for images
let rectA = { x: 0, y: 0, w: drawW, h: drawH };
let rectB = { x: 0, y: 0, w: drawW, h: drawH };
if (this.imgA?.width && this.imgA?.height) {
rectA = fitContain(this.imgA.width, this.imgA.height, drawW, drawH);
}
if (this.imgB?.width && this.imgB?.height) {
rectB = fitContain(this.imgB.width, this.imgB.height, drawW, drawH);
}
// Draw B as the base (aspect-correct, centered)
if (this.imgB) {
ctx.drawImage(
this.imgB,
drawX + rectB.x,
drawY + rectB.y,
rectB.w,
rectB.h
);
}
// Draw A clipped by the slider (left part, aspect-correct, centered)
if (this.imgA) {
const splitX = drawX + Math.floor(drawW * this.sliderPos);
ctx.save();
// Clip the left portion of the whole draw area
ctx.beginPath();
ctx.rect(drawX, drawY, splitX - drawX, drawH);
ctx.clip();
ctx.drawImage(
this.imgA,
drawX + rectA.x,
drawY + rectA.y,
rectA.w,
rectA.h
);
ctx.restore();
// Slider line
ctx.strokeStyle = "#00e0ff";
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(splitX, drawY);
ctx.lineTo(splitX, drawY + drawH);
ctx.stroke();
// Optional handle highlight when hovered/dragging
if (this.hovered || this.dragging) {
ctx.fillStyle = "#00e0ff";
ctx.beginPath();
ctx.arc(splitX, drawY + drawH / 2, 5, 0, Math.PI * 2);
ctx.fill();
}
}
// Labels
ctx.fillStyle = "white";
ctx.font = "bold 14px sans-serif";
ctx.shadowColor = "black";
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.fillText("A", drawX + 8, drawY + 20);
ctx.fillText("B", drawX + drawW - 20, drawY + 20);
//draw dimensions
ctx.font = "normal 10px sans-serif";
if (this.imgA && this.imgB) {
ctx.fillText(this.imgA.width + "x" + this.imgA.height, drawX + 8, drawY + 34);
ctx.textAlign = "right";
ctx.fillText(this.imgB.width + "x" + this.imgB.height, drawX + drawW - 10, drawY + 34);
}
ctx.restore();
};
const origOnExecuted = this.onExecuted;
this.onExecuted = function (output) {
if (origOnExecuted) origOnExecuted.apply(this, arguments);
if (output?.b64_a && output?.b64_b) {
// Clean up old images
if (this.imgA) {
this.imgA.src = "";
}
if (this.imgB) {
this.imgB.src = "";
}
this.imgA = new Image();
this.imgB = new Image();
// Handle errors gracefully
this.imgA.onerror = () => console.warn("[SBCODE.ImageCompareNode] Failed to load image A");
this.imgB.onerror = () => console.warn("[SBCODE.ImageCompareNode] Failed to load image B");
// Set sources after attaching handlers
// Handle both chunked arrays and plain strings
const imgA_data = Array.isArray(output.b64_a) ? output.b64_a.join("") : output.b64_a;
const imgB_data = Array.isArray(output.b64_b) ? output.b64_b.join("") : output.b64_b;
this.imgA.src = "data:image/png;base64," + imgA_data;
this.imgB.src = "data:image/png;base64," + imgB_data;
} else {
console.warn("[SBCODE.ImageCompareNode] Missing image base64 data.");
}
};
};
},
});
|