mirror of
https://github.com/Azornes/Comfyui-LayerForge.git
synced 2026-03-22 13:12:10 -03:00
Introduces ShapeTool to allow users to define custom polygonal output areas by holding Shift+S and clicking to add points. The selected shape is used to crop and mask images and layers, and is visualized on the canvas. Updates Canvas, CanvasIO, CanvasInteractions, CanvasLayers, CanvasRenderer, and types to support shape-based output areas, including shape-aware import, export, and rendering logic.
126 lines
3.9 KiB
JavaScript
126 lines
3.9 KiB
JavaScript
import { createModuleLogger } from "./utils/LoggerUtils.js";
|
|
const log = createModuleLogger('ShapeTool');
|
|
export class ShapeTool {
|
|
constructor(canvas) {
|
|
this.isActive = false;
|
|
this.canvas = canvas;
|
|
this.shape = {
|
|
points: [],
|
|
isClosed: false,
|
|
};
|
|
}
|
|
toggle() {
|
|
this.isActive = !this.isActive;
|
|
if (this.isActive) {
|
|
log.info('ShapeTool activated. Press "S" to exit.');
|
|
this.reset();
|
|
}
|
|
else {
|
|
log.info('ShapeTool deactivated.');
|
|
this.reset();
|
|
}
|
|
this.canvas.render();
|
|
}
|
|
activate() {
|
|
if (!this.isActive) {
|
|
this.isActive = true;
|
|
log.info('ShapeTool activated. Hold Shift+S to draw.');
|
|
this.reset();
|
|
this.canvas.render();
|
|
}
|
|
}
|
|
deactivate() {
|
|
if (this.isActive) {
|
|
this.isActive = false;
|
|
log.info('ShapeTool deactivated.');
|
|
this.reset();
|
|
this.canvas.render();
|
|
}
|
|
}
|
|
addPoint(point) {
|
|
if (this.shape.isClosed) {
|
|
this.reset();
|
|
}
|
|
// Check if the new point is close to the start point to close the shape
|
|
if (this.shape.points.length > 2) {
|
|
const firstPoint = this.shape.points[0];
|
|
const dx = point.x - firstPoint.x;
|
|
const dy = point.y - firstPoint.y;
|
|
if (Math.sqrt(dx * dx + dy * dy) < 10 / this.canvas.viewport.zoom) {
|
|
this.closeShape();
|
|
return;
|
|
}
|
|
}
|
|
this.shape.points.push(point);
|
|
this.canvas.render();
|
|
}
|
|
closeShape() {
|
|
if (this.shape.points.length > 2) {
|
|
this.shape.isClosed = true;
|
|
log.info('Shape closed with', this.shape.points.length, 'points.');
|
|
this.canvas.defineOutputAreaWithShape(this.shape);
|
|
this.reset();
|
|
}
|
|
this.canvas.render();
|
|
}
|
|
getBoundingBox() {
|
|
if (this.shape.points.length === 0) {
|
|
return null;
|
|
}
|
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
this.shape.points.forEach(p => {
|
|
minX = Math.min(minX, p.x);
|
|
minY = Math.min(minY, p.y);
|
|
maxX = Math.max(maxX, p.x);
|
|
maxY = Math.max(maxY, p.y);
|
|
});
|
|
return {
|
|
x: minX,
|
|
y: minY,
|
|
width: maxX - minX,
|
|
height: maxY - minY,
|
|
};
|
|
}
|
|
reset() {
|
|
this.shape = {
|
|
points: [],
|
|
isClosed: false,
|
|
};
|
|
log.info('ShapeTool reset.');
|
|
this.canvas.render();
|
|
}
|
|
render(ctx) {
|
|
if (this.shape.points.length === 0) {
|
|
return;
|
|
}
|
|
ctx.save();
|
|
ctx.strokeStyle = 'rgba(0, 255, 255, 0.9)';
|
|
ctx.lineWidth = 2 / this.canvas.viewport.zoom;
|
|
ctx.setLineDash([8 / this.canvas.viewport.zoom, 4 / this.canvas.viewport.zoom]);
|
|
ctx.beginPath();
|
|
const startPoint = this.shape.points[0];
|
|
ctx.moveTo(startPoint.x, startPoint.y);
|
|
for (let i = 1; i < this.shape.points.length; i++) {
|
|
ctx.lineTo(this.shape.points[i].x, this.shape.points[i].y);
|
|
}
|
|
if (this.shape.isClosed) {
|
|
ctx.closePath();
|
|
ctx.fillStyle = 'rgba(0, 255, 255, 0.2)';
|
|
ctx.fill();
|
|
}
|
|
else if (this.isActive) {
|
|
// Draw a line to the current mouse position
|
|
ctx.lineTo(this.canvas.lastMousePosition.x, this.canvas.lastMousePosition.y);
|
|
}
|
|
ctx.stroke();
|
|
// Draw vertices
|
|
ctx.fillStyle = 'rgba(0, 255, 255, 1)';
|
|
this.shape.points.forEach((point, index) => {
|
|
ctx.beginPath();
|
|
ctx.arc(point.x, point.y, 4 / this.canvas.viewport.zoom, 0, 2 * Math.PI);
|
|
ctx.fill();
|
|
});
|
|
ctx.restore();
|
|
}
|
|
}
|