'use strict';
const STATUS_OK = 0;
const STATUS_INVALID = -1;
const STATUS_REJECTED = -2;
const LAYOUT_GRAY8 = 0;
const LAYOUT_RGB8 = 2;
const LAYOUT_RGBA8 = 4;
const BLOCK = 64;
const CDYLIB_NAMES = ['pith_jpeg.dll', 'libpith_jpeg.so', 'libpith_jpeg.dylib'];
const path = require('node:path');
class FfiError extends Error {
constructor(op, status) {
const detail =
status === STATUS_INVALID
? 'invalid argument'
: status === STATUS_REJECTED
? 'input rejected'
: 'unknown failure';
super(`${op} failed: ${detail} (status ${status})`);
this.name = 'FfiError';
this.status = status;
}
}
class LibraryNotFoundError extends Error {
constructor() {
super(
'no pith-jpeg cdylib found (searched PITH_CDYLIB, PITH_CDYLIB_DIR, ' +
'the module directory and <repo>/target/release); ' +
'run `cargo build --release` first',
);
this.name = 'LibraryNotFoundError';
}
}
function findCdylib() {
const fs = require('node:fs');
const explicit = process.env.PITH_CDYLIB;
if (explicit && fs.existsSync(explicit) && fs.statSync(explicit).isFile()) {
return path.resolve(explicit);
}
const repoRoot = path.resolve(__dirname, '..', '..');
const dirs = [];
const envDir = process.env.PITH_CDYLIB_DIR;
if (envDir) {
dirs.push(envDir);
if (!path.isAbsolute(envDir)) {
dirs.push(path.resolve(envDir));
dirs.push(path.join(repoRoot, envDir));
}
}
dirs.push(__dirname); dirs.push(path.join(repoRoot, 'target', 'release'));
for (const dir of dirs) {
for (const name of CDYLIB_NAMES) {
const p = path.join(dir, name);
if (fs.existsSync(p) && fs.statSync(p).isFile()) return p;
}
}
throw new LibraryNotFoundError();
}
let cached = undefined;
function loadLibrary() {
if (cached !== undefined) return cached;
const koffi = require('koffi');
const lib = koffi.load(findCdylib());
cached = {
_decode: lib.func('pith_jpeg_decode', 'int32_t', [
'uint8_t *',
'uint64',
'uint32',
koffi.out(koffi.pointer('uint64')),
]),
_channels: lib.func('pith_jpeg_channels', 'int32_t', [
'uint8_t *',
'uint64',
koffi.out(koffi.pointer('uint32')),
]),
_dequant_zigzag: lib.func('pith_jpeg_dequant_zigzag', 'int32_t', [
'uint8_t *',
'uint64',
koffi.out(koffi.pointer('uint8_t')),
'uint64',
]),
_dequant: lib.func('pith_jpeg_dequant', 'int32_t', [
'uint8_t *',
'uint64',
koffi.out(koffi.pointer('uint8_t')),
'uint64',
]),
_idct_islow: lib.func('pith_jpeg_idct_islow', 'int32_t', [
'uint8_t *',
'uint64',
koffi.out(koffi.pointer('uint8_t')),
'uint64',
]),
_idct_oracle: lib.func('pith_jpeg_idct_oracle', 'int32_t', [
'uint8_t *',
'uint64',
koffi.out(koffi.pointer('uint8_t')),
'uint64',
]),
};
return cached;
}
function lanesToBuffer(values) {
const buf = Buffer.alloc(values.length * 8);
for (let i = 0; i < values.length; i++) {
buf.writeDoubleLE(values[i], i * 8);
}
return buf;
}
function bufferToLanes(buf) {
const out = [];
for (let i = 0; i + 8 <= buf.length; i += 8) {
out.push(buf.readDoubleLE(i));
}
return out;
}
function parseLane(hex) {
const bits = BigInt('0x' + hex);
const be = Buffer.from(hex, 'hex');
return { bits, value: be.readDoubleBE(0) };
}
function decodeDigest(data, layout) {
const lib = loadLibrary();
const out = [0n];
const status = lib._decode(data, data.length, layout, out);
if (status !== STATUS_OK) throw new FfiError('pith_jpeg_decode', status);
return out[0];
}
function channels(data) {
const lib = loadLibrary();
const out = [0];
const status = lib._channels(data, data.length, out);
if (status !== STATUS_OK) throw new FfiError('pith_jpeg_channels', status);
return out[0];
}
function replay(op, values, outLanes) {
const lib = loadLibrary();
const input = lanesToBuffer(values);
const out = Buffer.alloc(outLanes * 8);
const status = op(input, input.length, out, out.length);
if (status !== STATUS_OK) throw new FfiError(op.name || 'kernel', status);
return bufferToLanes(out);
}
function dequantZigzag(payload) {
return replay(loadLibrary()._dequant_zigzag, payload, BLOCK);
}
function dequant(coefs, qt) {
return replay(loadLibrary()._dequant, coefs.concat(qt), BLOCK);
}
function idctIslow(coefs, qt) {
return replay(loadLibrary()._idct_islow, coefs.concat(qt), BLOCK);
}
function idctOracle(block) {
return replay(loadLibrary()._idct_oracle, block, BLOCK);
}
module.exports = {
STATUS_OK,
STATUS_INVALID,
STATUS_REJECTED,
LAYOUT_GRAY8,
LAYOUT_RGB8,
LAYOUT_RGBA8,
BLOCK,
FfiError,
LibraryNotFoundError,
findCdylib,
parseLane,
decodeDigest,
channels,
dequantZigzag,
dequant,
idctIslow,
idctOracle,
};