Fixing an overlay bug in the RS Crop Image node
This commit is contained in:
+100
-94
@@ -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() {
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user