@raven-js/cortex
Version:
Zero-dependency machine learning, AI, and data processing library for modern JavaScript
230 lines (207 loc) • 7.29 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 flipping functionality for RGBA pixel data.
*
* This module provides efficient horizontal and vertical flipping operations
* for RGBA pixel data. Flipping is a lossless operation that rearranges
* pixels without any interpolation or quality loss.
*
* @example
* // Flip horizontally (mirror left-right)
* const result = flipPixels(pixels, 800, 600, "horizontal");
*
* // Flip vertically (mirror top-bottom)
* const result = flipPixels(pixels, 800, 600, "vertical");
*/
import { copyPixel, copyRow, getPixelIndex, swapPixels, swapRows, validateFlipParameters } from "./utils.js";
/**
* Flips RGBA pixel data horizontally or vertically.
*
* This function provides efficient flipping operations:
* - Horizontal: mirrors the image left-to-right
* - Vertical: mirrors the image top-to-bottom
*
* The operation is performed in-place when possible for optimal performance,
* or creates a new array when necessary.
*
* @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 {"horizontal"|"vertical"} direction - Flip direction
* @param {boolean} [inPlace=true] - Whether to modify the original array
* @returns {{pixels: Uint8Array, width: number, height: number}} Flipped image data
* @throws {Error} If parameters are invalid
*
* @example
* // Horizontal flip (mirror left-right)
* const result = flipPixels(pixels, 800, 600, "horizontal");
*
* // Vertical flip (mirror top-bottom)
* const result = flipPixels(pixels, 800, 600, "vertical");
*
* // Create new array instead of modifying original
* const result = flipPixels(pixels, 800, 600, "horizontal", false);
*/
export function flipPixels(pixels, width, height, direction, inPlace = true) {
// Validate all parameters
validateFlipParameters(pixels, width, height, direction);
// Choose the appropriate flipping algorithm
if (direction === "horizontal") {
return flipHorizontal(pixels, width, height, inPlace);
} else {
return flipVertical(pixels, width, height, inPlace);
}
}
/**
* Flips image horizontally (mirrors left-to-right).
* Each row is reversed, so the leftmost pixel becomes rightmost.
*
* @param {Uint8Array} pixels - Source RGBA pixel data
* @param {number} width - Image width in pixels
* @param {number} height - Image height in pixels
* @param {boolean} inPlace - Whether to modify the original array
* @returns {{pixels: Uint8Array, width: number, height: number}} Flipped image data
*/
export function flipHorizontal(pixels, width, height, inPlace) {
const output = inPlace ? pixels : new Uint8Array(pixels);
// Process each row
for (let y = 0; y < height; y++) {
// Swap pixels from both ends of the row, working towards center
for (let x = 0; x < Math.floor(width / 2); x++) {
const leftIndex = getPixelIndex(x, y, width);
const rightIndex = getPixelIndex(width - 1 - x, y, width);
if (inPlace) {
swapPixels(output, leftIndex, rightIndex);
} else {
// Copy pixels to swapped positions
copyPixel(pixels, output, leftIndex, rightIndex);
copyPixel(pixels, output, rightIndex, leftIndex);
}
}
}
return {
pixels: output,
width,
height,
};
}
/**
* Flips image vertically (mirrors top-to-bottom).
* Rows are reversed, so the top row becomes the bottom row.
*
* @param {Uint8Array} pixels - Source RGBA pixel data
* @param {number} width - Image width in pixels
* @param {number} height - Image height in pixels
* @param {boolean} inPlace - Whether to modify the original array
* @returns {{pixels: Uint8Array, width: number, height: number}} Flipped image data
*/
export function flipVertical(pixels, width, height, inPlace) {
const output = inPlace ? pixels : new Uint8Array(pixels);
// Process pairs of rows from top/bottom working towards center
for (let y = 0; y < Math.floor(height / 2); y++) {
const topRow = y;
const bottomRow = height - 1 - y;
if (inPlace) {
swapRows(output, topRow, bottomRow, width);
} else {
// Copy rows to swapped positions
copyRow(pixels, output, topRow, bottomRow, width);
copyRow(pixels, output, bottomRow, topRow, width);
}
}
return {
pixels: output,
width,
height,
};
}
/**
* Optimized horizontal flip using row-based processing.
* Processes entire rows at once for better cache performance.
*
* @param {Uint8Array} pixels - Source RGBA pixel data
* @param {number} width - Image width in pixels
* @param {number} height - Image height in pixels
* @param {boolean} inPlace - Whether to modify the original array
* @returns {{pixels: Uint8Array, width: number, height: number}} Flipped image data
*/
export function flipHorizontalOptimized(pixels, width, height, inPlace) {
const output = inPlace ? pixels : new Uint8Array(pixels);
// Process each row with optimized pixel swapping
for (let y = 0; y < height; y++) {
const rowStart = y * width * 4;
const rowEnd = rowStart + width * 4;
// Create temporary buffer for the row
const rowData = output.subarray(rowStart, rowEnd);
// Reverse the row by swapping 4-byte pixels
for (let i = 0; i < width * 2; i += 4) {
const leftPixel = i;
const rightPixel = (width - 1) * 4 - i;
if (leftPixel >= rightPixel) break;
// Swap RGBA values
for (let channel = 0; channel < 4; channel++) {
const temp = rowData[leftPixel + channel];
rowData[leftPixel + channel] = rowData[rightPixel + channel];
rowData[rightPixel + channel] = temp;
}
}
}
return {
pixels: output,
width,
height,
};
}
/**
* Gets information about a flip 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 {"horizontal"|"vertical"} direction - Flip direction
* @returns {{
* direction: string,
* outputDimensions: {width: number, height: number},
* isLossless: boolean,
* outputSize: number,
* isValid: boolean
* }} Flip operation information
*/
export function getFlipInfo(width, height, direction) {
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");
}
if (direction !== "horizontal" && direction !== "vertical") {
throw new Error("Invalid direction");
}
// Flipping never changes dimensions
const outputDimensions = { width, height };
const outputSize = width * height * 4;
return {
direction,
outputDimensions,
isLossless: true, // Flipping is always lossless
outputSize,
isValid: true,
};
} catch (_error) {
return {
direction: "horizontal",
outputDimensions: { width: 0, height: 0 },
isLossless: true,
outputSize: 0,
isValid: false,
};
}
}