Files
Comfyui-LayerForge/js/utils/MaskProcessingUtils.js
Dariusz L 207bacc1f8 Refactor image and mask utility functions
Moved convertToImage, createMaskFromImageSrc, and canvasToMaskImage from MaskProcessingUtils and mask_utils to ImageUtils for better modularity and reuse. Updated imports in dependent modules to use the new locations. Removed duplicate implementations from mask_utils and MaskProcessingUtils.
2025-07-27 18:47:41 +02:00

143 lines
6.0 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;
}
/**
* 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;
}