Skip to content
LogoLogo

@portable/png

Functions

Png.decode

Decodes a PNG file to 8-bit RGBA pixels. Decompresses with DecompressionStream unless you pass inflate.

async function decode(
  bytes: Uint8Array,
  options: decode.Options = {},
): Promise<Decoded>;
OptionTypeDefaultDescription
checkCrcbooleantrueVerify the CRC of every chunk.
maxPixelsnumberInfinityThe most pixels (width * height) to decode. A larger image throws MaxPixelsError before anything is decompressed or allocated.
inflate((data: Uint8Array) => Uint8Array | Promise<Uint8Array>)DecompressionStream('deflate')Decompresses zlib data.
signalAbortSignalRejects with signal.reason when aborted, and stops decompressing.

Throws

  • MaxPixelsError When width * height is more than maxPixels.
import * as Png from '@portable/png';
 
const response = await fetch('https://example.com/image.png');
const { width, height, data } = await Png.decode(
  await response.bytes(),
);

Png.decodeSync

Decodes a PNG file to 8-bit RGBA pixels synchronously, with the inflate function you pass.

function decodeSync(
  bytes: Uint8Array,
  options: decodeSync.Options,
): Decoded;
OptionTypeDefaultDescription
checkCrcbooleantrueVerify the CRC of every chunk.
maxPixelsnumberInfinityThe most pixels (width * height) to decode. A larger image throws MaxPixelsError before anything is decompressed or allocated.
inflate(data: Uint8Array) => Uint8ArrayDecompresses zlib data, such as unzlibSync from fflate.

Throws

  • MaxPixelsError When width * height is more than maxPixels.
import * as Png from '@portable/png';
import { unzlibSync } from 'fflate';
 
const { width, height, data } = Png.decodeSync(bytes, {
  inflate: unzlibSync,
});

Png.encode

Encodes 8-bit RGBA pixels as a PNG file. Compresses with CompressionStream unless you pass deflate.

async function encode(
  image: RgbaImage,
  options: encode.Options = {},
): Promise<Uint8Array<ArrayBuffer>>;
OptionTypeDefaultDescription
colorType0 | 2 | 4 | 66Color type to write: 0 grayscale, 2 RGB, 4 grayscale with alpha, 6 RGBA. Gray is the BT.709 luma of each pixel.
filterFilterType | readonly FilterType[][0, 1, 2, 3, 4]Filter for every row, or the filters to choose from per row. With more than one, each row gets the filter whose output has the smallest sum of absolute values.
background{ red: number; green: number; blue: number; }whiteColor to blend transparent pixels onto when colorType has no alpha.
deflate((data: Uint8Array) => Uint8Array | Promise<Uint8Array>)CompressionStream('deflate')Compresses to zlib format. Set it to choose a compression level.
signalAbortSignalRejects with signal.reason when aborted, and stops compressing.
import * as Png from '@portable/png';
 
const data = new Uint8Array(16 * 16 * 4).fill(255);
const bytes = await Png.encode({ width: 16, height: 16, data });
return new Response(bytes, {
  headers: { 'content-type': 'image/png' },
});

Png.encodeSync

Encodes 8-bit RGBA pixels as a PNG file synchronously, with the deflate function you pass.

function encodeSync(
  image: RgbaImage,
  options: encodeSync.Options,
): Uint8Array<ArrayBuffer>;
OptionTypeDefaultDescription
colorType0 | 2 | 4 | 66Color type to write: 0 grayscale, 2 RGB, 4 grayscale with alpha, 6 RGBA. Gray is the BT.709 luma of each pixel.
filterFilterType | readonly FilterType[][0, 1, 2, 3, 4]Filter for every row, or the filters to choose from per row. With more than one, each row gets the filter whose output has the smallest sum of absolute values.
background{ red: number; green: number; blue: number; }whiteColor to blend transparent pixels onto when colorType has no alpha.
deflate(data: Uint8Array) => Uint8ArrayCompresses to zlib format, such as zlibSync from fflate.
import * as Png from '@portable/png';
import { zlibSync } from 'fflate';
 
const bytes = Png.encodeSync(
  { width, height, data },
  { deflate: (raw) => zlibSync(raw, { level: 9 }) },
);

Errors

Png.InvalidInputError

Thrown when the input is malformed: a bad signature, a CRC mismatch, an invalid header, corrupt image data, or (from encode) pixel data that doesn't match the size.

class InvalidInputError extends Errors.InvalidInputError {
  readonly kind: string;
}
import * as Png from '@portable/png';
 
try {
  await Png.decode(new TextEncoder().encode('not a png'));
} catch (error) {
  if (error instanceof Png.InvalidInputError) {
    console.log(error.message);
  }
}

Png.MaxPixelsError

Thrown by decode and decodeSync when the image has more pixels (width * height) than maxPixels. The check runs on the IHDR chunk, before any image data is decompressed or allocated.

class MaxPixelsError extends Errors.BaseError {
  constructor(
    options: ErrorOptions & {
      maxPixels: number;
    },
  );
  readonly kind: string;
  readonly maxPixels: number;
}
import * as Png from '@portable/png';
 
try {
  await Png.decode(upload, { maxPixels: 50_000_000 });
} catch (error) {
  if (error instanceof Png.MaxPixelsError) {
    return new Response('Image too large', { status: 413 });
  }
  throw error;
}

Png.TruncatedError

Thrown by decode when the file ends early: before IEND, inside a chunk, or with less image data than the image needs.

class TruncatedError extends Errors.TruncatedError {
  readonly kind: string;
}
import * as Png from '@portable/png';
 
try {
  await Png.decode(bytes.subarray(0, 100));
} catch (error) {
  if (error instanceof Png.TruncatedError) {
    console.log('incomplete download');
  }
}

Png.UnsupportedError

Thrown by decode for a valid file that uses something this package doesn't handle: an unknown critical chunk, or a compression, filter or interlace method other than the ones PNG defines.

class UnsupportedError extends Errors.UnsupportedError {
  readonly kind: string;
}
import * as Png from '@portable/png';
 
try {
  await Png.decode(bytes);
} catch (error) {
  if (error instanceof Png.UnsupportedError) {
    console.log(error.message);
  }
}

Types

Png.BitDepth

Bits per sample (per palette index for color type 3).

type BitDepth =  |  |  |  | ;

Png.ColorType

PNG color type: 0 grayscale, 2 RGB, 3 palette, 4 grayscale with alpha, 6 RGBA.

type ColorType =  |  |  |  | ;

Png.Decoded

A decoded image. data holds 8-bit RGBA, four bytes per pixel, row by row from the top left.

type Decoded = {
  width: number;
  height: number;
  data: Uint8Array<ArrayBuffer>;
  depth: BitDepth;
  colorType: ColorType;
  interlace: boolean;
  alpha: boolean;
  gamma: number | undefined;
};

Png.RgbaImage

8-bit RGBA pixels, four bytes per pixel, row by row from the top left. A decoded image or a canvas ImageData fits.

type RgbaImage = {
  width: number;
  height: number;
  data: Uint8Array | Uint8ClampedArray;
  gamma?: number | undefined;
};

Png.FilterType

PNG scanline filter: 0 none, 1 sub, 2 up, 3 average, 4 Paeth.

type FilterType =  |  |  |  | ;