@raven-js/cortex
Version:
Zero-dependency machine learning, AI, and data processing library for modern JavaScript
201 lines (177 loc) • 7.34 kB
JavaScript
/**
* @author Anonyfox <max@anonyfox.com>
* @license MIT
* @see {@link https://github.com/Anonyfox/ravenjs}
* @see {@link https://ravenjs.dev}
* @see {@link https://anonyfox.com}
*/
/**
* @file Main cropping functionality for RGBA pixel data.
*
* This module provides the core cropPixels function that extracts a rectangular
* region from RGBA pixel data. The implementation is optimized for performance
* with efficient memory access patterns and minimal allocations.
*
* @example
* // Crop a 200x150 region starting at (100, 50)
* const croppedPixels = cropPixels(pixels, 800, 600, 100, 50, 200, 150);
* console.log(`Cropped: ${croppedPixels.length / 4} pixels`);
*/
import { calculateCropBounds, copyPixel, getPixelIndex, isIdentityCrop, validateCropParameters } from "./utils.js";
/**
* Crops RGBA pixel data to extract a rectangular region.
*
* This function efficiently extracts a rectangular region from source RGBA
* pixel data, handling edge cases like out-of-bounds coordinates and
* optimizing for common scenarios like identity crops.
*
* @param {Uint8Array} pixels - Source RGBA pixel data (4 bytes per pixel)
* @param {number} srcWidth - Source image width in pixels
* @param {number} srcHeight - Source image height in pixels
* @param {number} x - Crop region X coordinate (top-left, can be negative)
* @param {number} y - Crop region Y coordinate (top-left, can be negative)
* @param {number} width - Crop region width in pixels (must be positive)
* @param {number} height - Crop region height in pixels (must be positive)
* @returns {Uint8Array} Cropped RGBA pixel data
* @throws {Error} If parameters are invalid
*
* @example
* // Basic crop operation
* const pixels = new Uint8Array(800 * 600 * 4); // 800x600 RGBA
* const cropped = cropPixels(pixels, 800, 600, 100, 50, 200, 150);
* console.log(`Result: ${cropped.length / 4} pixels (${200}x${150})`);
*
* @example
* // Handle out-of-bounds crop (automatically clamped)
* const cropped = cropPixels(pixels, 800, 600, 750, 550, 100, 100);
* // Result will be smaller than 100x100 due to clamping
*/
export function cropPixels(pixels, srcWidth, srcHeight, x, y, width, height) {
// Validate all parameters
validateCropParameters(pixels, srcWidth, srcHeight, x, y, width, height);
// Check for identity crop (no-op optimization)
if (isIdentityCrop(srcWidth, srcHeight, x, y, width, height)) {
return new Uint8Array(pixels); // Return copy of original
}
// Calculate effective crop bounds (handles out-of-bounds cases)
const bounds = calculateCropBounds(srcWidth, srcHeight, x, y, width, height);
// Handle case where crop region is entirely outside image
if (!bounds) {
throw new Error(
`Crop region (${x}, ${y}, ${width}x${height}) is entirely outside ` + `image bounds (${srcWidth}x${srcHeight})`
);
}
// Extract effective bounds
const { x: cropX, y: cropY, width: cropWidth, height: cropHeight } = bounds;
// Allocate output buffer
const outputSize = cropWidth * cropHeight * 4; // RGBA = 4 bytes per pixel
const output = new Uint8Array(outputSize);
// Perform the crop operation
// Use row-by-row copying for optimal memory access patterns
for (let row = 0; row < cropHeight; row++) {
const srcY = cropY + row;
const srcRowStart = getPixelIndex(cropX, srcY, srcWidth);
const dstRowStart = getPixelIndex(0, row, cropWidth);
// Copy entire row at once for maximum efficiency
const rowBytes = cropWidth * 4;
output.set(pixels.subarray(srcRowStart, srcRowStart + rowBytes), dstRowStart);
}
return output;
}
/**
* Crops RGBA pixel data with pixel-by-pixel copying (alternative implementation).
* This version is more explicit but potentially slower than the row-based approach.
*
* @param {Uint8Array} pixels - Source RGBA pixel data
* @param {number} srcWidth - Source image width in pixels
* @param {number} srcHeight - Source image height in pixels
* @param {number} x - Crop region X coordinate (top-left)
* @param {number} y - Crop region Y coordinate (top-left)
* @param {number} width - Crop region width in pixels
* @param {number} height - Crop region height in pixels
* @returns {Uint8Array} Cropped RGBA pixel data
*/
export function cropPixelsExplicit(pixels, srcWidth, srcHeight, x, y, width, height) {
// Validate all parameters
validateCropParameters(pixels, srcWidth, srcHeight, x, y, width, height);
// Check for identity crop
if (isIdentityCrop(srcWidth, srcHeight, x, y, width, height)) {
return new Uint8Array(pixels);
}
// Calculate effective crop bounds
const bounds = calculateCropBounds(srcWidth, srcHeight, x, y, width, height);
if (!bounds) {
throw new Error(
`Crop region (${x}, ${y}, ${width}x${height}) is entirely outside ` + `image bounds (${srcWidth}x${srcHeight})`
);
}
const { x: cropX, y: cropY, width: cropWidth, height: cropHeight } = bounds;
// Allocate output buffer
const outputSize = cropWidth * cropHeight * 4;
const output = new Uint8Array(outputSize);
// Copy pixels one by one
for (let row = 0; row < cropHeight; row++) {
for (let col = 0; col < cropWidth; col++) {
const srcIndex = getPixelIndex(cropX + col, cropY + row, srcWidth);
const dstIndex = getPixelIndex(col, row, cropWidth);
copyPixel(pixels, output, srcIndex, dstIndex);
}
}
return output;
}
/**
* Gets information about a crop operation without performing it.
* Useful for validation and UI feedback.
*
* @param {number} srcWidth - Source image width in pixels
* @param {number} srcHeight - Source image height in pixels
* @param {number} x - Crop region X coordinate (top-left)
* @param {number} y - Crop region Y coordinate (top-left)
* @param {number} width - Crop region width in pixels
* @param {number} height - Crop region height in pixels
* @returns {{
* isValid: boolean,
* isIdentity: boolean,
* effectiveBounds: {x: number, y: number, width: number, height: number} | null,
* outputSize: number
* }} Crop operation information
*/
export function getCropInfo(srcWidth, srcHeight, x, y, width, height) {
try {
// Basic validation (without pixel data)
if (!Number.isInteger(srcWidth) || srcWidth <= 0) {
throw new Error("Invalid source width");
}
if (!Number.isInteger(srcHeight) || srcHeight <= 0) {
throw new Error("Invalid source height");
}
if (!Number.isInteger(x)) {
throw new Error("Invalid crop X coordinate");
}
if (!Number.isInteger(y)) {
throw new Error("Invalid crop Y coordinate");
}
if (!Number.isInteger(width) || width <= 0) {
throw new Error("Invalid crop width");
}
if (!Number.isInteger(height) || height <= 0) {
throw new Error("Invalid crop height");
}
const isIdentity = isIdentityCrop(srcWidth, srcHeight, x, y, width, height);
const effectiveBounds = calculateCropBounds(srcWidth, srcHeight, x, y, width, height);
const outputSize = effectiveBounds ? effectiveBounds.width * effectiveBounds.height * 4 : 0;
return {
isValid: effectiveBounds !== null,
isIdentity,
effectiveBounds,
outputSize,
};
} catch (_error) {
return {
isValid: false,
isIdentity: false,
effectiveBounds: null,
outputSize: 0,
};
}
}