@raven-js/cortex
Version:
Zero-dependency machine learning, AI, and data processing library for modern JavaScript
200 lines (180 loc) • 6.56 kB
JavaScript
// @ts-nocheck
/**
* @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 IHDR chunk creation for PNG encoding.
*
* The IHDR (Image Header) chunk contains critical image information and must
* be the first chunk in every PNG file. This module provides functions to
* create properly formatted IHDR chunks from image parameters.
*
* IHDR chunk format (13 bytes):
* - Width: 4 bytes (big-endian)
* - Height: 4 bytes (big-endian)
* - Bit depth: 1 byte
* - Color type: 1 byte
* - Compression method: 1 byte (always 0 for DEFLATE)
* - Filter method: 1 byte (always 0 for adaptive filtering)
* - Interlace method: 1 byte (0=none, 1=Adam7)
*
* @example
* // Create IHDR chunk for 800x600 RGBA image
* const ihdrData = createIHDRChunk(800, 600, 8, 6, 0, 0, 0);
* console.log(`IHDR chunk: ${ihdrData.length} bytes`);
*/
/**
* Creates IHDR chunk data for PNG encoding.
*
* @param {number} width - Image width in pixels (1-2^31-1)
* @param {number} height - Image height in pixels (1-2^31-1)
* @param {number} bitDepth - Bits per sample (1, 2, 4, 8, or 16)
* @param {number} colorType - PNG color type (0, 2, 3, 4, or 6)
* @param {number} compressionMethod - Compression method (always 0 for DEFLATE)
* @param {number} filterMethod - Filter method (always 0 for adaptive)
* @param {number} interlaceMethod - Interlace method (0=none, 1=Adam7)
* @returns {Uint8Array} IHDR chunk data (13 bytes)
*/
export function createIHDRChunk(width, height, bitDepth, colorType, compressionMethod, filterMethod, interlaceMethod) {
// Parameter validation
if (!Number.isInteger(width) || width < 1 || width > 0x7fffffff) {
throw new Error(`Invalid width: ${width} (must be integer 1-2147483647)`);
}
if (!Number.isInteger(height) || height < 1 || height > 0x7fffffff) {
throw new Error(`Invalid height: ${height} (must be integer 1-2147483647)`);
}
if (![1, 2, 4, 8, 16].includes(bitDepth)) {
throw new Error(`Invalid bit depth: ${bitDepth} (must be 1, 2, 4, 8, or 16)`);
}
if (![0, 2, 3, 4, 6].includes(colorType)) {
throw new Error(`Invalid color type: ${colorType} (must be 0, 2, 3, 4, or 6)`);
}
if (compressionMethod !== 0) {
throw new Error(`Invalid compression method: ${compressionMethod} (must be 0 for DEFLATE)`);
}
if (filterMethod !== 0) {
throw new Error(`Invalid filter method: ${filterMethod} (must be 0 for adaptive)`);
}
if (![0, 1].includes(interlaceMethod)) {
throw new Error(`Invalid interlace method: ${interlaceMethod} (must be 0 or 1)`);
}
// Validate bit depth and color type combinations
validateBitDepthColorType(bitDepth, colorType);
// Create 13-byte IHDR chunk data
const ihdrData = new Uint8Array(13);
let offset = 0;
// Width (4 bytes, big-endian)
ihdrData[offset++] = (width >>> 24) & 0xff;
ihdrData[offset++] = (width >>> 16) & 0xff;
ihdrData[offset++] = (width >>> 8) & 0xff;
ihdrData[offset++] = width & 0xff;
// Height (4 bytes, big-endian)
ihdrData[offset++] = (height >>> 24) & 0xff;
ihdrData[offset++] = (height >>> 16) & 0xff;
ihdrData[offset++] = (height >>> 8) & 0xff;
ihdrData[offset++] = height & 0xff;
// Bit depth (1 byte)
ihdrData[offset++] = bitDepth;
// Color type (1 byte)
ihdrData[offset++] = colorType;
// Compression method (1 byte)
ihdrData[offset++] = compressionMethod;
// Filter method (1 byte)
ihdrData[offset++] = filterMethod;
// Interlace method (1 byte)
ihdrData[offset++] = interlaceMethod;
return ihdrData;
}
/**
* Validates bit depth and color type combination according to PNG specification.
*
* @param {number} bitDepth - Bits per sample
* @param {number} colorType - PNG color type
* @throws {Error} If combination is invalid
*/
function validateBitDepthColorType(bitDepth, colorType) {
const validCombinations = {
0: [1, 2, 4, 8, 16], // Grayscale
2: [8, 16], // RGB
3: [1, 2, 4, 8], // Palette
4: [8, 16], // Grayscale + Alpha
6: [8, 16], // RGB + Alpha
};
const validBitDepths = validCombinations[colorType];
if (!validBitDepths || !validBitDepths.includes(bitDepth)) {
throw new Error(
`Invalid bit depth ${bitDepth} for color type ${colorType}. ` +
`Valid bit depths: ${validBitDepths ? validBitDepths.join(", ") : "none"}`
);
}
}
/**
* Creates IHDR chunk data from image properties.
*
* @param {{width: number, height: number, bitDepth: number, colorType: number, interlaceMethod?: number}} imageInfo - Image properties
* @returns {Uint8Array} IHDR chunk data (13 bytes)
*/
export function createIHDRFromImageInfo(imageInfo) {
const { width, height, bitDepth, colorType, interlaceMethod = 0 } = imageInfo;
return createIHDRChunk(
width,
height,
bitDepth,
colorType,
0, // compression method (always 0)
0, // filter method (always 0)
interlaceMethod
);
}
/**
* Gets the number of samples per pixel for a given color type.
*
* @param {number} colorType - PNG color type
* @returns {number} Samples per pixel
*/
export function getSamplesPerPixel(colorType) {
switch (colorType) {
case 0:
return 1; // Grayscale
case 2:
return 3; // RGB
case 3:
return 1; // Palette (index)
case 4:
return 2; // Grayscale + Alpha
case 6:
return 4; // RGB + Alpha
default:
throw new Error(`Invalid color type: ${colorType}`);
}
}
/**
* Gets the number of bytes per pixel for given bit depth and color type.
*
* @param {number} bitDepth - Bits per sample
* @param {number} colorType - PNG color type
* @returns {number} Bytes per pixel (may be fractional for bit depths < 8)
*/
export function getBytesPerPixel(bitDepth, colorType) {
const samplesPerPixel = getSamplesPerPixel(colorType);
const bitsPerPixel = bitDepth * samplesPerPixel;
return Math.ceil(bitsPerPixel / 8);
}
/**
* Gets the scanline width in bytes for given image parameters.
*
* @param {number} width - Image width in pixels
* @param {number} bitDepth - Bits per sample
* @param {number} colorType - PNG color type
* @returns {number} Scanline width in bytes (including filter byte)
*/
export function getScanlineWidth(width, bitDepth, colorType) {
const samplesPerPixel = getSamplesPerPixel(colorType);
const bitsPerScanline = width * bitDepth * samplesPerPixel;
const bytesPerScanline = Math.ceil(bitsPerScanline / 8);
return bytesPerScanline + 1; // +1 for filter byte
}