mirror of
https://github.com/Azornes/Comfyui-LayerForge.git
synced 2026-03-21 20:52:12 -03:00
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})`);
|
|
}
|
|
}
|