mirror of
https://github.com/Azornes/Comfyui-LayerForge.git
synced 2026-03-22 05:02:11 -03:00
Replaces direct usage of document.createElement('canvas') and manual context setup with the createCanvas utility across multiple utility modules. This change improves code consistency, reduces duplication, and centralizes canvas/context creation logic. Also updates notification usage in ClipboardManager to use showNotification and showInfoNotification utilities.
160 lines
6.4 KiB
JavaScript
160 lines
6.4 KiB
JavaScript
import { createModuleLogger } from "./LoggerUtils.js";
|
|
import { createCanvas } from "./CommonUtils.js";
|
|
const log = createModuleLogger('MaskProcessingUtils');
|
|
/**
|
|
* Processes an image to create a mask with inverted alpha channel
|
|
* @param sourceImage - Source image or canvas element
|
|
* @param options - Processing options
|
|
* @returns Promise with processed mask as HTMLCanvasElement
|
|
*/
|
|
export async function processImageToMask(sourceImage, options = {}) {
|
|
const { targetWidth = sourceImage.width, targetHeight = sourceImage.height, invertAlpha = true, maskColor = { r: 255, g: 255, b: 255 } } = options;
|
|
log.debug('Processing image to mask:', {
|
|
sourceSize: { width: sourceImage.width, height: sourceImage.height },
|
|
targetSize: { width: targetWidth, height: targetHeight },
|
|
invertAlpha,
|
|
maskColor
|
|
});
|
|
// Create temporary canvas for processing
|
|
const { canvas: tempCanvas, ctx: tempCtx } = createCanvas(targetWidth, targetHeight, '2d', { willReadFrequently: true });
|
|
if (!tempCtx) {
|
|
throw new Error("Failed to get 2D context for mask processing");
|
|
}
|
|
// Draw the source image
|
|
tempCtx.drawImage(sourceImage, 0, 0, targetWidth, targetHeight);
|
|
// Get image data for processing
|
|
const imageData = tempCtx.getImageData(0, 0, targetWidth, targetHeight);
|
|
const data = imageData.data;
|
|
// Process pixels to create mask
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
const originalAlpha = data[i + 3];
|
|
// Set RGB to mask color
|
|
data[i] = maskColor.r; // Red
|
|
data[i + 1] = maskColor.g; // Green
|
|
data[i + 2] = maskColor.b; // Blue
|
|
// Handle alpha channel
|
|
if (invertAlpha) {
|
|
data[i + 3] = 255 - originalAlpha; // Invert alpha
|
|
}
|
|
else {
|
|
data[i + 3] = originalAlpha; // Keep original alpha
|
|
}
|
|
}
|
|
// Put processed data back to canvas
|
|
tempCtx.putImageData(imageData, 0, 0);
|
|
log.debug('Mask processing completed');
|
|
return tempCanvas;
|
|
}
|
|
/**
|
|
* Processes image data with custom pixel transformation
|
|
* @param sourceImage - Source image or canvas element
|
|
* @param pixelTransform - Custom pixel transformation function
|
|
* @param options - Processing options
|
|
* @returns Promise with processed image as HTMLCanvasElement
|
|
*/
|
|
export async function processImageWithTransform(sourceImage, pixelTransform, options = {}) {
|
|
const { targetWidth = sourceImage.width, targetHeight = sourceImage.height } = options;
|
|
const { canvas: tempCanvas, ctx: tempCtx } = createCanvas(targetWidth, targetHeight, '2d', { willReadFrequently: true });
|
|
if (!tempCtx) {
|
|
throw new Error("Failed to get 2D context for image processing");
|
|
}
|
|
tempCtx.drawImage(sourceImage, 0, 0, targetWidth, targetHeight);
|
|
const imageData = tempCtx.getImageData(0, 0, targetWidth, targetHeight);
|
|
const data = imageData.data;
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
const [r, g, b, a] = pixelTransform(data[i], data[i + 1], data[i + 2], data[i + 3], i / 4);
|
|
data[i] = r;
|
|
data[i + 1] = g;
|
|
data[i + 2] = b;
|
|
data[i + 3] = a;
|
|
}
|
|
tempCtx.putImageData(imageData, 0, 0);
|
|
return tempCanvas;
|
|
}
|
|
/**
|
|
* Converts a canvas or image to an Image element
|
|
* @param source - Source canvas or image
|
|
* @returns Promise with Image element
|
|
*/
|
|
export async function convertToImage(source) {
|
|
if (source instanceof HTMLImageElement) {
|
|
return source; // Already an image
|
|
}
|
|
const image = new Image();
|
|
image.src = source.toDataURL();
|
|
await new Promise((resolve, reject) => {
|
|
image.onload = () => resolve();
|
|
image.onerror = reject;
|
|
});
|
|
return image;
|
|
}
|
|
/**
|
|
* Crops an image to a specific region
|
|
* @param sourceImage - Source image or canvas
|
|
* @param cropArea - Crop area {x, y, width, height}
|
|
* @returns Promise with cropped image as HTMLCanvasElement
|
|
*/
|
|
export async function cropImage(sourceImage, cropArea) {
|
|
const { x, y, width, height } = cropArea;
|
|
log.debug('Cropping image:', {
|
|
sourceSize: { width: sourceImage.width, height: sourceImage.height },
|
|
cropArea
|
|
});
|
|
const { canvas, ctx } = createCanvas(width, height);
|
|
if (!ctx) {
|
|
throw new Error("Failed to get 2D context for image cropping");
|
|
}
|
|
ctx.drawImage(sourceImage, x, y, width, height, // Source rectangle
|
|
0, 0, width, height // Destination rectangle
|
|
);
|
|
return canvas;
|
|
}
|
|
/**
|
|
* Applies a mask to an image using viewport positioning
|
|
* @param maskImage - Mask image or canvas
|
|
* @param targetWidth - Target viewport width
|
|
* @param targetHeight - Target viewport height
|
|
* @param viewportOffset - Viewport offset {x, y}
|
|
* @param maskColor - Mask color (default: white)
|
|
* @returns Promise with processed mask for viewport
|
|
*/
|
|
export async function processMaskForViewport(maskImage, targetWidth, targetHeight, viewportOffset, maskColor = { r: 255, g: 255, b: 255 }) {
|
|
log.debug("Processing mask for viewport:", {
|
|
sourceSize: { width: maskImage.width, height: maskImage.height },
|
|
targetSize: { width: targetWidth, height: targetHeight },
|
|
viewportOffset
|
|
});
|
|
const { canvas: tempCanvas, ctx: tempCtx } = createCanvas(targetWidth, targetHeight, '2d', { willReadFrequently: true });
|
|
if (!tempCtx) {
|
|
throw new Error("Failed to get 2D context for viewport mask processing");
|
|
}
|
|
// Calculate source coordinates based on viewport offset
|
|
const sourceX = -viewportOffset.x;
|
|
const sourceY = -viewportOffset.y;
|
|
// Draw the mask with viewport cropping
|
|
tempCtx.drawImage(maskImage, // Source: full mask from "output area"
|
|
sourceX, // sx: Real X coordinate on large mask
|
|
sourceY, // sy: Real Y coordinate on large mask
|
|
targetWidth, // sWidth: Width of cropped fragment
|
|
targetHeight, // sHeight: Height of cropped fragment
|
|
0, // dx: Where to paste in target canvas (always 0)
|
|
0, // dy: Where to paste in target canvas (always 0)
|
|
targetWidth, // dWidth: Width of pasted image
|
|
targetHeight // dHeight: Height of pasted image
|
|
);
|
|
// Apply mask color
|
|
const imageData = tempCtx.getImageData(0, 0, targetWidth, targetHeight);
|
|
const data = imageData.data;
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
const alpha = data[i + 3];
|
|
if (alpha > 0) {
|
|
data[i] = maskColor.r;
|
|
data[i + 1] = maskColor.g;
|
|
data[i + 2] = maskColor.b;
|
|
}
|
|
}
|
|
tempCtx.putImageData(imageData, 0, 0);
|
|
log.debug("Viewport mask processing completed");
|
|
return tempCanvas;
|
|
}
|