Delete js/loop_CropImage.js
This commit is contained in:
@@ -1,474 +0,0 @@
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { $el } from "../../../scripts/ui.js";
|
||||
import { ComfyWidgets } from "../../scripts/widgets.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "loop.clickCrop",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass !== "ImageCropLoop") return;
|
||||
|
||||
const DEFAULT_DIMENSIONS = {
|
||||
WIDTH: 200,
|
||||
HEIGHT: 200,
|
||||
MARGIN: 0,
|
||||
TITLE_HEIGHT: 24
|
||||
};
|
||||
|
||||
class CoordinateConverter {
|
||||
/* Core class that handles coordinate transformations */
|
||||
constructor(originalDimensions) {
|
||||
this.originalDimensions = originalDimensions;
|
||||
this.previewDimensions = { width: 0, height: 0 };
|
||||
}
|
||||
|
||||
toOriginalCoords(previewX, previewY, frame) {
|
||||
/* Converts preview coordinates to original image coordinates */
|
||||
if (!this.isValidDimensions()) return [previewX, previewY];
|
||||
|
||||
const scaleX = this.originalDimensions.width / frame.width;
|
||||
const scaleY = this.originalDimensions.height / frame.height;
|
||||
|
||||
return [
|
||||
Math.round(previewX * scaleX),
|
||||
Math.round(previewY * scaleY)
|
||||
];
|
||||
}
|
||||
|
||||
toPreviewCoords(originalX, originalY, frame) {
|
||||
/* Converts original coordinates to preview coordinates */
|
||||
if (!this.isValidDimensions()) return [originalX, originalY];
|
||||
|
||||
const scaleX = frame.width / this.originalDimensions.width;
|
||||
const scaleY = frame.height / this.originalDimensions.height;
|
||||
|
||||
return [
|
||||
Math.round(originalX * scaleX),
|
||||
Math.round(originalY * scaleY)
|
||||
];
|
||||
}
|
||||
|
||||
toOriginalSize(previewSize, frame) {
|
||||
/* Converts preview size to original image size */
|
||||
if (!this.isValidDimensions()) return previewSize;
|
||||
return Math.round(previewSize * (this.originalDimensions.width / frame.width));
|
||||
}
|
||||
|
||||
toPreviewSize(originalSize, frame) {
|
||||
/* Converts original size to preview size */
|
||||
if (!this.isValidDimensions()) return originalSize;
|
||||
// First, ensure originalSize doesn't exceed original image dimensions
|
||||
const maxOriginalSize = Math.min(originalSize, this.originalDimensions.width);
|
||||
return Math.round(maxOriginalSize * (frame.width / this.originalDimensions.width));
|
||||
}
|
||||
|
||||
constrainOriginalCoords(x, y, size) {
|
||||
/* Ensures coordinates stay within image bounds */
|
||||
if (!this.isValidDimensions()) return [x, y];
|
||||
|
||||
// Ensure size doesn't exceed original dimensions
|
||||
const constrainedSize = Math.min(size, this.originalDimensions.width);
|
||||
|
||||
const maxX = Math.max(0, this.originalDimensions.width - constrainedSize);
|
||||
const maxY = Math.max(0, this.originalDimensions.height - constrainedSize);
|
||||
|
||||
return [
|
||||
Math.max(0, Math.min(x, maxX)),
|
||||
Math.max(0, Math.min(y, maxY))
|
||||
];
|
||||
}
|
||||
|
||||
isValidDimensions() {
|
||||
return this.originalDimensions?.width && this.originalDimensions?.height;
|
||||
}
|
||||
|
||||
updateDimensions(originalWidth, originalHeight, previewWidth, previewHeight) {
|
||||
this.originalDimensions = { width: originalWidth, height: originalHeight };
|
||||
this.previewDimensions = { width: previewWidth, height: previewHeight };
|
||||
}
|
||||
}
|
||||
|
||||
// Define ClickCropWidget class inside the extension scope
|
||||
class ClickCropWidget {
|
||||
/* Handles the visual interface */
|
||||
constructor(node) {
|
||||
this.node = node;
|
||||
this.type = "HTML";
|
||||
this.name = "clickableFrame";
|
||||
this.value = null;
|
||||
|
||||
// this.originalOnMouseEnter = node.onMouseEnter;
|
||||
// this.originalOnMouseLeave = node.onMouseLeave;
|
||||
|
||||
// node.onMouseEnter = (e, pos) => {
|
||||
// if (this.node) {
|
||||
// this.node.flags = this.node.flags || {};
|
||||
// this.node.flags.pinned = true;
|
||||
// app.graph.setDirtyCanvas(true, false);
|
||||
// }
|
||||
// if (this.originalOnMouseEnter) {
|
||||
// this.originalOnMouseEnter.call(node, e, pos);
|
||||
// }
|
||||
// };
|
||||
|
||||
// node.onMouseLeave = (e, pos) => {
|
||||
// if (this.node) {
|
||||
// this.node.flags = this.node.flags || {};
|
||||
// this.node.flags.pinned = false;
|
||||
// app.graph.setDirtyCanvas(true, false);
|
||||
// }
|
||||
// if (this.originalOnMouseLeave) {
|
||||
// this.originalOnMouseLeave.call(node, e, pos);
|
||||
// }
|
||||
// };
|
||||
}
|
||||
|
||||
draw(ctx, node, widget_width, y, widget_height) {
|
||||
/* Main rendering function for the widget */
|
||||
if (!this.isValidImage(node)) return;
|
||||
|
||||
this.updateOriginalDimensions(node);
|
||||
const frame = this.calculateFrame(y, node.size[0], node.size[1]);
|
||||
node.frameArea = frame;
|
||||
|
||||
this.drawFrame(ctx, frame);
|
||||
this.drawMarker(ctx, node, frame);
|
||||
}
|
||||
|
||||
isValidImage(node) {
|
||||
return node.imgs?.[0]?.width && node.imgs?.[0]?.height;
|
||||
}
|
||||
|
||||
updateOriginalDimensions(node) {
|
||||
const img = node.imgs[0];
|
||||
const match = img.src.match(/_(\d+)_(\d+)_/);
|
||||
if (match) {
|
||||
node.converter.updateDimensions(parseInt(match[1]), parseInt(match[2]));
|
||||
this.updateMarkerPosition(node);
|
||||
}
|
||||
}
|
||||
|
||||
calculateFrame(y, nodeWidth, nodeHeight) {
|
||||
/* Determines the frame dimensions and position */
|
||||
const TITLE_HEIGHT = 24;
|
||||
const img = this.node.imgs[0];
|
||||
|
||||
const availWidth = nodeWidth;
|
||||
const availHeight = nodeHeight - TITLE_HEIGHT - y;
|
||||
|
||||
const imgRatio = img.naturalWidth / img.naturalHeight;
|
||||
const containerRatio = availWidth / availHeight;
|
||||
|
||||
let frameWidth, frameHeight;
|
||||
if (containerRatio > imgRatio) {
|
||||
frameHeight = availHeight;
|
||||
frameWidth = frameHeight * imgRatio;
|
||||
} else {
|
||||
frameWidth = availWidth;
|
||||
frameHeight = frameWidth / imgRatio;
|
||||
}
|
||||
|
||||
return {
|
||||
left: (availWidth - frameWidth) / 2,
|
||||
top: y + TITLE_HEIGHT + (availHeight - frameHeight) / 2,
|
||||
width: frameWidth,
|
||||
height: frameHeight
|
||||
};
|
||||
}
|
||||
|
||||
drawFrame(ctx, frame) {
|
||||
/* Renders the frame border */
|
||||
// ctx.strokeStyle = "#ff7500";
|
||||
ctx.strokeStyle = "green";
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeRect(frame.left, frame.top, frame.width, frame.height);
|
||||
}
|
||||
|
||||
drawMarker(ctx, node, frame) {
|
||||
/* Renders the selection marker */
|
||||
if (!node.clickMarker) return;
|
||||
|
||||
const previewSize = node.converter.toPreviewSize(
|
||||
node.widgets.find(w => w.name === 'size').value,
|
||||
frame
|
||||
);
|
||||
ctx.strokeStyle = "green";
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeRect(
|
||||
node.clickMarker.x,
|
||||
node.clickMarker.y,
|
||||
previewSize,
|
||||
previewSize
|
||||
);
|
||||
}
|
||||
|
||||
updateMarkerPosition(node) {
|
||||
/* Updates marker position when coordinates change */
|
||||
const frame = node.frameArea;
|
||||
if (!node.clickMarker || !frame) return;
|
||||
|
||||
const xWidget = node.widgets.find(w => w.name === 'x');
|
||||
const yWidget = node.widgets.find(w => w.name === 'y');
|
||||
|
||||
const [newPreviewX, newPreviewY] = node.converter.toPreviewCoords(
|
||||
xWidget.value,
|
||||
yWidget.value,
|
||||
frame
|
||||
);
|
||||
node.clickMarker.x = frame.left + newPreviewX;
|
||||
node.clickMarker.y = frame.top + newPreviewY;
|
||||
}
|
||||
}
|
||||
|
||||
nodeType.prototype.setupNode = function() {
|
||||
/* Initializes the node with default values and widgets */
|
||||
this.converter = new CoordinateConverter({ width: 0, height: 0 });
|
||||
this.size = [DEFAULT_DIMENSIONS.WIDTH, DEFAULT_DIMENSIONS.HEIGHT];
|
||||
|
||||
const widgets = {
|
||||
x: this.widgets.find(w => w.name === 'x'),
|
||||
y: this.widgets.find(w => w.name === 'y'),
|
||||
size: this.widgets.find(w => w.name === 'size')
|
||||
};
|
||||
|
||||
this.setupWidgetCallbacks(widgets);
|
||||
this.addCustomWidget(new ClickCropWidget(this));
|
||||
this.setupMouseHandler(widgets);
|
||||
|
||||
this.serialize_widgets = false;
|
||||
};
|
||||
|
||||
nodeType.prototype.initializeMarker = function(frame, widgets) {
|
||||
/* Initializes the marker with default values and widgets */
|
||||
if (!frame) return;
|
||||
const { x: xWidget, y: yWidget, size: sizeWidget } = widgets;
|
||||
|
||||
// Apply initial constraints
|
||||
const [constrainedX, constrainedY] = this.converter.constrainOriginalCoords(
|
||||
xWidget.value,
|
||||
yWidget.value,
|
||||
sizeWidget.value
|
||||
);
|
||||
|
||||
// Update widget values
|
||||
xWidget.value = constrainedX;
|
||||
yWidget.value = constrainedY;
|
||||
|
||||
// Convert to preview coordinates
|
||||
const [previewX, previewY] = this.converter.toPreviewCoords(
|
||||
constrainedX,
|
||||
constrainedY,
|
||||
frame
|
||||
);
|
||||
|
||||
// Create marker
|
||||
this.clickMarker = {
|
||||
x: frame.left + previewX,
|
||||
y: frame.top + previewY
|
||||
};
|
||||
|
||||
this.setDirtyCanvas(true);
|
||||
};
|
||||
|
||||
nodeType.prototype.setupWidgetCallbacks = function(widgets) {
|
||||
/* Sets up event handlers for widget value changes */
|
||||
const { x: xWidget, y: yWidget, size: sizeWidget } = widgets;
|
||||
|
||||
// Size widget callback with dynamic coordinate updates
|
||||
sizeWidget.callback = function(value) {
|
||||
const frame = this.frameArea;
|
||||
if (!frame) return;
|
||||
|
||||
// Constrain size to original image dimensions
|
||||
const maxSize = Math.min(this.converter.originalDimensions.width, this.converter.originalDimensions.height);
|
||||
|
||||
const constrainedValue = Math.min(value, maxSize);
|
||||
|
||||
// Round to nearest multiple of 8
|
||||
const roundedValue = Math.round(constrainedValue / 8) * 8;
|
||||
if (value !== roundedValue) {
|
||||
sizeWidget.value = roundedValue;
|
||||
this.onSizeChange(roundedValue, frame, widgets);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.clickMarker) {
|
||||
this.initializeMarker(frame, widgets);
|
||||
} else {
|
||||
this.onSizeChange(roundedValue, frame, widgets);
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
// X and Y widget callbacks with floor to ensure integer values
|
||||
const createAutoMarkerCallback = (axis) => {
|
||||
/* Creates handler for marker changes */
|
||||
return function(value) {
|
||||
const frame = this.frameArea;
|
||||
if (!frame) return;
|
||||
|
||||
// Ensure integer values
|
||||
const intValue = Math.floor(value);
|
||||
if (value !== intValue) {
|
||||
if (axis === 'x') {
|
||||
xWidget.value = intValue;
|
||||
} else {
|
||||
yWidget.value = intValue;
|
||||
}
|
||||
value = intValue;
|
||||
}
|
||||
|
||||
if (!this.clickMarker) {
|
||||
this.initializeMarker(frame, widgets);
|
||||
} else {
|
||||
this.createCoordinateCallback(axis, xWidget, yWidget, sizeWidget).call(this, value);
|
||||
}
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
xWidget.callback = createAutoMarkerCallback('x');
|
||||
yWidget.callback = createAutoMarkerCallback('y');
|
||||
};
|
||||
|
||||
nodeType.prototype.onSizeChange = function(newSize, frame, widgets) {
|
||||
/* Handles size change events */
|
||||
const { x: xWidget, y: yWidget, size: sizeWidget } = widgets;
|
||||
|
||||
if (!frame || !this.converter.isValidDimensions()) return;
|
||||
|
||||
// Get current original coordinates
|
||||
let currentX = Math.floor(xWidget.value);
|
||||
let currentY = Math.floor(yWidget.value);
|
||||
|
||||
// Apply constraints with new size
|
||||
const [constrainedX, constrainedY] = this.converter.constrainOriginalCoords(
|
||||
currentX,
|
||||
currentY,
|
||||
newSize
|
||||
);
|
||||
|
||||
// Update widget values if they were constrained
|
||||
if (currentX !== constrainedX || currentY !== constrainedY) {
|
||||
xWidget.value = constrainedX;
|
||||
yWidget.value = constrainedY;
|
||||
}
|
||||
|
||||
// Convert to preview coordinates and update marker
|
||||
const [previewX, previewY] = this.converter.toPreviewCoords(
|
||||
constrainedX,
|
||||
constrainedY,
|
||||
frame
|
||||
);
|
||||
|
||||
this.clickMarker = {
|
||||
x: frame.left + previewX,
|
||||
y: frame.top + previewY
|
||||
};
|
||||
|
||||
this.setDirtyCanvas(true);
|
||||
};
|
||||
|
||||
nodeType.prototype.createCoordinateCallback = function(axis, xWidget, yWidget, sizeWidget) {
|
||||
/* Creates handlers for coordinate changes */
|
||||
return function(value) {
|
||||
const frame = this.frameArea;
|
||||
if (!frame || !this.clickMarker) return;
|
||||
|
||||
// Ensure integer values for coordinates
|
||||
const currentCoords = axis === 'x'
|
||||
? [Math.floor(value), Math.floor(yWidget.value)]
|
||||
: [Math.floor(xWidget.value), Math.floor(value)];
|
||||
|
||||
const [constrainedX, constrainedY] = this.converter.constrainOriginalCoords(
|
||||
...currentCoords,
|
||||
sizeWidget.value
|
||||
);
|
||||
|
||||
const updatedValue = axis === 'x' ? constrainedX : constrainedY;
|
||||
if (value !== updatedValue) {
|
||||
if (axis === 'x') xWidget.value = updatedValue;
|
||||
else yWidget.value = updatedValue;
|
||||
return;
|
||||
}
|
||||
|
||||
const [previewX, previewY] = this.converter.toPreviewCoords(
|
||||
axis === 'x' ? constrainedX : xWidget.value,
|
||||
axis === 'y' ? constrainedY : yWidget.value,
|
||||
frame
|
||||
);
|
||||
|
||||
const markerSize = this.converter.toPreviewSize(sizeWidget.value, frame);
|
||||
this.clickMarker.x = frame.left + (axis === 'x' ? previewX : this.clickMarker.x - frame.left);
|
||||
this.clickMarker.y = frame.top + (axis === 'y' ? previewY : this.clickMarker.y - frame.top);
|
||||
|
||||
this.setDirtyCanvas(true);
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
nodeType.prototype.createSizeCallback = function(xWidget, yWidget, sizeWidget) {
|
||||
/* Creates handler for size changes */
|
||||
return function(value) {
|
||||
const frame = this.frameArea;
|
||||
if (!frame || !this.clickMarker) return;
|
||||
|
||||
// Get current original coordinates
|
||||
const [origX, origY] = this.converter.toOriginalCoords(
|
||||
this.clickMarker.x - frame.left,
|
||||
this.clickMarker.y - frame.top,
|
||||
frame
|
||||
);
|
||||
|
||||
// Constrain coordinates with new size value
|
||||
const [constrainedX, constrainedY] = this.converter.constrainOriginalCoords(origX, origY, value);
|
||||
|
||||
// Update x and y widgets if they were constrained
|
||||
if (xWidget.value !== constrainedX) xWidget.value = constrainedX;
|
||||
if (yWidget.value !== constrainedY) yWidget.value = constrainedY;
|
||||
|
||||
// Convert back to preview coordinates
|
||||
const [previewX, previewY] = this.converter.toPreviewCoords(constrainedX, constrainedY, frame);
|
||||
|
||||
// Update marker position
|
||||
this.clickMarker.x = frame.left + previewX;
|
||||
this.clickMarker.y = frame.top + previewY;
|
||||
|
||||
this.setDirtyCanvas(true);
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
nodeType.prototype.setupMouseHandler = function(widgets) {
|
||||
/* Handles mouse interactions for selection */
|
||||
this.onMouseDown = (e, pos) => {
|
||||
const frame = this.frameArea;
|
||||
if (!frame ||
|
||||
pos[0] < frame.left || pos[0] > frame.left + frame.width ||
|
||||
pos[1] < frame.top || pos[1] > frame.top + frame.height) return;
|
||||
|
||||
const { x: xWidget, y: yWidget, size: sizeWidget } = widgets;
|
||||
const relativeX = pos[0] - frame.left;
|
||||
const relativeY = pos[1] - frame.top;
|
||||
|
||||
const previewSize = this.converter.toPreviewSize(sizeWidget.value, frame);
|
||||
const markerX = Math.max(0, Math.min(relativeX - previewSize / 2, frame.width - previewSize));
|
||||
const markerY = Math.max(0, Math.min(relativeY - previewSize / 2, frame.height - previewSize));
|
||||
|
||||
const [origX, origY] = this.converter.toOriginalCoords(markerX, markerY, frame);
|
||||
const [constrainedX, constrainedY] = this.converter.constrainOriginalCoords(origX, origY, sizeWidget.value);
|
||||
|
||||
xWidget.value = constrainedX;
|
||||
yWidget.value = constrainedY;
|
||||
|
||||
const [previewX, previewY] = this.converter.toPreviewCoords(constrainedX, constrainedY, frame);
|
||||
this.clickMarker = { x: frame.left + previewX, y: frame.top + previewY };
|
||||
this.setDirtyCanvas(true);
|
||||
};
|
||||
};
|
||||
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
|
||||
nodeType.prototype.onNodeCreated = function() {
|
||||
onNodeCreated?.apply(this, arguments);
|
||||
this.setupNode();
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user