@raven-js/cortex
Version:
Zero-dependency machine learning, AI, and data processing library for modern JavaScript
201 lines (188 loc) • 5.79 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 Marker writers for JPEG encoder.
*/
/**
* Write big-endian 16-bit into an array.
* @param {number[]} arr
* @param {number} v
*/
function be16(arr, v) {
arr.push((v >>> 8) & 0xff, v & 0xff);
}
/**
* Write SOI marker.
* @param {number[]} out
*/
export function writeSOI(out) {
out.push(0xff, 0xd8);
}
/**
* Write EOI marker.
* @param {number[]} out
*/
export function writeEOI(out) {
out.push(0xff, 0xd9);
}
/**
* Write JFIF APP0 segment with given density.
* @param {number[]} out
* @param {{ units: 0|1|2, xDensity: number, yDensity: number }} jfif
*/
export function writeAPP0_JFIF(out, jfif) {
const { units = 1, xDensity = 72, yDensity = 72 } = jfif || {};
out.push(0xff, 0xe0); // APP0
const payload = [];
// Identifier "JFIF\0"
payload.push(0x4a, 0x46, 0x49, 0x46, 0x00);
// Version 1.01
payload.push(0x01, 0x01);
payload.push(units & 0xff);
be16(payload, Math.max(1, Math.min(0xffff, xDensity | 0)));
be16(payload, Math.max(1, Math.min(0xffff, yDensity | 0)));
// Thumbnail 0x0
payload.push(0x00, 0x00);
// Length includes the two length bytes
be16(out, payload.length + 2);
out.push(...payload);
}
/**
* Write APP1 EXIF segment if provided. Accepts raw EXIF buffer that may include leading "Exif\0\0".
* @param {number[]} out
* @param {Uint8Array} exif
*/
export function writeAPP1_EXIF(out, exif) {
if (!(exif instanceof Uint8Array) || exif.length === 0) return;
out.push(0xff, 0xe1);
const payload = [];
// Ensure header
const needHeader = !(
exif[0] === 0x45 &&
exif[1] === 0x78 &&
exif[2] === 0x69 &&
exif[3] === 0x66 &&
exif[4] === 0x00 &&
exif[5] === 0x00
);
if (needHeader) payload.push(0x45, 0x78, 0x69, 0x66, 0x00, 0x00);
for (let i = 0; i < exif.length; i++) payload.push(exif[i]);
be16(out, payload.length + 2);
out.push(...payload);
}
/**
* Write APP2 ICC_PROFILE segments, splitting as needed.
* @param {number[]} out
* @param {Uint8Array} icc
*/
export function writeAPP2_ICC(out, icc) {
if (!(icc instanceof Uint8Array) || icc.length === 0) return;
const chunkMax = 65519; // per spec
const count = Math.ceil(icc.length / chunkMax);
for (let i = 0; i < count; i++) {
const start = i * chunkMax;
const end = Math.min(icc.length, start + chunkMax);
const payload = [];
// Identifier "ICC_PROFILE\0"
payload.push(0x49, 0x43, 0x43, 0x5f, 0x50, 0x52, 0x4f, 0x46, 0x49, 0x4c, 0x45, 0x00);
payload.push((i + 1) & 0xff, count & 0xff);
for (let p = start; p < end; p++) payload.push(icc[p]);
out.push(0xff, 0xe2);
be16(out, payload.length + 2);
out.push(...payload);
}
}
/**
* Write DQT for up to 4 tables (8-bit precision) with ids 0..3, natural order 64 entries each.
* @param {number[]} out
* @param {{id:number, table:Int32Array}[]} tables
*/
export function writeDQT(out, tables) {
const payload = [];
for (const { id, table } of tables) {
payload.push((0 << 4) | (id & 0x0f)); // Pq=0, Tq=id
// emit in zig-zag order: we assume caller passes natural order; provide zigzag mapping here
const zigzag = [
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21,
28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61,
54, 47, 55, 62, 63,
];
for (let i = 0; i < 64; i++) {
const v = table[zigzag[i]] | 0;
payload.push(Math.max(1, Math.min(255, v)) & 0xff);
}
}
out.push(0xff, 0xdb);
be16(out, payload.length + 2);
out.push(...payload);
}
/**
* Write DHT segment for provided DC/AC tables in JPEG format.
* @param {number[]} out
* @param {{class:0|1,id:number, codeLengthCounts:Uint8Array, symbols:Uint8Array}[]} tables
*/
export function writeDHT(out, tables) {
for (const t of tables) {
const payload = [];
payload.push(((t.class & 1) << 4) | (t.id & 0x0f));
for (let i = 0; i < 16; i++) payload.push(t.codeLengthCounts[i] | 0);
for (let i = 0; i < t.symbols.length; i++) payload.push(t.symbols[i] | 0);
out.push(0xff, 0xc4);
be16(out, payload.length + 2);
out.push(...payload);
}
}
/**
* Write SOF0 (baseline) frame header.
* @param {number[]} out
* @param {{width:number,height:number, components:{id:number,h:number,v:number,Tq:number}[]}} frame
*/
export function writeSOF0(out, frame) {
const payload = [];
payload.push(8); // precision
be16(payload, frame.height);
be16(payload, frame.width);
payload.push(frame.components.length & 0xff);
for (const c of frame.components) {
payload.push(c.id & 0xff);
payload.push(((c.h & 0x0f) << 4) | (c.v & 0x0f));
payload.push(c.Tq & 0x0f);
}
out.push(0xff, 0xc0);
be16(out, payload.length + 2);
out.push(...payload);
}
/**
* Write DRI segment (restart interval) if Ri>0.
* @param {number[]} out
* @param {number} Ri MCUs per restart interval
*/
export function writeDRI(out, Ri) {
if (!Ri) return;
out.push(0xff, 0xdd);
be16(out, 4);
be16(out, Ri & 0xffff);
}
/**
* Write SOS segment for a baseline single-scan.
* @param {number[]} out
* @param {{ components:{id:number,Td:number,Ta:number}[] }} scan
*/
export function writeSOS(out, scan) {
const payload = [];
payload.push(scan.components.length & 0xff);
for (const c of scan.components) {
payload.push(c.id & 0xff);
payload.push(((c.Td & 0x0f) << 4) | (c.Ta & 0x0f));
}
// Baseline params Ss=0, Se=63, AhAl=0
payload.push(0x00, 0x3f, 0x00);
out.push(0xff, 0xda);
be16(out, payload.length + 2);
out.push(...payload);
}