Fixing an overlay bug in the RS Crop Image node

This commit is contained in:
Raykosan
2026-04-02 18:05:57 +03:00
parent 217c78cb5b
commit e2bdbcd544
+100 -94
View File
@@ -4,48 +4,6 @@ import { api } from "../../scripts/api.js";
app.registerExtension({
name: "RaykoStudio.CropImage",
setup() {
api.addEventListener("rayko.rscrop.show", (event) => {
const { node_id, image_url, image_width, image_height } = event.detail;
const node = app.graph.getNodeById(node_id);
if (node) {
node.image.src = image_url + "&t=" + Date.now();
node.image.onload = () => {
node.imageReady = true;
node.imageWidth = image_width || node.image.width;
node.imageHeight = image_height || node.image.height;
const overlay = document.querySelector(`canvas[style*="z-index: 1000"]`);
if (overlay) {
overlay.style.display = "block";
}
if (node.properties?.crop_rect) {
try {
const r = JSON.parse(node.properties.crop_rect);
if (r.x >= node.imageWidth) r.x = 0;
if (r.y >= node.imageHeight) r.y = 0;
if (r.x + r.width > node.imageWidth) r.width = Math.max(64, node.imageWidth - r.x);
if (r.y + r.height > node.imageHeight) r.height = Math.max(64, node.imageHeight - r.y);
node.properties.crop_rect = JSON.stringify(r);
node._cropRect = r;
} catch (e) {}
}
setTimeout(() => {
_lastRect = null;
syncPosition();
}, 100);
node.setDirtyCanvas(true, true);
};
if (node.size[1] < 650) node.setSize([node.size[0], 650]);
node.currentStatus = "🖱️ Drag to select crop area, then APPROVE!";
app.canvas.centerOnNode(node);
node.setDirtyCanvas(true, true);
}
});
},
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name !== "RSCropImage") return;
@@ -61,8 +19,8 @@ app.registerExtension({
node.buttons = [];
node.imageWidth = 0;
node.imageHeight = 0;
node._cropRect = { x: 0, y: 0, width: 256, height: 256 };
node._overlayCanvas = null;
const cropDataWidget = node.widgets?.find(w => w.name === "crop_data");
if (cropDataWidget) {
@@ -85,9 +43,9 @@ app.registerExtension({
node.setSize([500, 650]);
let _overlayCanvas = null;
let _syncRunning = false;
let _lastRect = null;
let _lastScale = 1;
let _isSelecting = false;
let _isDragging = false;
let _isResizing = false;
@@ -109,10 +67,12 @@ app.registerExtension({
node.properties.crop_rect = jsonStr;
if (cropDataWidget) cropDataWidget.value = jsonStr;
drawOverlay();
node.setDirtyCanvas(true, true);
};
// ⚠️ ИСПРАВЛЕНО: Точно как в onDrawForeground
const calculateImageRect = () => {
if (!app.canvas) return null;
if (!app.canvas || !node.imageReady) return null;
const ds = app.canvas.ds;
const canvasEl = app.canvas.canvas;
@@ -139,8 +99,10 @@ app.registerExtension({
const footerHeight = 60;
const outputSlotsHeight = 40;
const availableHeight = (node.size[1] * scale) - (titleBarHeight * scale) - (widgetsTotalHeight * scale) - (footerHeight * scale) - (padding * 2 * scale) - outputSlotsHeight;
const availableWidth = (node.size[0] * scale) - (padding * 2 * scale);
// ⚠️ Точно как в onDrawForeground
const startY = titleBarHeight + widgetsTotalHeight + padding + outputSlotsHeight;
const availableHeight = node.size[1] - startY - footerHeight - padding;
const availableWidth = node.size[0] - (padding * 2);
if (availableWidth <= 0 || availableHeight <= 0) return null;
@@ -148,7 +110,7 @@ app.registerExtension({
let drawH = availableHeight;
let contentScale = 1;
if (node.imageReady && node.image && node.imageWidth > 0) {
if (node.imageWidth > 0 && node.imageHeight > 0) {
const imgRatio = node.imageWidth / node.imageHeight;
if (availableWidth / availableHeight > imgRatio) {
@@ -162,14 +124,15 @@ app.registerExtension({
contentScale = drawW / node.imageWidth;
}
const drawX = nodeScreenX + (padding * scale) + ((availableWidth - drawW) / 2);
const drawY = nodeScreenY + (titleBarHeight * scale) + (widgetsTotalHeight * scale) + (padding * scale) + ((availableHeight - drawH) / 2) + outputSlotsHeight;
// ⚠️ Конвертируем в экранные координаты
const drawX = nodeScreenX + (padding * scale) + ((availableWidth - drawW) * scale / 2);
const drawY = nodeScreenY + (startY * scale) + ((availableHeight - drawH) * scale / 2);
return {
left: drawX,
top: drawY,
width: drawW,
height: drawH,
width: drawW * scale,
height: drawH * scale,
scale: contentScale
};
};
@@ -180,54 +143,60 @@ app.registerExtension({
const syncLoop = () => {
if (!_syncRunning) return;
syncPosition();
if (_overlayCanvas) requestAnimationFrame(syncLoop);
if (node._overlayCanvas) requestAnimationFrame(syncLoop);
else _syncRunning = false;
};
requestAnimationFrame(syncLoop);
};
const syncPosition = () => {
if (!_overlayCanvas) return;
if (!node._overlayCanvas) return;
const imgRect = calculateImageRect();
if (!imgRect) return;
const currentScale = app.canvas.ds.scale;
const scaleChanged = Math.abs(_lastScale - currentScale) > 0.01;
const hasChanged = !_lastRect ||
Math.abs(_lastRect.left - imgRect.left) > 0.5 ||
Math.abs(_lastRect.top - imgRect.top) > 0.5 ||
Math.abs(_lastRect.width - imgRect.width) > 0.5 ||
Math.abs(_lastRect.height - imgRect.height) > 0.5;
Math.abs(_lastRect.height - imgRect.height) > 0.5 ||
scaleChanged;
if (hasChanged) {
_lastScale = currentScale;
_lastRect = { ...imgRect };
_overlayCanvas.style.left = `${imgRect.left}px`;
_overlayCanvas.style.top = `${imgRect.top}px`;
_overlayCanvas.style.width = `${imgRect.width}px`;
_overlayCanvas.style.height = `${imgRect.height}px`;
_overlayCanvas.dataset.scale = imgRect.scale;
node._overlayCanvas.style.left = `${imgRect.left}px`;
node._overlayCanvas.style.top = `${imgRect.top}px`;
node._overlayCanvas.style.width = `${imgRect.width}px`;
node._overlayCanvas.style.height = `${imgRect.height}px`;
node._overlayCanvas.dataset.scale = imgRect.scale;
node._overlayCanvas.style.display = "block"; // ⚠️ Всегда показываем
drawOverlay();
}
};
const createOverlayCanvas = () => {
if (_overlayCanvas) return;
_overlayCanvas = document.createElement("canvas");
_overlayCanvas.style.cssText = `
if (node._overlayCanvas) return;
node._overlayCanvas = document.createElement("canvas");
node._overlayCanvas.style.cssText = `
position: fixed !important; z-index: 1000 !important;
pointer-events: auto !important; cursor: crosshair !important;
background: transparent !important; touch-action: none;
border: 1px dashed #00FF00 !important; box-sizing: border-box !important;
display: none;
`;
document.body.appendChild(_overlayCanvas);
document.body.appendChild(node._overlayCanvas);
_overlayCanvas.addEventListener("mousedown", (e) => {
node._overlayCanvas.addEventListener("mousedown", (e) => {
e.preventDefault();
e.stopPropagation();
const rect = _overlayCanvas.getBoundingClientRect();
const rect = node._overlayCanvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const scale = parseFloat(_overlayCanvas.dataset.scale || "1");
const scale = parseFloat(node._overlayCanvas.dataset.scale || "1");
const imgX = x / scale;
const imgY = y / scale;
@@ -266,16 +235,16 @@ app.registerExtension({
_startY = y;
_startRect = { ...node._cropRect };
_overlayCanvas.setPointerCapture(e.pointerId || 1);
node._overlayCanvas.setPointerCapture(e.pointerId || 1);
});
_overlayCanvas.addEventListener("mousemove", (e) => {
node._overlayCanvas.addEventListener("mousemove", (e) => {
if (!_isSelecting && !_isDragging && !_isResizing) return;
const rect = _overlayCanvas.getBoundingClientRect();
const rect = node._overlayCanvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const scale = parseFloat(_overlayCanvas.dataset.scale || "1");
const scale = parseFloat(node._overlayCanvas.dataset.scale || "1");
const deltaX = (x - _startX) / scale;
const deltaY = (y - _startY) / scale;
@@ -335,31 +304,31 @@ app.registerExtension({
updateCropRect();
});
_overlayCanvas.addEventListener("mouseup", (e) => {
node._overlayCanvas.addEventListener("mouseup", (e) => {
_isSelecting = false;
_isDragging = false;
_isResizing = false;
_resizeHandle = null;
_overlayCanvas.releasePointerCapture(e.pointerId || 1);
node._overlayCanvas.releasePointerCapture(e.pointerId || 1);
});
startSyncLoop();
};
const drawOverlay = () => {
if (!_overlayCanvas || !_lastRect) return;
const width = parseFloat(_overlayCanvas.style.width || "0");
const height = parseFloat(_overlayCanvas.style.height || "0");
if (!node._overlayCanvas) return;
const width = parseFloat(node._overlayCanvas.style.width || "0");
const height = parseFloat(node._overlayCanvas.style.height || "0");
if (width <= 0 || height <= 0) return;
const dpr = window.devicePixelRatio || 1;
_overlayCanvas.width = width * dpr;
_overlayCanvas.height = height * dpr;
const ctx = _overlayCanvas.getContext("2d");
node._overlayCanvas.width = width * dpr;
node._overlayCanvas.height = height * dpr;
const ctx = node._overlayCanvas.getContext("2d");
ctx.scale(dpr, dpr);
ctx.clearRect(0, 0, width, height);
const scale = parseFloat(_overlayCanvas.dataset.scale || "1");
const scale = parseFloat(node._overlayCanvas.dataset.scale || "1");
const cropX = node._cropRect.x * scale;
const cropY = node._cropRect.y * scale;
@@ -471,13 +440,11 @@ app.registerExtension({
const onResize = node.onResize;
node.onResize = function(size) {
if (onResize) onResize.apply(this, arguments);
_lastRect = null;
};
const onMove = node.onMove;
node.onMove = function() {
if (onMove) onMove.apply(this, arguments);
_lastRect = null;
};
node.onMouseMove = function(event, pos, graphPos) {
@@ -551,9 +518,9 @@ app.registerExtension({
node.onRemoved = function() {
_syncRunning = false;
if (_overlayCanvas) {
_overlayCanvas.remove();
_overlayCanvas = null;
if (node._overlayCanvas) {
node._overlayCanvas.remove();
node._overlayCanvas = null;
}
fetch("/rayko/rscrop/cleanup", {
@@ -565,20 +532,59 @@ app.registerExtension({
}).catch(err => {});
};
createOverlayCanvas();
const originalOnRendered = app.canvas.onRendered;
app.canvas.onRendered = function() {
if (originalOnRendered) originalOnRendered.apply(this, arguments);
if (_overlayCanvas && node.imageReady) {
_lastRect = null;
syncPosition();
api.addEventListener("rayko.rscrop.show", (event) => {
const { node_id, image_url, image_width, image_height } = event.detail;
const currentId = node.id;
if (String(node_id) === String(currentId)) {
node.image.src = image_url + "&t=" + Date.now();
node.image.onload = () => {
console.log("[RS Crop 🦊] Image loaded, showing overlay");
node.imageReady = true;
node.imageWidth = image_width || node.image.width;
node.imageHeight = image_height || node.image.height;
// ⚠️ Показываем оверлей
if (node._overlayCanvas) {
node._overlayCanvas.style.display = "block";
console.log("[RS Crop 🦊] Overlay display set to block");
}
if (node.properties?.crop_rect) {
try {
const r = JSON.parse(node.properties.crop_rect);
if (r.x >= node.imageWidth) r.x = 0;
if (r.y >= node.imageHeight) r.y = 0;
if (r.x + r.width > node.imageWidth) r.width = Math.max(64, node.imageWidth - r.x);
if (r.y + r.height > node.imageHeight) r.height = Math.max(64, node.imageHeight - r.y);
node.properties.crop_rect = JSON.stringify(r);
node._cropRect = r;
} catch (e) {}
}
setTimeout(() => {
_lastRect = null;
_lastScale = 1;
syncPosition();
node.setDirtyCanvas(true, true);
}, 100);
};
if (node.size[1] < 650) node.setSize([node.size[0], 650]);
node.currentStatus = "🖱️ Drag to select crop area, then APPROVE!";
app.canvas.centerOnNode(node);
node.setDirtyCanvas(true, true);
}
};
});
createOverlayCanvas();
} catch (error) {
console.error("[RS Crop 🦊] Critical Error:", error);
}
};
},
setup() {
}
});