Files
MajoorWaldi-ComfyUI-Majoor-…/js/preview/interactions/crop.js
T

178 lines
8.3 KiB
JavaScript

import { computeCropRect, clampCropCenter, clampCropScale } from "../crop.js";
import {
getCropControlState,
setCropControlState,
isFreeCropResizeEnabled,
getCropInteractionMode,
getCropCursor,
canvasToSourcePoint,
resolveCropAspectRatioValue,
cropRectFromAnchor,
freeCropRectFromAnchor
} from "../nodes/crop.js";
import { getCanvasPointer, screenToWorld } from "../shared/geometry.js";
import { resetNodeWidgetsToDefaults, widgetNumber } from "../shared/widgets.js";
function attachInteractions(node, ctx) {
const st = node.__imageops_state;
if (!st?.canvas || st.cropInteractiveHooked) return;
st.cropInteractiveHooked = true;
let moveRafPending = false;
const canvas = st.canvas;
const listenerOptions = st._abortController?.signal ? { signal: st._abortController.signal } : void 0;
const worldPt = (event) => {
const raw = getCanvasPointer(canvas, event);
return screenToWorld(raw.x, raw.y, st.previewZoom ?? 1, st.previewPanX ?? 0, st.previewPanY ?? 0, canvas.width);
};
st.cropResetButton?.addEventListener("click", (event) => {
event.preventDefault();
resetNodeWidgetsToDefaults(node);
st.cropAspectRatio = null;
st.cropDrag = null;
canvas.style.cursor = "default";
ctx.schedule(node, () => {
ctx.startLoopIfVideo(node);
ctx.refreshDependents(node);
}, 0);
ctx.refreshNode(node);
}, listenerOptions);
canvas.addEventListener("pointerdown", (event) => {
const geometry = st.cropGeometry;
if (!geometry) return;
const point = worldPt(event);
const mode = getCropInteractionMode(geometry, point.x, point.y);
if (!mode) return;
event.preventDefault();
event.stopPropagation();
try {
canvas.setPointerCapture?.(event.pointerId);
} catch {
}
const controls = getCropControlState(node, geometry.sourceWidth, geometry.sourceHeight);
st.cropDrag = {
pointerId: event.pointerId,
mode,
startCanvasX: point.x,
startCanvasY: point.y,
startCenterX: controls.centerX,
startCenterY: controls.centerY,
startScale: controls.scale,
startCropX: geometry.cropX,
startCropY: geometry.cropY,
startCropWidth: geometry.cropWidth,
startCropHeight: geometry.cropHeight,
startOutputWidth: Math.max(1, Math.round(widgetNumber(node, "width", geometry.cropWidth))),
startOutputHeight: Math.max(1, Math.round(widgetNumber(node, "height", geometry.cropHeight)))
};
canvas.style.cursor = getCropCursor(mode);
}, listenerOptions);
canvas.addEventListener("pointermove", (event) => {
const point = worldPt(event);
const drag = st.cropDrag;
const geometry = st.cropGeometry;
if (!drag || drag.pointerId !== event.pointerId || !geometry) {
canvas.style.cursor = getCropCursor(getCropInteractionMode(geometry, point.x, point.y));
return;
}
event.preventDefault();
const targetRatio = resolveCropAspectRatioValue(node, geometry.sourceWidth, geometry.sourceHeight);
const maxRect = computeCropRect(geometry.sourceWidth, geometry.sourceHeight, targetRatio, 0.5, 0.5, 1);
let nextCenterX = drag.startCenterX;
let nextCenterY = drag.startCenterY;
let nextScale = drag.startScale;
if (drag.mode === "move") {
const deltaX = (point.x - drag.startCanvasX) * geometry.sourceWidth / Math.max(1, geometry.fitDrawWidth);
const deltaY = (point.y - drag.startCanvasY) * geometry.sourceHeight / Math.max(1, geometry.fitDrawHeight);
let centerPx = drag.startCenterX * geometry.sourceWidth + deltaX;
let centerPy = drag.startCenterY * geometry.sourceHeight + deltaY;
centerPx = Math.max(drag.startCropWidth / 2, Math.min(geometry.sourceWidth - drag.startCropWidth / 2, centerPx));
centerPy = Math.max(drag.startCropHeight / 2, Math.min(geometry.sourceHeight - drag.startCropHeight / 2, centerPy));
nextCenterX = clampCropCenter(centerPx / Math.max(1, geometry.sourceWidth));
nextCenterY = clampCropCenter(centerPy / Math.max(1, geometry.sourceHeight));
} else {
const pointerSource = canvasToSourcePoint(geometry, point.x, point.y);
const freeResize = isFreeCropResizeEnabled(node);
const anchor = (() => {
if (drag.mode === "n") return { x: drag.startCropX + drag.startCropWidth / 2, y: drag.startCropY + drag.startCropHeight };
if (drag.mode === "e") return { x: drag.startCropX, y: drag.startCropY + drag.startCropHeight / 2 };
if (drag.mode === "s") return { x: drag.startCropX + drag.startCropWidth / 2, y: drag.startCropY };
if (drag.mode === "w") return { x: drag.startCropX + drag.startCropWidth, y: drag.startCropY + drag.startCropHeight / 2 };
if (drag.mode === "nw") return { x: drag.startCropX + drag.startCropWidth, y: drag.startCropY + drag.startCropHeight };
if (drag.mode === "ne") return { x: drag.startCropX, y: drag.startCropY + drag.startCropHeight };
if (drag.mode === "sw") return { x: drag.startCropX + drag.startCropWidth, y: drag.startCropY };
return { x: drag.startCropX, y: drag.startCropY };
})();
if (freeResize) {
const nextRect = freeCropRectFromAnchor(
anchor,
drag.mode,
pointerSource,
{ x: drag.startCropX, y: drag.startCropY, cropWidth: drag.startCropWidth, cropHeight: drag.startCropHeight },
geometry.sourceWidth,
geometry.sourceHeight
);
const cropWidth = nextRect.cropWidth;
const cropHeight = nextRect.cropHeight;
const centerPx = nextRect.x + cropWidth / 2;
const centerPy = nextRect.y + cropHeight / 2;
const widthDensity = drag.startOutputWidth / Math.max(1, drag.startCropWidth);
const heightDensity = drag.startOutputHeight / Math.max(1, drag.startCropHeight);
const uniformDensity = Math.max(1e-4, Math.sqrt(Math.max(1e-4, widthDensity * heightDensity)));
const outputWidth = Math.max(1, Math.round(cropWidth * uniformDensity));
const outputHeight = Math.max(1, Math.round(cropHeight * uniformDensity));
ctx.setCropOutputDimensions(node, outputWidth, outputHeight, false);
const freeRatio = Math.max(1e-4, outputWidth / Math.max(1, outputHeight));
const freeMaxRect = computeCropRect(geometry.sourceWidth, geometry.sourceHeight, freeRatio, 0.5, 0.5, 1);
nextCenterX = clampCropCenter(centerPx / Math.max(1, geometry.sourceWidth));
nextCenterY = clampCropCenter(centerPy / Math.max(1, geometry.sourceHeight));
nextScale = clampCropScale(Math.min(
cropWidth / Math.max(1, freeMaxRect.cropWidth),
cropHeight / Math.max(1, freeMaxRect.cropHeight)
));
} else {
const next = cropRectFromAnchor(anchor, drag.mode, targetRatio, pointerSource, maxRect, geometry.sourceWidth, geometry.sourceHeight);
nextCenterX = next.centerX;
nextCenterY = next.centerY;
nextScale = next.scale;
}
}
setCropControlState(node, nextCenterX, nextCenterY, nextScale, false);
if (!moveRafPending) {
moveRafPending = true;
requestAnimationFrame(() => {
moveRafPending = false;
ctx.refreshPreviewOnly(node);
});
}
}, listenerOptions);
const releaseDrag = (event) => {
if (!st.cropDrag || st.cropDrag.pointerId !== event.pointerId) return;
st.cropDrag = null;
try {
canvas.releasePointerCapture?.(event.pointerId);
} catch {
}
const point = worldPt(event);
canvas.style.cursor = getCropCursor(getCropInteractionMode(st.cropGeometry, point.x, point.y));
const controls = getCropControlState(node, 1, 1);
setCropControlState(node, controls.centerX, controls.centerY, controls.scale, true);
if (st.cropGeometry && isFreeCropResizeEnabled(node)) {
ctx.setCropOutputDimensions(
node,
Math.max(1, Math.round(widgetNumber(node, "width", st.cropGeometry.cropWidth))),
Math.max(1, Math.round(widgetNumber(node, "height", st.cropGeometry.cropHeight))),
true
);
}
ctx.refreshNode(node);
};
canvas.addEventListener("pointerup", releaseDrag, listenerOptions);
canvas.addEventListener("pointercancel", releaseDrag, listenerOptions);
canvas.addEventListener("lostpointercapture", releaseDrag, listenerOptions);
canvas.addEventListener("pointerleave", () => {
if (!st.cropDrag) canvas.style.cursor = "default";
}, listenerOptions);
}
export {
attachInteractions
};