@cottonc/compress-image
v0.3.1
Published
Image compression utilities
Downloads
271
Readme
Used for png compression
Compatible with node and browser environment.
Currently support png (imagequant + oxipng)
notes
for vite users add the following to vite.config.ts
optimizeDeps: {
exclude: ["@cottonc/compress-image"],
},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, Auto)
import { encodePngInWorker, EPngProgress } from "@cottonc/compress-image";
async function fileToImageData(
file: File
): Promise<{ imageData: ImageData; width: number; height: number }> {
const bitmap = await createImageBitmap(file);
const canvas = new OffscreenCanvas(bitmap.width || 1, bitmap.height || 1);
const ctx = canvas.getContext("2d");
if (!ctx) {
throw new Error("Canvas is not supported in this browser");
}
ctx.drawImage(bitmap, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
return { imageData, width: canvas.width, height: canvas.height };
}
const { imageData, width, height } = await fileToImageData(file);
const transferable = new Uint8ClampedArray(imageData.data);
const result = await encodePngInWorker(
{
buffer: transferable.buffer,
width,
height,
options: {
quality: 80,
quantize_speed: 4,
oxi_level: 4,
losses_compress: false,
},
},
{
onProgress: (progress, message) => {
console.log(`${EPngProgress[progress]}: ${message}`);
},
}
);
// Uint8Array of compressed PNG bytes
const compressedPng = new Uint8Array(result.buffer);browser (With Worker, Manuel)
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}`);
},
});