0.2.7 • Published 7 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,
  },
});