Files
platform/seed/skills/sharp/SKILL.md
T
2026-02-16 19:34:35 +00:00

726 lines
21 KiB
Markdown

---
name: sharp
description: Process images using the sharp Node.js library. Use when the user wants to resize, convert, crop, composite, transform, or optimize images programmatically.
---
# Sharp
API reference for sharp — a high-performance Node.js image processing library built on libvips.
Typically 4-5x faster than ImageMagick/GraphicsMagick. Supports JPEG, PNG, WebP, GIF, AVIF, TIFF, SVG, HEIF, JP2, and JXL.
Official docs: https://sharp.pixelplumbing.com
Repository: https://github.com/lovell/sharp
## Installation
```bash
npm install sharp
```
Requires Node.js ^18.17.0 or >= 20.3.0 (or Deno/Bun with Node-API v9).
## Usage
Sharp uses a fluent, chainable API. Every call returns a Sharp instance.
```js
import sharp from 'sharp';
await sharp('input.jpg')
.resize(800, 600)
.jpeg({ quality: 80 })
.toFile('output.jpg');
```
Sharp implements `stream.Duplex` — it can be piped to/from.
---
## Constructor
```js
sharp([input], [options])
```
- `input` (Buffer | string | Array): Image buffer, file path, array of inputs, or omit for stream input.
### Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `failOn` | string | `'warning'` | `'none'`, `'truncated'`, `'error'`, `'warning'` |
| `limitInputPixels` | number \| boolean | `268402689` | Max pixels; `false` to disable |
| `unlimited` | boolean | `false` | Remove memory safety for JPEG/PNG/SVG/HEIF |
| `autoOrient` | boolean | `false` | Auto-rotate per EXIF Orientation |
| `sequentialRead` | boolean | `true` | Sequential vs random access |
| `density` | number | `72` | DPI for vector images (1-100000) |
| `ignoreIcc` | boolean | `false` | Ignore embedded ICC profile |
| `pages` | number | `1` | Pages to extract; `-1` for all |
| `page` | number | `0` | Starting page (zero-based) |
| `animated` | boolean | `false` | Read all frames (equiv. `pages: -1`) |
### Raw Input
```js
sharp(buffer, { raw: { width: 100, height: 100, channels: 4 } })
```
| Property | Type | Description |
|----------|------|-------------|
| `width` | number | Pixel width |
| `height` | number | Pixel height |
| `channels` | number | 1-4 |
| `premultiplied` | boolean | Skip premultiplication (default `false`) |
### Create New Image
```js
sharp({ create: { width: 300, height: 200, channels: 4, background: '#ff0000' } })
```
| Property | Type | Description |
|----------|------|-------------|
| `width` | number | Pixel width |
| `height` | number | Pixel height |
| `channels` | number | 3 (RGB) or 4 (RGBA) |
| `background` | string \| Object | Color (parsed by color module) |
| `noise` | Object | `{ type: 'gaussian', mean: 128, sigma: 30 }` |
### Render Text
```js
sharp({ text: { text: 'Hello', font: 'Arial', dpi: 150 } })
```
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `text` | string | -- | UTF-8; supports Pango markup |
| `font` | string | -- | Font name |
| `fontfile` | string | -- | Absolute path to font file |
| `width` | number | `0` | Word-wrap boundary; 0 = no wrap |
| `height` | number | `0` | Max height |
| `align` | string | `'left'` | `'left'`, `'centre'`, `'center'`, `'right'` |
| `justify` | boolean | `false` | Text justification |
| `dpi` | number | `72` | Render resolution |
| `rgba` | boolean | `false` | RGBA for color emoji/Pango markup |
| `spacing` | number | `0` | Line height in points |
| `wrap` | string | `'word'` | `'word'`, `'char'`, `'word-char'`, `'none'` |
### Join Array
```js
sharp([img1, img2, img3], { join: { across: 3, shim: 10 } })
```
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `across` | number | `1` | Images per row |
| `animated` | boolean | `false` | Join as animated image |
| `shim` | number | `0` | Pixel gap between images |
| `background` | string \| Object | -- | Gap fill color |
| `halign` | string | `'left'` | `'left'`, `'centre'`, `'right'` |
| `valign` | string | `'top'` | `'top'`, `'centre'`, `'bottom'` |
### Clone
```js
const pipeline = sharp('input.jpg');
const clone1 = pipeline.clone().resize(200).toFile('thumb.jpg');
const clone2 = pipeline.clone().resize(800).toFile('large.jpg');
```
---
## Resize
```js
.resize([width], [height], [options])
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `width` | number | -- | Target width (null to auto-scale) |
| `height` | number | -- | Target height (null to auto-scale) |
| `fit` | string | `'cover'` | `'cover'`, `'contain'`, `'fill'`, `'inside'`, `'outside'` |
| `position` | string | `'centre'` | Gravity/position for cover/contain |
| `background` | string \| Object | `{r:0,g:0,b:0,alpha:1}` | Fill color for `contain` |
| `kernel` | string | `'lanczos3'` | `'nearest'`, `'linear'`, `'cubic'`, `'mitchell'`, `'lanczos2'`, `'lanczos3'` |
| `withoutEnlargement` | boolean | `false` | Don't upscale |
| `withoutReduction` | boolean | `false` | Don't downscale |
| `fastShrinkOnLoad` | boolean | `true` | JPEG/WebP shrink-on-load |
**Fit modes:**
- `cover` — crop to fill both dimensions
- `contain` — letterbox within dimensions
- `fill` — stretch to exact dimensions (ignores aspect ratio)
- `inside` — fit within, no exceeding
- `outside` — minimum size meeting both dimensions
**Position values:** `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`, `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `centre`/`center`
**Strategy (cover only):** `entropy`, `attention`
Only one resize per pipeline.
---
## Operations
### Rotation & Orientation
| Method | Description |
|--------|-------------|
| `.rotate([angle], [options])` | Rotate by degrees; omit angle for EXIF auto-rotate. `options.background` for fill color |
| `.autoOrient()` | Auto-orient from EXIF, then remove Orientation tag |
| `.flip([flip])` | Vertical mirror (default `true`) |
| `.flop([flop])` | Horizontal mirror (default `true`) |
### Transform
| Method | Description |
|--------|-------------|
| `.affine(matrix, [options])` | 2x2 affine transform. Options: `background`, `idx`, `idy`, `odx`, `ody`, `interpolator` |
| `.extend(extend)` | Add padding. Number for uniform, or `{ top, right, bottom, left, extendWith, background }`. `extendWith`: `'background'`, `'copy'`, `'repeat'`, `'mirror'` |
| `.extract({ left, top, width, height })` | Crop region. Can be called before and/or after resize |
| `.trim([options])` | Auto-crop to content. Options: `background` (default top-left pixel), `threshold` (default `10`), `lineArt` |
### Enhancement
| Method | Description |
|--------|-------------|
| `.sharpen([options])` | Sharpen. `options.sigma` (0.000001-10), `.m1` (flat), `.m2` (jagged), `.x1`, `.y2`, `.y3` |
| `.blur([options])` | No args: 3x3 box blur. `options.sigma` (0.3-1000) for Gaussian. Options: `precision`, `minAmplitude` |
| `.median([size])` | Median filter, default 3x3 |
| `.gamma([gamma], [gammaOut])` | Gamma correction (1.0-3.0, default 2.2) |
| `.normalise([options])` | Stretch luminance. `options.lower` (default `1`), `.upper` (default `99`) percentiles |
| `.clahe({ width, height, [maxSlope] })` | Contrast Limited Adaptive Histogram Equalization |
### Morphology
| Method | Description |
|--------|-------------|
| `.dilate([width])` | Dilation, default 1px |
| `.erode([width])` | Erosion, default 1px |
### Pixel Operations
| Method | Description |
|--------|-------------|
| `.negate([options])` | Invert colors. `options.alpha` (default `true`) |
| `.threshold([value], [options])` | Binarize at threshold (0-255, default 128). `options.greyscale` (default `true`) |
| `.boolean(operand, operator)` | Bitwise op with another image: `'and'`, `'or'`, `'eor'` |
| `.linear([a], [b])` | Per-channel linear transform: `a * pixel + b` |
| `.recomb(matrix)` | 3x3 or 4x4 color recombination matrix |
| `.modulate([options])` | Adjust `brightness` (multiply), `saturation` (multiply), `hue` (degrees), `lightness` (add) |
| `.convolve(kernel)` | Custom convolution: `{ width, height, kernel, scale, offset }` |
| `.flatten([options])` | Merge alpha with `options.background`, remove alpha |
| `.unflatten()` | Add alpha; white becomes transparent (experimental) |
---
## Colour
| Method | Description |
|--------|-------------|
| `.tint(color)` | Apply tint, preserving alpha |
| `.greyscale([bool])` | Convert to 8-bit greyscale (alias: `.grayscale()`) |
| `.pipelineColourspace(space)` | Set pipeline colorspace (e.g. `'rgb16'`, `'lab'`, `'grey16'`) |
| `.toColourspace(space)` | Set output colorspace (e.g. `'srgb'`, `'cmyk'`, `'b-w'`) |
---
## Channel
| Method | Description |
|--------|-------------|
| `.removeAlpha()` | Remove alpha channel |
| `.ensureAlpha([alpha])` | Add alpha if missing. `alpha`: 0 (transparent) to 1 (opaque, default) |
| `.extractChannel(channel)` | Extract single channel: `0`-`3` or `'red'`, `'green'`, `'blue'`, `'alpha'` |
| `.joinChannel(images, [options])` | Add channel(s) from other image(s) |
| `.bandbool(op)` | Bitwise across all bands: `'and'`, `'or'`, `'eor'` |
---
## Composite
```js
.composite(images)
```
Overlay images onto the pipeline image. `images` is an array of objects:
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `input` | Buffer \| string | -- | Image data, file path, or `create`/`text` object |
| `blend` | string | `'over'` | Blend mode |
| `gravity` | string | `'centre'` | Placement gravity |
| `top` | number | -- | Pixel offset from top (overrides gravity) |
| `left` | number | -- | Pixel offset from left (overrides gravity) |
| `tile` | boolean | `false` | Repeat overlay across image |
| `premultiplied` | boolean | `false` | Skip premultiplication |
| `density` | number | `72` | DPI for vector overlays |
**Blend modes:** `over`, `multiply`, `screen`, `overlay`, `darken`, `lighten`, `hard-light`, `soft-light`, `difference`, `exclusion`, `colour-dodge`, `colour-burn`, `add`, `saturate`, `clear`, `source`, `in`, `out`, `atop`, `dest`, `dest-over`, `dest-in`, `dest-out`, `dest-atop`, `xor`
```js
await sharp('base.png')
.composite([{ input: 'overlay.png', gravity: 'southeast' }])
.toFile('output.png');
```
---
## Output
### Write to File
```js
await sharp('input.jpg').resize(800).toFile('output.jpg');
```
Format inferred from extension. Returns `{ format, size, width, height, channels, premultiplied }`.
### Write to Buffer
```js
const buffer = await sharp('input.jpg').resize(800).toBuffer();
// or with info:
const { data, info } = await sharp('input.jpg').resize(800).toBuffer({ resolveWithObject: true });
```
### Format Methods
#### JPEG
```js
.jpeg([options])
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `quality` | number | `80` | 1-100 |
| `progressive` | boolean | `false` | Progressive JPEG |
| `chromaSubsampling` | string | `'4:2:0'` | `'4:2:0'` or `'4:4:4'` |
| `mozjpeg` | boolean | `false` | MozJPEG optimizations |
| `force` | boolean | `true` | Force JPEG output |
#### PNG
```js
.png([options])
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `progressive` | boolean | `false` | Progressive (interlace) |
| `compressionLevel` | number | `6` | 0-9 |
| `adaptiveFiltering` | boolean | `false` | Adaptive row filtering |
| `palette` | boolean | `false` | Quantise to palette |
| `quality` | number | `100` | Palette quality (1-100) |
| `effort` | number | `7` | CPU effort (1-10, palette mode) |
| `colours`/`colors` | number | `256` | Max palette colors (2-256) |
| `dither` | number | `1.0` | Floyd-Steinberg dithering level |
| `force` | boolean | `true` | Force PNG output |
#### WebP
```js
.webp([options])
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `quality` | number | `80` | 1-100 |
| `alphaQuality` | number | `100` | 0-100 |
| `lossless` | boolean | `false` | Lossless compression |
| `nearLossless` | boolean | `false` | Near-lossless mode |
| `smartSubsample` | boolean | `false` | Smart chroma subsampling |
| `preset` | string | `'default'` | `'default'`, `'photo'`, `'picture'`, `'drawing'`, `'icon'`, `'text'` |
| `effort` | number | `4` | 0-6 |
| `loop` | number | `0` | Animation loops (0 = infinite) |
| `delay` | number \| Array | -- | Frame delay(s) in ms |
| `force` | boolean | `true` | Force WebP output |
#### AVIF
```js
.avif([options])
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `quality` | number | `50` | 1-100 |
| `lossless` | boolean | `false` | Lossless mode |
| `effort` | number | `4` | 0-9 |
| `chromaSubsampling` | string | `'4:4:4'` | Chroma subsampling |
| `bitdepth` | number | `8` | 8, 10, or 12 |
#### GIF
```js
.gif([options])
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `reuse` | boolean | `true` | Reuse palette |
| `progressive` | boolean | `false` | Progressive (interlace) |
| `colours`/`colors` | number | `256` | 2-256 |
| `effort` | number | `7` | 1-10 |
| `dither` | number | `1.0` | 0-1 |
| `loop` | number | `0` | 0 = infinite |
| `delay` | number \| Array | -- | Frame delay(s) in ms |
| `force` | boolean | `true` | Force GIF output |
#### TIFF
```js
.tiff([options])
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `quality` | number | `80` | 1-100 |
| `compression` | string | `'jpeg'` | `'none'`, `'jpeg'`, `'deflate'`, `'packbits'`, `'lzw'`, `'webp'`, `'zstd'`, `'jp2k'`, `'ccittfax4'` |
| `predictor` | string | `'horizontal'` | `'none'`, `'horizontal'`, `'float'` |
| `pyramid` | boolean | `false` | Write image pyramid |
| `tile` | boolean | `false` | Tiled TIFF |
| `tileWidth` | number | `256` | Tile width |
| `tileHeight` | number | `256` | Tile height |
| `bitdepth` | number | `8` | 1, 2, 4, or 8 |
| `force` | boolean | `true` | Force TIFF output |
#### HEIF
```js
.heif({ compression: 'hevc' })
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `compression` | string | required | `'av1'` or `'hevc'` |
| `quality` | number | `50` | 1-100 |
| `lossless` | boolean | `false` | Lossless mode |
| `effort` | number | `4` | 0-9 |
| `bitdepth` | number | `8` | 8, 10, or 12 |
#### Raw
```js
.raw([options])
```
- `options.depth` (string, default `'uchar'`): `'char'`, `'uchar'`, `'short'`, `'ushort'`, `'int'`, `'uint'`, `'float'`, `'double'`
#### Tile (DZI / Zoomify / IIIF)
```js
.tile([options])
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `size` | number | `256` | Tile size (1-8192) |
| `overlap` | number | `0` | Tile overlap (0-8192) |
| `layout` | string | `'dz'` | `'dz'`, `'iiif'`, `'iiif3'`, `'zoomify'`, `'google'` |
| `container` | string | `'fs'` | `'fs'` or `'zip'` |
| `angle` | number | `0` | Rotation (multiple of 90) |
| `background` | string \| Object | white | Fill color |
---
## Metadata & Stats
### metadata()
```js
const meta = await sharp('input.jpg').metadata();
```
Returns without decoding pixels:
| Property | Type | Description |
|----------|------|-------------|
| `format` | string | `'jpeg'`, `'png'`, `'webp'`, `'gif'`, `'svg'`, etc. |
| `width` | number | Pixel width |
| `height` | number | Pixel height |
| `space` | string | Color space (`'srgb'`, `'rgb'`, `'cmyk'`, `'b-w'`, etc.) |
| `channels` | number | Band count |
| `depth` | string | Pixel depth (`'uchar'`, `'ushort'`, `'float'`, etc.) |
| `density` | number | DPI |
| `chromaSubsampling` | string | e.g. `'4:2:0'` |
| `isProgressive` | boolean | Progressive/interlaced |
| `hasAlpha` | boolean | Has alpha channel |
| `hasProfile` | boolean | Has ICC profile |
| `orientation` | number | EXIF orientation (1-8) |
| `pages` | number | Page count |
| `size` | number | Total bytes (Buffer/Stream input) |
| `exif` | Buffer | Raw EXIF |
| `icc` | Buffer | ICC profile |
| `xmp` | Buffer | XMP data |
### stats()
```js
const stats = await sharp('input.jpg').stats();
```
Returns pixel-derived statistics:
| Property | Type | Description |
|----------|------|-------------|
| `channels` | Array | Per-channel: `min`, `max`, `sum`, `mean`, `stdev`, `minX`, `minY`, `maxX`, `maxY` |
| `isOpaque` | boolean | Fully opaque |
| `entropy` | number | Greyscale entropy |
| `sharpness` | number | Laplacian sharpness |
| `dominant` | Object | Dominant sRGB color |
---
## Metadata Preservation
By default, sharp strips all metadata and converts to sRGB.
| Method | Description |
|--------|-------------|
| `.keepMetadata()` | Preserve all metadata (EXIF, ICC, XMP, IPTC) |
| `.keepExif()` | Preserve EXIF only |
| `.withExif(exif)` | Set EXIF (replaces input). Object keyed by IFD |
| `.withExifMerge(exif)` | Merge with existing EXIF |
| `.keepIccProfile()` | Preserve ICC profile |
| `.withIccProfile(icc, [options])` | Set ICC: path or `'srgb'`, `'p3'`, `'cmyk'` |
| `.keepXmp()` | Preserve XMP |
| `.withXmp(xmp)` | Set XMP (XML string) |
| `.withMetadata([options])` | Preserve most metadata. Options: `orientation`, `density` |
---
## Timeout
```js
.timeout({ seconds: 30 })
```
Abort processing after N seconds. `0` = no timeout (default).
---
## Utility (Static)
| Property/Method | Description |
|-----------------|-------------|
| `sharp.format` | Object with available input/output format booleans |
| `sharp.versions` | Version info for sharp, libvips, dependencies |
| `sharp.interpolators` | Enum: `nearest`, `bilinear`, `bicubic`, `lbb`, `nohalo`, `vsqbs` |
| `sharp.cache([options])` | Get/set cache: `{ memory: 50, files: 20, items: 100 }` |
| `sharp.concurrency([n])` | Get/set thread count (default: CPU cores) |
| `sharp.counters()` | Returns `{ queue, process }` |
| `sharp.simd([bool])` | Enable/disable SIMD (default `true`) |
| `sharp.block({ operation })` | Block specific operations |
| `sharp.unblock({ operation })` | Unblock operations |
---
## Common Recipes
### Resize and convert format
```js
await sharp('input.png')
.resize(800, 600)
.webp({ quality: 80 })
.toFile('output.webp');
```
### Resize to fit within bounds (no upscale)
```js
await sharp('input.jpg')
.resize(1200, 800, { fit: 'inside', withoutEnlargement: true })
.toFile('output.jpg');
```
### Create thumbnail (cover crop)
```js
await sharp('input.jpg')
.resize(250, 250, { fit: 'cover', position: 'attention' })
.toFile('thumb.jpg');
```
### Crop region
```js
await sharp('input.jpg')
.extract({ left: 100, top: 50, width: 400, height: 300 })
.toFile('cropped.jpg');
```
### Add watermark overlay
```js
await sharp('photo.jpg')
.composite([{ input: 'watermark.png', gravity: 'southeast' }])
.toFile('watermarked.jpg');
```
### Composite text overlay
```js
await sharp('photo.jpg')
.composite([{
input: { text: { text: 'Hello World', font: 'sans', dpi: 200, rgba: true } },
gravity: 'south'
}])
.toFile('annotated.jpg');
```
### Convert to greyscale
```js
await sharp('input.jpg')
.greyscale()
.toFile('grey.jpg');
```
### Blur
```js
await sharp('input.jpg')
.blur({ sigma: 5 })
.toFile('blurred.jpg');
```
### Rotate
```js
await sharp('input.jpg')
.rotate(90)
.toFile('rotated.jpg');
```
### Auto-orient from EXIF
```js
await sharp('input.jpg')
.autoOrient()
.toFile('oriented.jpg');
```
### Extend with padding
```js
await sharp('input.png')
.extend({ top: 20, bottom: 20, left: 20, right: 20, background: '#ffffff' })
.toFile('padded.png');
```
### Auto-trim whitespace
```js
await sharp('input.png')
.trim({ threshold: 10 })
.toFile('trimmed.png');
```
### Optimize JPEG for web
```js
await sharp('input.jpg')
.resize(1920, null, { withoutEnlargement: true })
.jpeg({ quality: 75, mozjpeg: true, progressive: true })
.toFile('optimized.jpg');
```
### Generate AVIF from JPEG
```js
await sharp('input.jpg')
.avif({ quality: 50, effort: 4 })
.toFile('output.avif');
```
### Extract channel
```js
await sharp('input.png')
.extractChannel('red')
.toFile('red-channel.png');
```
### Get image metadata
```js
const { width, height, format, space } = await sharp('input.jpg').metadata();
```
### Buffer round-trip
```js
const buffer = await sharp('input.jpg')
.resize(300)
.png()
.toBuffer();
```
### Create solid color image
```js
await sharp({ create: { width: 100, height: 100, channels: 4, background: '#ff6600' } })
.png()
.toFile('orange.png');
```
### Join images into grid
```js
await sharp(['a.png', 'b.png', 'c.png', 'd.png'], { join: { across: 2 } })
.toFile('grid.png');
```
### Preserve metadata
```js
await sharp('input.jpg')
.resize(800)
.keepMetadata()
.toFile('output.jpg');
```
### Animated GIF resize
```js
await sharp('input.gif', { animated: true })
.resize(200)
.gif()
.toFile('small.gif');
```
### Multiple outputs from one input
```js
const pipeline = sharp('input.jpg');
await Promise.all([
pipeline.clone().resize(200).toFile('thumb.jpg'),
pipeline.clone().resize(800).toFile('medium.jpg'),
pipeline.clone().resize(1600).toFile('large.jpg'),
]);
```
---
## Source
- Repository: https://github.com/lovell/sharp
- Documentation: https://sharp.pixelplumbing.com
- API — Constructor: https://sharp.pixelplumbing.com/api-constructor
- API — Input: https://sharp.pixelplumbing.com/api-input
- API — Resize: https://sharp.pixelplumbing.com/api-resize
- API — Operations: https://sharp.pixelplumbing.com/api-operation
- API — Colour: https://sharp.pixelplumbing.com/api-colour
- API — Channel: https://sharp.pixelplumbing.com/api-channel
- API — Composite: https://sharp.pixelplumbing.com/api-composite
- API — Output: https://sharp.pixelplumbing.com/api-output
- API — Utility: https://sharp.pixelplumbing.com/api-utility