@napi-rs/image
Transform and optimize images library.
See Examples for usage.
Transformer:
This library support encode/decode these formats:
Format | Input | Output |
---|
RawPixels | RGBA 8 bits pixels | |
JPEG | Baseline and progressive | Baseline JPEG |
PNG | All supported color types | Same as decoding |
BMP | ✅ | Rgb8, Rgba8, Gray8, GrayA8 |
ICO | ✅ | ✅ |
TIFF | Baseline(no fax support) + LZW + PackBits | Rgb8, Rgba8, Gray8 |
WebP | ✅ | ✅ |
AVIF | ✅ | ✅ |
PNM | PBM, PGM, PPM, standard PAM | ✅ |
DDS | DXT1, DXT3, DXT5 | No |
TGA | ✅ | Rgb8, Rgba8, Bgr8, Bgra8, Gray8, GrayA8 |
OpenEXR | Rgb32F, Rgba32F (no dwa compression) | Rgb32F, Rgba32F (no dwa compression) |
farbfeld | ✅ | ✅ |
See index.d.ts for API reference.
New from Constructor
import { Transformer } from '@napi-rs/image'
const transformer = new Transformer(input)
New from RGBA RawPixels
import { Transformer } from '@napi-rs/image'
import { decode } from 'blurhash'
const pixels = decode('LEHV6nWB2yk8pyo0adR*.7kCMdnj', 32, 32)
const transformer = Transformer.fromRgbaPixels(pixels, 32, 32)
Metadata
metadata(withExif?: boolean | undefined | null, signal?: AbortSignal | undefined | null): Promise<Metadata>
export interface Metadata {
width: number
height: number
exif?: Record<string, string> | undefined | null
orientation?: number | undefined | null
format: string
colorType: JsColorType
}
export enum JsColorType {
L8 = 0,
La8 = 1,
Rgb8 = 2,
Rgba8 = 3,
L16 = 4,
La16 = 5,
Rgb16 = 6,
Rgba16 = 7,
Rgb32F = 8,
Rgba32F = 9
}
Example:
import { promises as fs } from 'fs'
import { Transformer } from '@napi-rs/image'
const WITH_EXIF_JPG = await fs.readFile('with-exif.jpg')
const decoder = new Transformer(WITH_EXIF_JPG)
const metadata = await decoder.metadata(true)
The metadata will be
{
colorType: 2,
exif: {
Orientation: 'Unknown (5)',
'Resolution Unit': 'in',
'This image has an Exif SubIFD': '90',
'X Resolution': '72 pixels per in',
'Y Resolution': '72 pixels per in',
},
format: 'jpeg',
height: 450,
orientation: 5,
width: 600,
}
Transform Image format
import { promises as fs } from 'fs'
import { Transformer } from '@napi-rs/image'
const PNG = await fs.readFile('./un-optimized.png')
const webp = await new Transformer(PNG).webp()
await fs.writeFile('optimized.webp)
webp
The quality factor quality_factor
ranges from 0 to 100 and controls the loss and quality during compression.
The value 0 corresponds to low quality and small output sizes, whereas 100 is the highest quality and largest output size.
https://developers.google.com/speed/webp/docs/api#simple_encoding_api
Default is 90
webp(qualityFactor: number, signal?: AbortSignal | undefined | null): Promise<Buffer>
webpSync(qualityFactor: number): Buffer
webpLossless(signal?: AbortSignal | undefined | null): Promise<Buffer>
webpLosslessSync(): Buffer
AVIF
Config:
export interface AvifConfig {
quality?: number | undefined | null
alphaQuality?: number | undefined | null
speed?: number | undefined | null
threads?: number | undefined | null
chromaSubsampling?: ChromaSubsampling | undefined | null
}
export enum ChromaSubsampling {
Yuv444 = 0,
Yuv422 = 1,
Yuv420 = 2,
Yuv400 = 3,
}
avif(options?: AvifConfig | undefined | null, signal?: AbortSignal | undefined | null): Promise<Buffer>
avifSync(options?: AvifConfig | undefined | null): Buffer
PNG
PngEncodeOptions:
export interface PngEncodeOptions {
compressionType?: CompressionType | undefined | null
filterType?: FilterType | undefined | null
}
export enum CompressionType {
Default = 0,
Fast = 1,
Best = 2,
Huffman = 3,
Rle = 4,
}
export enum FilterType {
NoFilter = 0,
Sub = 1,
Up = 2,
Avg = 3,
Paeth = 4,
Adaptive = 5,
}
png(options?: PngEncodeOptions | undefined | null, signal?: AbortSignal | undefined | null): Promise<Buffer>
pngSync(options?: PngEncodeOptions | undefined | null): Buffer
JPEG
jpeg(quality?: number | undefined | null, signal?: AbortSignal | undefined | null): Promise<Buffer>
jpegSync(quality?: number | undefined | null): Buffer
BMP
bmp(signal?: AbortSignal | undefined | null): Promise<Buffer>
bmpSync(): Buffer
ICO
ico(signal?: AbortSignal | undefined | null): Promise<Buffer>
icoSync(): Buffer
TIFF
tiff(signal?: AbortSignal | undefined | null): Promise<Buffer>
tiffSync(): Buffer
PNM
pnm(signal?: AbortSignal | undefined | null): Promise<Buffer>
pnmSync(): Buffer
TGA
tga(signal?: AbortSignal | undefined | null): Promise<Buffer>
tgaSync(): Buffer
Farbfeld
farbfeld(signal?: AbortSignal | undefined | null): Promise<Buffer>
farbfeldSync(): Buffer
Manipulate Image
rotate
Rotate the image with exif orientation, if the input image contains no exif information, this API will have no effect.
rotate(): this
Example:
This image has orientation value 5
in exif:
Without rotate:
import { promises as fs } from 'fs'
import { Transformer } from '@napi-rs/image'
const WITH_EXIF_JPG = await fs.readFile('with-exif.jpg')
const imageOutputWithoutRotateWebp = await new Transformer(WITH_EXIF).resize(450 / 2).webp(75)
writeFileSync('output-exif.no-rotate.image.webp', imageOutputWithoutRotateWebp)
With rotate:
import { promises as fs } from 'fs'
import { Transformer } from '@napi-rs/image'
const WITH_EXIF_JPG = await fs.readFile('with-exif.jpg')
const imageOutputWebp = await new Transformer(WITH_EXIF)
.rotate()
.resize(450 / 2)
.webp(75)
console.timeEnd('@napi-rs/image webp')
writeFileSync('output-exif.image.webp', imageOutputWebp)
grayscale
grayscale(): this
invert
Invert the colors of this image.
invert(): this
resize
resize(width: number, height?: number | undefined | null, filterType?: ResizeFilterType | undefined | null): this
export enum ResizeFilterType {
Nearest = 0,
Triangle = 1,
CatmullRom = 2,
Gaussian = 3,
Lanczos3 = 4
}
overlay
overlay(onTop: Buffer, x: number, y: number): this
import { writeFileSync } from 'fs'
import { Transformer } from '@napi-rs/image'
const imageOutputPng = await new Transformer(PNG).overlay(PNG, 200, 200).png()
writeFileSync('output-overlay-png.png', imageOutputPng)
ResizeFilterType:
To test the different sampling filters on a real example, you can find two
examples called
scaledown
and
scaleup
in the examples
directory of the crate source code.
Here is a 3.58 MiB
test image
that has been scaled down to 300x225 px:
Nearest Neighbor
Linear: Triangle
Cubic: Catmull-Rom
Gaussian
Lanczos with window 3
Speed
Time required to create each of the examples above, tested on an Intel
i7-4770 CPU with Rust 1.37 in release mode:
Nearest | 31 ms |
---|
Triangle | 414 ms |
---|
CatmullRom | 817 ms |
---|
Gaussian | 1180 ms |
---|
Lanczos3 | 1170 ms |
---|
blur
Performs a Gaussian blur on this image.
sigma` is a measure of how much to blur by.
blur(sigma: number): this
unsharpen
Performs an unsharpen mask on this image.
sigma
is the amount to blur the image by.
threshold
is a control of how much to sharpen.
See https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking
unsharpen(sigma: number, threshold: number): this
filter3x3
Filters this image with the specified 3x3 kernel. Error will thrown if the kernel length is not 9
.
filter3x3(kernel: Array<number>): this
adjustContrast
Adjust the contrast of this image.
contrast
is the amount to adjust the contrast by.
Negative values decrease the contrast and positive values increase the contrast.
adjustContrast(contrast: number): this
brighten
Brighten the pixels of this image.
value
is the amount to brighten each pixel by.
Negative values decrease the brightness and positive values increase it.
brighten(brightness: number): this
huerotate
Hue rotate the supplied image.
value
is the degrees to rotate each pixel by.
0 and 360 do nothing, the rest rotates by the given degree value.
just like the css webkit filter hue-rotate(180)
huerotate(hue: number): this
Optimize PNG
Lossless compression
Lossless optimize PNG powered by oxipng.
PNGLosslessOptions
export interface PNGLosslessOptions {
fixErrors?: boolean | undefined | null
force?: boolean | undefined | null
filter?: Array<number> | undefined | null
bitDepthReduction?: boolean | undefined | null
colorTypeReduction?: boolean | undefined | null
paletteReduction?: boolean | undefined | null
grayscaleReduction?: boolean | undefined | null
idatRecoding?: boolean | undefined | null
strip?: boolean | undefined | null
useHeuristics?: boolean | undefined | null
}
export function losslessCompressPng(
input: Buffer,
options?: PNGLosslessOptions | undefined | null,
signal?: AbortSignal | undefined | null,
): Promise<Buffer>
export function losslessCompressPngSync(input: Buffer, options?: PNGLosslessOptions | undefined | null): Buffer
Lossy compression
Powered by pngquant, converts RGBA images to palette-based 8-bit indexed images, including alpha component.
PngQuantOptions:
export interface PngQuantOptions {
minQuality?: number | undefined | null
maxQuality?: number | undefined | null
speed?: number | undefined | null
posterization?: number | undefined | null
}
export function pngQuantize(
input: Buffer,
options?: PngQuantOptions | undefined | null,
signal?: AbortSignal | undefined | null,
): Promise<Buffer>
export function pngQuantizeSync(input: Buffer, options?: PngQuantOptions | undefined | null): Buffer
Optimize JPEG
Lossy and Lossless JPEG compression powered by mozjpeg.
JpegCompressOptions:
export interface JpegCompressOptions {
quality?: number | undefined | null
optimizeScans?: boolean | undefined | null
}
export function compressJpeg(
input: Buffer,
options?: JpegCompressOptions | undefined | null,
signal?: AbortSignal | undefined | null,
): Promise<Buffer>
export function compressJpegSync(input: Buffer, options?: JpegCompressOptions | undefined | null): Buffer
Credits
See Credits