@raven-js/cortex
Version:
Zero-dependency machine learning, AI, and data processing library for modern JavaScript
355 lines (326 loc) • 11.9 kB
JavaScript
/**
* @author Anonyfox <max@anonyfox.com>
* @license MIT
* @see https://github.com/Anonyfox/ravenjs
* @see https://ravenjs.dev
* @see https://anonyfox.com
*/
/**
* @file Main convolution functions for kernel-based image filtering.
*/
import {
applyKernelToPixel,
createBoxBlurKernel,
createEdgeDetectionKernel,
createGaussianKernel,
createSharpenKernel,
createUnsharpMaskKernel,
validateConvolutionParameters,
validateKernel,
} from "./utils.js";
/**
* Applies a convolution kernel to RGBA pixel data.
*
* Convolution is a fundamental operation in image processing that applies
* a kernel (small matrix) to each pixel and its neighbors to create effects
* like blur, sharpen, edge detection, and more.
*
* @param {Uint8Array} pixels - Source RGBA pixel data (4 bytes per pixel)
* @param {number} width - Image width in pixels
* @param {number} height - Image height in pixels
* @param {number[][]} kernel - 2D convolution kernel matrix
* @param {Object} [options={}] - Convolution options
* @param {string} [options.edgeHandling="clamp"] - Edge handling ("clamp", "wrap", "mirror")
* @param {boolean} [options.preserveAlpha=true] - Whether to preserve original alpha values
* @param {boolean} [options.inPlace=true] - Whether to modify the original array
* @returns {{pixels: Uint8Array, width: number, height: number}} Convolved image data
* @throws {Error} If parameters are invalid
*
* @example
* // Apply Gaussian blur
* const blurKernel = createGaussianKernel(5, 1.0);
* const result = applyConvolution(pixels, 800, 600, blurKernel);
*
* // Apply sharpening
* const sharpenKernel = createSharpenKernel(1.5);
* const result = applyConvolution(pixels, 800, 600, sharpenKernel);
*
* // Custom edge handling
* const result = applyConvolution(pixels, 800, 600, kernel, {
* edgeHandling: "mirror",
* preserveAlpha: false
* });
*/
export function applyConvolution(pixels, width, height, kernel, options = {}) {
// Validate all parameters
validateConvolutionParameters(pixels, width, height, kernel);
const { edgeHandling = "clamp", preserveAlpha = true, inPlace = true } = options;
const output = inPlace ? pixels : new Uint8Array(pixels);
// Process all pixels
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const [newR, newG, newB, newA] = applyKernelToPixel(
pixels, // Always read from original
x,
y,
width,
height,
kernel,
edgeHandling
);
const index = (y * width + x) * 4;
output[index] = newR; // Red
output[index + 1] = newG; // Green
output[index + 2] = newB; // Blue
output[index + 3] = preserveAlpha ? pixels[index + 3] : newA; // Alpha
}
}
return {
pixels: output,
width,
height,
};
}
/**
* Applies Gaussian blur to RGBA pixel data.
*
* Gaussian blur creates a smooth, natural-looking blur effect using a
* mathematically derived Gaussian distribution kernel.
*
* @param {Uint8Array} pixels - Source RGBA pixel data (4 bytes per pixel)
* @param {number} width - Image width in pixels
* @param {number} height - Image height in pixels
* @param {number} [radius=1.0] - Blur radius [0.5 to 5.0]
* @param {number} [sigma] - Standard deviation (auto-calculated if not provided)
* @param {boolean} [inPlace=true] - Whether to modify the original array
* @returns {{pixels: Uint8Array, width: number, height: number}} Blurred image data
* @throws {Error} If parameters are invalid
*
* @example
* // Light blur
* const result = applyGaussianBlur(pixels, 800, 600, 1.0);
*
* // Heavy blur
* const result = applyGaussianBlur(pixels, 800, 600, 3.0);
*
* // Custom sigma
* const result = applyGaussianBlur(pixels, 800, 600, 2.0, 1.5);
*/
export function applyGaussianBlur(pixels, width, height, radius = 1.0, sigma, inPlace = true) {
if (radius < 0.5 || radius > 5.0) {
throw new Error("Blur radius must be between 0.5 and 5.0");
}
// Auto-calculate sigma if not provided (common practice: sigma = radius / 2)
const actualSigma = sigma ?? radius / 2;
// Calculate kernel size (should be odd and cover ~3 sigma on each side)
const kernelSize = Math.max(3, Math.ceil(radius * 2) * 2 + 1);
const kernel = createGaussianKernel(kernelSize, actualSigma);
return applyConvolution(pixels, width, height, kernel, {
edgeHandling: "clamp",
preserveAlpha: true,
inPlace,
});
}
/**
* Applies box blur to RGBA pixel data.
*
* Box blur creates a simple, uniform blur effect. It's faster than Gaussian
* blur but produces less natural-looking results.
*
* @param {Uint8Array} pixels - Source RGBA pixel data (4 bytes per pixel)
* @param {number} width - Image width in pixels
* @param {number} height - Image height in pixels
* @param {number} [size=3] - Kernel size (must be odd: 3, 5, 7, etc.)
* @param {boolean} [inPlace=true] - Whether to modify the original array
* @returns {{pixels: Uint8Array, width: number, height: number}} Blurred image data
* @throws {Error} If parameters are invalid
*
* @example
* // Light box blur
* const result = applyBoxBlur(pixels, 800, 600, 3);
*
* // Heavy box blur
* const result = applyBoxBlur(pixels, 800, 600, 9);
*/
export function applyBoxBlur(pixels, width, height, size = 3, inPlace = true) {
const kernel = createBoxBlurKernel(size);
return applyConvolution(pixels, width, height, kernel, {
edgeHandling: "clamp",
preserveAlpha: true,
inPlace,
});
}
/**
* Applies sharpening to RGBA pixel data.
*
* Sharpening enhances edges and details in the image by emphasizing
* differences between neighboring pixels.
*
* @param {Uint8Array} pixels - Source RGBA pixel data (4 bytes per pixel)
* @param {number} width - Image width in pixels
* @param {number} height - Image height in pixels
* @param {number} [strength=1.0] - Sharpening strength [0.0 to 2.0]
* @param {boolean} [inPlace=true] - Whether to modify the original array
* @returns {{pixels: Uint8Array, width: number, height: number}} Sharpened image data
* @throws {Error} If parameters are invalid
*
* @example
* // Light sharpening
* const result = applySharpen(pixels, 800, 600, 0.5);
*
* // Strong sharpening
* const result = applySharpen(pixels, 800, 600, 1.5);
*/
export function applySharpen(pixels, width, height, strength = 1.0, inPlace = true) {
const kernel = createSharpenKernel(strength);
return applyConvolution(pixels, width, height, kernel, {
edgeHandling: "clamp",
preserveAlpha: true,
inPlace,
});
}
/**
* Applies unsharp mask sharpening to RGBA pixel data.
*
* Unsharp masking is a more sophisticated sharpening technique that
* creates a "mask" from a blurred version of the image and uses it
* to enhance edge contrast.
*
* @param {Uint8Array} pixels - Source RGBA pixel data (4 bytes per pixel)
* @param {number} width - Image width in pixels
* @param {number} height - Image height in pixels
* @param {number} [amount=1.0] - Sharpening amount [0.0 to 3.0]
* @param {number} [radius=1.0] - Blur radius for mask [0.5 to 3.0]
* @param {boolean} [inPlace=true] - Whether to modify the original array
* @returns {{pixels: Uint8Array, width: number, height: number}} Sharpened image data
* @throws {Error} If parameters are invalid
*
* @example
* // Subtle unsharp masking
* const result = applyUnsharpMask(pixels, 800, 600, 0.8, 1.0);
*
* // Strong unsharp masking
* const result = applyUnsharpMask(pixels, 800, 600, 2.0, 1.5);
*/
export function applyUnsharpMask(pixels, width, height, amount = 1.0, radius = 1.0, inPlace = true) {
const kernel = createUnsharpMaskKernel(amount, radius);
return applyConvolution(pixels, width, height, kernel, {
edgeHandling: "clamp",
preserveAlpha: true,
inPlace,
});
}
/**
* Applies edge detection to RGBA pixel data.
*
* Edge detection highlights boundaries and transitions in the image
* using various mathematical operators.
*
* @param {Uint8Array} pixels - Source RGBA pixel data (4 bytes per pixel)
* @param {number} width - Image width in pixels
* @param {number} height - Image height in pixels
* @param {string} [type="sobel-x"] - Edge detection type
* @param {boolean} [inPlace=true] - Whether to modify the original array
* @returns {{pixels: Uint8Array, width: number, height: number}} Edge-detected image data
* @throws {Error} If parameters are invalid
*
* @example
* // Horizontal edge detection
* const result = applyEdgeDetection(pixels, 800, 600, "sobel-x");
*
* // Laplacian edge detection
* const result = applyEdgeDetection(pixels, 800, 600, "laplacian");
*/
export function applyEdgeDetection(pixels, width, height, type = "sobel-x", inPlace = true) {
const kernel = createEdgeDetectionKernel(type);
return applyConvolution(pixels, width, height, kernel, {
edgeHandling: "clamp",
preserveAlpha: true,
inPlace,
});
}
/**
* Gets information about a convolution operation without performing it.
* Useful for validation and UI feedback.
*
* @param {number} width - Image width in pixels
* @param {number} height - Image height in pixels
* @param {number[][]} kernel - 2D convolution kernel matrix
* @param {string} [operation="convolution"] - Operation name for description
* @returns {{
* operation: string,
* kernelSize: number,
* isLossless: boolean,
* isReversible: boolean,
* outputDimensions: {width: number, height: number},
* outputSize: number,
* description: string,
* isValid: boolean
* }} Convolution operation information
*/
export function getConvolutionInfo(width, height, kernel, operation = "convolution") {
try {
// Basic validation
if (!Number.isInteger(width) || width <= 0) {
throw new Error("Invalid width");
}
if (!Number.isInteger(height) || height <= 0) {
throw new Error("Invalid height");
}
// Validate kernel
validateKernel(kernel);
// Convolution never changes dimensions
const outputDimensions = { width, height };
const outputSize = width * height * 4;
const kernelSize = kernel.length;
// Determine operation characteristics (for future use)
const _isBlur = operation.includes("blur");
const _isSharpen = operation.includes("sharpen");
const _isEdge = operation.includes("edge");
return {
operation,
kernelSize,
isLossless: false, // Convolution typically involves rounding
isReversible: false, // Most convolutions are not reversible
outputDimensions,
outputSize,
description: `${operation} using ${kernelSize}×${kernelSize} kernel`,
isValid: true,
};
} catch (_error) {
return {
operation,
kernelSize: 0,
isLossless: false,
isReversible: false,
outputDimensions: { width: 0, height: 0 },
outputSize: 0,
description: "",
isValid: false,
};
}
}
/**
* Creates a preview of convolution effects on a small sample.
* Useful for real-time UI feedback without processing the entire image.
*
* @param {Uint8Array} samplePixels - Small sample of RGBA pixel data
* @param {number} sampleWidth - Sample width in pixels
* @param {number} sampleHeight - Sample height in pixels
* @param {number[][]} kernel - 2D convolution kernel matrix
* @param {Object} [options={}] - Convolution options
* @returns {{pixels: Uint8Array, width: number, height: number}} Preview result
*/
export function createConvolutionPreview(samplePixels, sampleWidth, sampleHeight, kernel, options = {}) {
// Validate sample size (should be small for performance)
if (samplePixels.length > 64 * 64 * 4) {
throw new Error("Sample too large for preview (max 64x64 pixels)");
}
return applyConvolution(
samplePixels,
sampleWidth,
sampleHeight,
kernel,
{ ...options, inPlace: false } // Always create new array for preview
);
}