UNPKG

@raven-js/cortex

Version:

Zero-dependency machine learning, AI, and data processing library for modern JavaScript

146 lines (125 loc) 4.81 kB
/** * @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 Nearest neighbor interpolation for image resizing. * * Implements the fastest resizing algorithm by selecting the closest source pixel * for each destination pixel. Preserves hard edges and is ideal for pixel art, * but produces blocky results for photographic content. */ import { clampCoord } from "./utils.js"; /** * Resizes RGBA pixel data using nearest neighbor interpolation. * * Maps each destination pixel to the nearest source pixel without any filtering. * This preserves sharp edges and produces no new colors, making it perfect for * pixel art, icons, and cases where exact color preservation is critical. * * @param {Uint8Array} pixels - Source RGBA pixel data * @param {number} srcWidth - Source image width * @param {number} srcHeight - Source image height * @param {number} dstWidth - Target image width * @param {number} dstHeight - Target image height * @returns {Uint8Array} Resized RGBA pixel data * * @example * // Upscale 16x16 pixel art to 64x64 (4x zoom) * const upscaled = resizeNearest(pixels, 16, 16, 64, 64); * * @example * // Downscale screenshot preserving UI elements * const thumbnail = resizeNearest(pixels, 1920, 1080, 320, 180); */ export function resizeNearest(pixels, srcWidth, srcHeight, dstWidth, dstHeight) { // Allocate output buffer const result = new Uint8Array(dstWidth * dstHeight * 4); // Calculate scaling factors const scaleX = srcWidth / dstWidth; const scaleY = srcHeight / dstHeight; // Process each destination pixel let dstOffset = 0; for (let dstY = 0; dstY < dstHeight; dstY++) { // Find nearest source Y coordinate const srcY = clampCoord(Math.floor(dstY * scaleY), srcHeight); const srcRowOffset = srcY * srcWidth * 4; for (let dstX = 0; dstX < dstWidth; dstX++) { // Find nearest source X coordinate const srcX = clampCoord(Math.floor(dstX * scaleX), srcWidth); const srcOffset = srcRowOffset + srcX * 4; // Copy RGBA values directly (no interpolation) result[dstOffset] = pixels[srcOffset]; // Red result[dstOffset + 1] = pixels[srcOffset + 1]; // Green result[dstOffset + 2] = pixels[srcOffset + 2]; // Blue result[dstOffset + 3] = pixels[srcOffset + 3]; // Alpha dstOffset += 4; } } return result; } /** * Optimized nearest neighbor resize for integer scale factors. * * When scaling by exact integer multiples (2x, 3x, 4x, etc.), this function * provides better performance by avoiding floating-point calculations. * * @param {Uint8Array} pixels - Source RGBA pixel data * @param {number} srcWidth - Source image width * @param {number} srcHeight - Source image height * @param {number} scale - Integer scale factor * @returns {Uint8Array} Resized RGBA pixel data */ export function resizeNearestInteger(pixels, srcWidth, srcHeight, scale) { if (!Number.isInteger(scale) || scale <= 0) { throw new Error(`Scale must be positive integer, got ${scale}`); } const dstWidth = srcWidth * scale; const dstHeight = srcHeight * scale; const result = new Uint8Array(dstWidth * dstHeight * 4); // For each source pixel, replicate it scale×scale times for (let srcY = 0; srcY < srcHeight; srcY++) { for (let srcX = 0; srcX < srcWidth; srcX++) { const srcOffset = (srcY * srcWidth + srcX) * 4; // Get source RGBA values const r = pixels[srcOffset]; const g = pixels[srcOffset + 1]; const b = pixels[srcOffset + 2]; const a = pixels[srcOffset + 3]; // Replicate to scale×scale destination block for (let dy = 0; dy < scale; dy++) { for (let dx = 0; dx < scale; dx++) { const dstX = srcX * scale + dx; const dstY = srcY * scale + dy; const dstOffset = (dstY * dstWidth + dstX) * 4; result[dstOffset] = r; result[dstOffset + 1] = g; result[dstOffset + 2] = b; result[dstOffset + 3] = a; } } } } return result; } /** * Checks if resize operation can use integer optimization. * * @param {number} srcWidth - Source width * @param {number} srcHeight - Source height * @param {number} dstWidth - Target width * @param {number} dstHeight - Target height * @returns {number|null} Integer scale factor or null if not applicable */ export function getIntegerScale(srcWidth, srcHeight, dstWidth, dstHeight) { const scaleX = dstWidth / srcWidth; const scaleY = dstHeight / srcHeight; // Check if both scales are the same integer if (scaleX === scaleY && Number.isInteger(scaleX) && scaleX > 0) { return scaleX; } return null; }