mirror of
https://github.com/Azornes/Comfyui-LayerForge.git
synced 2026-03-22 05:02:11 -03:00
Introduces x/y coordinates to MaskTool for world-space positioning, allowing the mask to extend beyond the output area. Updates mask drawing, export, and rendering logic to account for mask position. Ensures mask position is updated when moving or resizing the canvas, and preserves mask content during canvas resizing. Improves mask extraction and rendering accuracy.
225 lines
8.4 KiB
JavaScript
225 lines
8.4 KiB
JavaScript
import {createModuleLogger} from "./utils/LoggerUtils.js";
|
|
const log = createModuleLogger('Mask_tool');
|
|
|
|
export class MaskTool {
|
|
constructor(canvasInstance) {
|
|
this.canvasInstance = canvasInstance;
|
|
this.mainCanvas = canvasInstance.canvas;
|
|
this.maskCanvas = document.createElement('canvas');
|
|
this.maskCtx = this.maskCanvas.getContext('2d');
|
|
|
|
// Add position coordinates for the mask
|
|
this.x = 0;
|
|
this.y = 0;
|
|
|
|
this.isActive = false;
|
|
this.brushSize = 20;
|
|
this.brushStrength = 0.5;
|
|
this.brushSoftness = 0.5;
|
|
this.isDrawing = false;
|
|
this.lastPosition = null;
|
|
|
|
this.initMaskCanvas();
|
|
}
|
|
|
|
setBrushSoftness(softness) {
|
|
this.brushSoftness = Math.max(0, Math.min(1, softness));
|
|
}
|
|
|
|
initMaskCanvas() {
|
|
// Create a larger mask canvas that can extend beyond the output area
|
|
const extraSpace = 2000; // Allow for a generous drawing area outside the output area
|
|
this.maskCanvas.width = this.canvasInstance.width + extraSpace;
|
|
this.maskCanvas.height = this.canvasInstance.height + extraSpace;
|
|
|
|
// Position the mask's origin point in the center of the expanded canvas
|
|
// This allows drawing in any direction from the output area
|
|
this.x = -extraSpace / 2;
|
|
this.y = -extraSpace / 2;
|
|
|
|
this.maskCtx.clearRect(0, 0, this.maskCanvas.width, this.maskCanvas.height);
|
|
log.info(`Initialized mask canvas with extended size: ${this.maskCanvas.width}x${this.maskCanvas.height}, origin at (${this.x}, ${this.y})`);
|
|
}
|
|
|
|
activate() {
|
|
this.isActive = true;
|
|
this.canvasInstance.interaction.mode = 'drawingMask';
|
|
if (this.canvasInstance.canvasState && this.canvasInstance.canvasState.maskUndoStack.length === 0) {
|
|
this.canvasInstance.canvasState.saveMaskState();
|
|
}
|
|
this.canvasInstance.updateHistoryButtons();
|
|
|
|
log.info("Mask tool activated");
|
|
}
|
|
|
|
deactivate() {
|
|
this.isActive = false;
|
|
this.canvasInstance.interaction.mode = 'none';
|
|
this.canvasInstance.updateHistoryButtons();
|
|
|
|
log.info("Mask tool deactivated");
|
|
}
|
|
|
|
setBrushSize(size) {
|
|
this.brushSize = Math.max(1, size);
|
|
}
|
|
|
|
setBrushStrength(strength) {
|
|
this.brushStrength = Math.max(0, Math.min(1, strength));
|
|
}
|
|
|
|
handleMouseDown(worldCoords) {
|
|
if (!this.isActive) return;
|
|
this.isDrawing = true;
|
|
this.lastPosition = worldCoords;
|
|
this.draw(worldCoords);
|
|
}
|
|
|
|
handleMouseMove(worldCoords) {
|
|
if (!this.isActive || !this.isDrawing) return;
|
|
this.draw(worldCoords);
|
|
this.lastPosition = worldCoords;
|
|
}
|
|
|
|
handleMouseUp() {
|
|
if (!this.isActive) return;
|
|
if (this.isDrawing) {
|
|
this.isDrawing = false;
|
|
this.lastPosition = null;
|
|
if (this.canvasInstance.canvasState) {
|
|
this.canvasInstance.canvasState.saveMaskState();
|
|
}
|
|
}
|
|
}
|
|
|
|
draw(worldCoords) {
|
|
if (!this.lastPosition) {
|
|
this.lastPosition = worldCoords;
|
|
}
|
|
|
|
// Convert world coordinates to mask canvas coordinates
|
|
// Account for the mask's position in world space
|
|
const canvasLastX = this.lastPosition.x - this.x;
|
|
const canvasLastY = this.lastPosition.y - this.y;
|
|
const canvasX = worldCoords.x - this.x;
|
|
const canvasY = worldCoords.y - this.y;
|
|
|
|
// Check if drawing is within the expanded canvas bounds
|
|
// Since our canvas is much larger now, this should rarely be an issue
|
|
const canvasWidth = this.maskCanvas.width;
|
|
const canvasHeight = this.maskCanvas.height;
|
|
|
|
if (canvasX >= 0 && canvasX < canvasWidth &&
|
|
canvasY >= 0 && canvasY < canvasHeight &&
|
|
canvasLastX >= 0 && canvasLastX < canvasWidth &&
|
|
canvasLastY >= 0 && canvasLastY < canvasHeight) {
|
|
|
|
this.maskCtx.beginPath();
|
|
this.maskCtx.moveTo(canvasLastX, canvasLastY);
|
|
this.maskCtx.lineTo(canvasX, canvasY);
|
|
const gradientRadius = this.brushSize / 2;
|
|
|
|
if (this.brushSoftness === 0) {
|
|
this.maskCtx.strokeStyle = `rgba(255, 255, 255, ${this.brushStrength})`;
|
|
} else {
|
|
const innerRadius = gradientRadius * this.brushSoftness;
|
|
const gradient = this.maskCtx.createRadialGradient(
|
|
canvasX, canvasY, innerRadius,
|
|
canvasX, canvasY, gradientRadius
|
|
);
|
|
gradient.addColorStop(0, `rgba(255, 255, 255, ${this.brushStrength})`);
|
|
gradient.addColorStop(1, `rgba(255, 255, 255, 0)`);
|
|
this.maskCtx.strokeStyle = gradient;
|
|
}
|
|
|
|
this.maskCtx.lineWidth = this.brushSize;
|
|
this.maskCtx.lineCap = 'round';
|
|
this.maskCtx.lineJoin = 'round';
|
|
this.maskCtx.globalCompositeOperation = 'source-over';
|
|
this.maskCtx.stroke();
|
|
} else {
|
|
log.debug(`Drawing outside mask canvas bounds: (${canvasX}, ${canvasY})`);
|
|
}
|
|
}
|
|
|
|
clear() {
|
|
this.maskCtx.clearRect(0, 0, this.maskCanvas.width, this.maskCanvas.height);
|
|
if (this.isActive && this.canvasInstance.canvasState) {
|
|
this.canvasInstance.canvasState.saveMaskState();
|
|
}
|
|
}
|
|
|
|
getMask() {
|
|
return this.maskCanvas;
|
|
}
|
|
|
|
getMaskImageWithAlpha() {
|
|
const tempCanvas = document.createElement('canvas');
|
|
tempCanvas.width = this.maskCanvas.width;
|
|
tempCanvas.height = this.maskCanvas.height;
|
|
const tempCtx = tempCanvas.getContext('2d');
|
|
tempCtx.drawImage(this.maskCanvas, 0, 0);
|
|
const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
|
|
const data = imageData.data;
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
const alpha = data[i];
|
|
data[i] = 255;
|
|
data[i + 1] = 255;
|
|
data[i + 2] = 255;
|
|
data[i + 3] = alpha;
|
|
}
|
|
tempCtx.putImageData(imageData, 0, 0);
|
|
const maskImage = new Image();
|
|
maskImage.src = tempCanvas.toDataURL();
|
|
return maskImage;
|
|
}
|
|
|
|
resize(width, height) {
|
|
const oldMask = this.maskCanvas;
|
|
const oldX = this.x;
|
|
const oldY = this.y;
|
|
const oldWidth = oldMask.width;
|
|
const oldHeight = oldMask.height;
|
|
|
|
// Determine if we're increasing or decreasing the canvas size
|
|
const isIncreasingWidth = width > (this.canvasInstance.width);
|
|
const isIncreasingHeight = height > (this.canvasInstance.height);
|
|
|
|
// Create a new mask canvas
|
|
this.maskCanvas = document.createElement('canvas');
|
|
|
|
// Calculate the new size based on whether we're increasing or decreasing
|
|
const extraSpace = 2000;
|
|
|
|
// If we're increasing the size, expand the mask canvas
|
|
// If we're decreasing, keep the current mask canvas size to preserve content
|
|
const newWidth = isIncreasingWidth ? width + extraSpace : Math.max(oldWidth, width + extraSpace);
|
|
const newHeight = isIncreasingHeight ? height + extraSpace : Math.max(oldHeight, height + extraSpace);
|
|
|
|
this.maskCanvas.width = newWidth;
|
|
this.maskCanvas.height = newHeight;
|
|
this.maskCtx = this.maskCanvas.getContext('2d');
|
|
|
|
if (oldMask.width > 0 && oldMask.height > 0) {
|
|
// Calculate offset to maintain the same world position of the mask content
|
|
const offsetX = this.x - oldX;
|
|
const offsetY = this.y - oldY;
|
|
|
|
// Draw the old mask at the correct position to maintain world alignment
|
|
this.maskCtx.drawImage(oldMask, offsetX, offsetY);
|
|
|
|
log.debug(`Preserved mask content with offset (${offsetX}, ${offsetY})`);
|
|
}
|
|
|
|
log.info(`Mask canvas resized to ${this.maskCanvas.width}x${this.maskCanvas.height}, position (${this.x}, ${this.y})`);
|
|
log.info(`Canvas size change: width ${isIncreasingWidth ? 'increased' : 'decreased'}, height ${isIncreasingHeight ? 'increased' : 'decreased'}`);
|
|
}
|
|
|
|
// Add method to update mask position
|
|
updatePosition(dx, dy) {
|
|
this.x += dx;
|
|
this.y += dy;
|
|
log.info(`Mask position updated to (${this.x}, ${this.y})`);
|
|
}
|
|
}
|