Files
Azornes-Comfyui-LayerForge/js/utils/MaskPixelUtils.js
T

131 lines
4.9 KiB
JavaScript

/**
* Converts RGB luminance into an opaque white mask with luminance as alpha.
*/
export function applyLuminanceAsAlpha(imageData) {
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const luminance = Math.round(0.299 * data[i] +
0.587 * data[i + 1] +
0.114 * data[i + 2]);
data[i] = 255;
data[i + 1] = 255;
data[i + 2] = 255;
data[i + 3] = luminance;
}
}
/**
* Fills an opaque white mask from the inverse alpha of visibility data.
*/
export function fillInverseAlphaMask(visibilityData, maskData) {
for (let i = 0; i < visibilityData.data.length; i += 4) {
const maskValue = 255 - visibilityData.data[i + 3];
maskData.data[i] = maskValue;
maskData.data[i + 1] = maskValue;
maskData.data[i + 2] = maskValue;
maskData.data[i + 3] = 255;
}
}
/**
* Converts one ImageData channel into a binary mask.
*/
export function imageDataToBinaryMask(imageData, width, height, channel) {
const binaryMask = new Uint8Array(width * height);
for (let i = 0; i < binaryMask.length; i++) {
binaryMask[i] = imageData.data[i * 4 + channel] > 0 ? 1 : 0;
}
return binaryMask;
}
/**
* Rasterizes a distance field into a white RGBA mask with feathered alpha.
* A null binary mask means every pixel is inside the mask.
*/
export function rasterizeDistanceFieldMask(distanceMap, binaryMask, threshold, outputData) {
for (let i = 0; i < distanceMap.length; i++) {
const distance = distanceMap[i];
const isInside = binaryMask === null || binaryMask[i] === 1;
const pixelIndex = i * 4;
outputData[pixelIndex] = 255;
outputData[pixelIndex + 1] = 255;
outputData[pixelIndex + 2] = 255;
if (!isInside) {
outputData[pixelIndex + 3] = 0;
}
else if (distance <= threshold) {
const gradientValue = distance / threshold;
outputData[pixelIndex + 3] = Math.floor(gradientValue * 255);
}
else {
outputData[pixelIndex + 3] = 255;
}
}
}
/**
* Calculates the Euclidean distance transform of a binary mask.
* Uses a two-pass algorithm for efficiency.
* @param binaryMask - Binary mask where 1 = inside, 0 = outside
* @param width - Width of the mask
* @param height - Height of the mask
* @returns Float32Array containing distance values
*/
export function calculateDistanceTransform(binaryMask, width, height) {
const distances = new Float32Array(width * height);
const infinity = width + height; // A value larger than any possible distance
// Initialize distances
for (let i = 0; i < width * height; i++) {
distances[i] = binaryMask[i] === 1 ? infinity : 0;
}
// Forward pass (top-left to bottom-right)
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const idx = y * width + x;
if (distances[idx] > 0) {
let minDist = distances[idx];
// Check top neighbor
if (y > 0) {
minDist = Math.min(minDist, distances[(y - 1) * width + x] + 1);
}
// Check left neighbor
if (x > 0) {
minDist = Math.min(minDist, distances[y * width + (x - 1)] + 1);
}
// Check top-left diagonal
if (x > 0 && y > 0) {
minDist = Math.min(minDist, distances[(y - 1) * width + (x - 1)] + Math.sqrt(2));
}
// Check top-right diagonal
if (x < width - 1 && y > 0) {
minDist = Math.min(minDist, distances[(y - 1) * width + (x + 1)] + Math.sqrt(2));
}
distances[idx] = minDist;
}
}
}
// Backward pass (bottom-right to top-left)
for (let y = height - 1; y >= 0; y--) {
for (let x = width - 1; x >= 0; x--) {
const idx = y * width + x;
if (distances[idx] > 0) {
let minDist = distances[idx];
// Check bottom neighbor
if (y < height - 1) {
minDist = Math.min(minDist, distances[(y + 1) * width + x] + 1);
}
// Check right neighbor
if (x < width - 1) {
minDist = Math.min(minDist, distances[y * width + (x + 1)] + 1);
}
// Check bottom-right diagonal
if (x < width - 1 && y < height - 1) {
minDist = Math.min(minDist, distances[(y + 1) * width + (x + 1)] + Math.sqrt(2));
}
// Check bottom-left diagonal
if (x > 0 && y < height - 1) {
minDist = Math.min(minDist, distances[(y + 1) * width + (x - 1)] + Math.sqrt(2));
}
distances[idx] = minDist;
}
}
}
return distances;
}