0.2.7 • Published 8 months ago
@cottonc/compress-image v0.2.7
Used for image compression
Compatible with node and browser environment.
Currently support png (imagequant + oxipng) and jpg (mozjpeg).
png
node
import { PNG } from "pngjs";
import fs from "fs/promises";
import { encodePng, EPngProgress } from "@cottonc/compress-image";
const pngBuffer = await fs.readFile("./test.png");
const png = PNG.sync.read(pngBuffer);
const compressedPng = await encodePng({
imageData: {
data: new Uint8ClampedArray(png.data),
width: png.width,
height: png.height,
},
options: {
// 0 - 100 default 100
quality: 100,
// 1 - 10 default 4
quantize_speed: 4,
// 0 - 6 default 4
oxi_level: 4,
// only use oxi for lossless compression, default false
losses_compress: false,
},
progressCallback: (progress, message) => {
console.log(`${EPngProgress[progress]}: ${message}`);
},
});browser (With Worker)
import { encodePng, EPngProgress } from "@cottonc/compress-image";
function readFileAsArrayBuffer(file: File): Promise<ArrayBuffer> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (event: ProgressEvent<FileReader>) => {
if (event.target?.result instanceof ArrayBuffer) {
resolve(event.target.result);
} else {
reject(new Error("Failed to read file as ArrayBuffer."));
}
};
reader.onerror = (err) => reject(new Error("FileReader error: " + err));
reader.readAsArrayBuffer(file);
});
}
async function decodePngToRgba(
pngBytes: Uint8Array
): Promise<{ width: number; height: number; data: Uint8Array }> {
const blob = new Blob([pngBytes], { type: "image/png" });
const imageBitmap = await createImageBitmap(blob);
const canvas = new OffscreenCanvas(imageBitmap.width, imageBitmap.height);
const ctx = canvas.getContext("2d");
if (!ctx) {
throw new Error("Failed to get canvas context");
}
// Draw the image to the canvas
ctx.drawImage(imageBitmap, 0, 0);
// Get the raw RGBA pixel data
const imageData = ctx.getImageData(
0,
0,
imageBitmap.width,
imageBitmap.height
);
// Clean up
imageBitmap.close();
return {
width: imageData.width,
height: imageData.height,
data: new Uint8ClampedArray(imageData.data.buffer),
};
}
const imageData = await readFileAsArrayBuffer(file);
const { width, height, data } = await decodePngToRgba(
new Uint8Array(imageData)
);
// same as node
const compressedPng = await encodePng({
imageData: {
data,
width,
height,
},
options: {
quality: 100,
quantize_speed: 4,
oxi_level: 4,
losses_compress: false,
},
progressCallback: (progress, message) => {
console.log(`${EPngProgress[progress]}: ${message}`);
},
});jpg
node
import { decodeJpeg, encodeJpeg } from "@cottonc/compress-image";
import fs from "fs/promises";
const jpgBuffer = await fs.readFile("./test.jpg");
const jpg = await decodeJpeg(jpgBuffer);
// following is the default options
// align with squoosh mozjpeg compress
const compressedJpg = await encodeJpeg({
imageData: jpg,
options: {
quality: 75,
baseline: false,
arithmetic: false,
progressive: true,
optimize_coding: true,
smoothing: 0,
color_space: MozJpegColorSpace.YCbCr,
quant_table: 3,
trellis_multipass: false,
trellis_opt_zero: false,
trellis_opt_table: false,
trellis_loops: 1,
auto_subsample: true,
chroma_subsample: 2,
separate_chroma_quality: false,
chroma_quality: 75,
},
});browser (With Worker)
function readFileAsArrayBuffer(file: File): Promise<ArrayBuffer> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (event: ProgressEvent<FileReader>) => {
if (event.target?.result instanceof ArrayBuffer) {
resolve(event.target.result);
} else {
reject(new Error("Failed to read file as ArrayBuffer."));
}
};
reader.onerror = (err) => reject(new Error("FileReader error: " + err));
reader.readAsArrayBuffer(file);
});
}
const jpgBuffer = await readFileAsArrayBuffer(file);
// same as node
const jpg = await decodeJpeg(jpgBuffer);
const compressedJpg = await encodeJpeg({
imageData: jpg,
options: {
quality: 75,
baseline: false,
arithmetic: false,
progressive: true,
optimize_coding: true,
smoothing: 0,
color_space: MozJpegColorSpace.YCbCr,
quant_table: 3,
trellis_multipass: false,
trellis_opt_zero: false,
trellis_opt_table: false,
trellis_loops: 1,
auto_subsample: true,
chroma_subsample: 2,
separate_chroma_quality: false,
chroma_quality: 75,
},
});