112 lines
3.3 KiB
Plaintext
112 lines
3.3 KiB
Plaintext
import { preprocessors, codecs as supportedFormats } from "./codecs.js";
|
|
import ImageData from "./image_data.js";
|
|
const DELAY_MS = 1e3;
|
|
let _promise;
|
|
function delayOnce(ms) {
|
|
if (!_promise) {
|
|
_promise = new Promise((resolve) => {
|
|
setTimeout(resolve, ms);
|
|
});
|
|
}
|
|
return _promise;
|
|
}
|
|
function maybeDelay() {
|
|
const isAppleM1 = process.arch === "arm64" && process.platform === "darwin";
|
|
if (isAppleM1) {
|
|
return delayOnce(DELAY_MS);
|
|
}
|
|
return Promise.resolve();
|
|
}
|
|
async function decodeBuffer(_buffer) {
|
|
const buffer = Buffer.from(_buffer);
|
|
const firstChunk = buffer.slice(0, 16);
|
|
const firstChunkString = Array.from(firstChunk).map((v) => String.fromCodePoint(v)).join("");
|
|
if (firstChunkString.includes("GIF")) {
|
|
throw Error(`GIF images are not supported, please use the Sharp image service`);
|
|
}
|
|
const key = Object.entries(supportedFormats).find(
|
|
([, { detectors }]) => detectors.some((detector) => detector.exec(firstChunkString))
|
|
)?.[0];
|
|
if (!key) {
|
|
throw Error(`Buffer has an unsupported format`);
|
|
}
|
|
const encoder = supportedFormats[key];
|
|
const mod = await encoder.dec();
|
|
const rgba = mod.decode(new Uint8Array(buffer));
|
|
return rgba;
|
|
}
|
|
async function rotate(image, numRotations) {
|
|
image = ImageData.from(image);
|
|
const m = await preprocessors["rotate"].instantiate();
|
|
return await m(image.data, image.width, image.height, { numRotations });
|
|
}
|
|
async function resize({ image, width, height }) {
|
|
image = ImageData.from(image);
|
|
const p = preprocessors["resize"];
|
|
const m = await p.instantiate();
|
|
await maybeDelay();
|
|
return await m(image.data, image.width, image.height, {
|
|
...p.defaultOptions,
|
|
width,
|
|
height
|
|
});
|
|
}
|
|
async function encodeJpeg(image, opts) {
|
|
image = ImageData.from(image);
|
|
const e = supportedFormats["mozjpeg"];
|
|
const m = await e.enc();
|
|
await maybeDelay();
|
|
const quality = opts.quality || e.defaultEncoderOptions.quality;
|
|
const r = await m.encode(image.data, image.width, image.height, {
|
|
...e.defaultEncoderOptions,
|
|
quality
|
|
});
|
|
return r;
|
|
}
|
|
async function encodeWebp(image, opts) {
|
|
image = ImageData.from(image);
|
|
const e = supportedFormats["webp"];
|
|
const m = await e.enc();
|
|
await maybeDelay();
|
|
const quality = opts.quality || e.defaultEncoderOptions.quality;
|
|
const r = await m.encode(image.data, image.width, image.height, {
|
|
...e.defaultEncoderOptions,
|
|
quality
|
|
});
|
|
return r;
|
|
}
|
|
async function encodeAvif(image, opts) {
|
|
image = ImageData.from(image);
|
|
const e = supportedFormats["avif"];
|
|
const m = await e.enc();
|
|
await maybeDelay();
|
|
const val = e.autoOptimize.min;
|
|
const quality = opts.quality || 75;
|
|
const r = await m.encode(image.data, image.width, image.height, {
|
|
...e.defaultEncoderOptions,
|
|
// Think of cqLevel as the "amount" of quantization (0 to 62),
|
|
// so a lower value yields higher quality (0 to 100).
|
|
cqLevel: quality === 0 ? val : Math.round(val - quality / 100 * val)
|
|
});
|
|
return r;
|
|
}
|
|
async function encodePng(image) {
|
|
image = ImageData.from(image);
|
|
const e = supportedFormats["oxipng"];
|
|
const m = await e.enc();
|
|
await maybeDelay();
|
|
const r = await m.encode(image.data, image.width, image.height, {
|
|
...e.defaultEncoderOptions
|
|
});
|
|
return r;
|
|
}
|
|
export {
|
|
decodeBuffer,
|
|
encodeAvif,
|
|
encodeJpeg,
|
|
encodePng,
|
|
encodeWebp,
|
|
resize,
|
|
rotate
|
|
};
|