sharp-web
v1.0.0
Published
Browser-based image processing library with a sharp-compatible fluent API, powered by the HTML Canvas API
Maintainers
Readme
sharp-web
Browser-based image processing library with a sharp-compatible fluent API, powered by the HTML Canvas API.
Process images entirely in the browser — no server round-trip, no WebAssembly binary, zero production dependencies.
Features
- Sharp-compatible API — familiar fluent interface for anyone coming from Node.js sharp
- Zero dependencies — uses only native browser APIs (Canvas, Image, Blob)
- Lazy pipeline — no pixel work happens until an output method is awaited
- Multiple input types —
Blob,File, URL string, data URL,Uint8Array,ArrayBuffer,ImageData,HTMLImageElement,HTMLCanvasElement, raw pixel buffers - Multiple output formats — PNG, JPEG, WebP, raw RGBA pixels
- Full TypeScript support — strict types and exported interfaces
- Lightweight — minimal footprint with no external image processing libraries
Installation
npm install sharp-webQuick Start
import sharp from "sharp-web";
// Resize an image from a URL
const buffer = await sharp("/images/photo.jpg")
.resize(300, 200)
.jpeg()
.toBuffer();
// Process a file from <input type="file">
const file = inputElement.files[0];
const blob = await sharp(file).resize(800, 600).png().toBlob();Usage
Importing
import sharp from "sharp-web";
// Or import the class directly
import { SharpWeb } from "sharp-web";Input Types
sharp-web accepts a variety of image sources:
// From a URL or data URL
sharp("/images/photo.jpg");
sharp("data:image/png;base64,...");
// From a Blob or File
sharp(file);
sharp(blob);
// From an HTMLImageElement
sharp(document.querySelector("img"));
// From an HTMLCanvasElement
sharp(document.querySelector("canvas"));
// From raw pixel data (Uint8Array or ArrayBuffer)
sharp(rgbaBytes, {
raw: { width: 100, height: 100, channels: 4 },
});
// From ImageData
sharp(ctx.getImageData(0, 0, width, height));Resizing
// Resize to 300px wide, height auto-calculated to preserve aspect ratio
await sharp(input).resize(300).toBuffer();
// Resize to specific dimensions
await sharp(input).resize(300, 200).toBuffer();
// Resize with fit strategies
await sharp(input).resize(300, 200, { fit: "cover" }).toBuffer(); // crop to fill (default)
await sharp(input).resize(300, 200, { fit: "contain" }).toBuffer(); // fit within, may letterbox
await sharp(input).resize(300, 200, { fit: "fill" }).toBuffer(); // stretch to fill exactly
await sharp(input).resize(300, 200, { fit: "inside" }).toBuffer(); // scale down to fit inside
await sharp(input).resize(300, 200, { fit: "outside" }).toBuffer(); // scale up to coverCropping / Extraction
// Extract a 100x100 region starting at (10, 10)
await sharp(input).extract(10, 10, 100, 100).toBuffer();
// Or use an options object
await sharp(input)
.extract({ left: 10, top: 10, width: 100, height: 100 })
.toBuffer();Rotation
// Rotate 90 degrees clockwise
await sharp(input).rotate(90).toBuffer();
// Arbitrary angle
await sharp(input).rotate(45).toBuffer();Flipping
// Flip vertically (around horizontal axis)
await sharp(input).flip().toBuffer();
// Flop horizontally (mirror, around vertical axis)
await sharp(input).flop().toBuffer();Compositing
Overlay one or more images on top of the base image:
await sharp(baseImage)
.composite([
{ input: watermark, left: 10, top: 10, opacity: 0.5 },
{ input: overlay, left: 50, top: 50, blend: "multiply" },
])
.toBuffer();Supported blend modes: "source-over" (default), "multiply", "screen", "overlay", "darken", "lighten".
Chaining Operations
All transformation methods return this, so operations can be freely chained:
const result = await sharp(file)
.resize(800, 600, { fit: "inside" })
.rotate(90)
.extract({ left: 0, top: 0, width: 400, height: 300 })
.flip()
.jpeg()
.toBuffer();Output Formats
// PNG (default)
await sharp(input).png().toBuffer();
// JPEG
await sharp(input).jpeg().toBuffer();
// WebP
await sharp(input).webp().toBuffer();
// Raw RGBA pixel bytes
await sharp(input).raw().toBuffer();Output Methods
// As Uint8Array
const buffer = await sharp(input).toBuffer();
// As Blob
const blob = await sharp(input).toBlob();
// As data URL (e.g. for setting img.src)
const dataUrl = await sharp(input).toDataURL();
document.querySelector("img").src = dataUrl;Metadata
Retrieve image metadata without processing:
const meta = await sharp(input).metadata();
console.log(meta.width); // number
console.log(meta.height); // number
console.log(meta.format); // "png" | "jpeg" | "webp" | "raw" | ...
console.log(meta.channels); // 3 | 4
console.log(meta.hasAlpha); // boolean
console.log(meta.space); // "srgb"
console.log(meta.size); // byte size (when available)Cloning
Create independent copies of a pipeline to produce multiple outputs from the same source:
const base = sharp(file).resize(300);
const [png, jpeg] = await Promise.all([
base.clone().png().toBuffer(),
base.clone().jpeg().toBuffer(),
]);API Reference
sharp(input, options?)
Creates a new processing pipeline.
| Parameter | Type | Description |
| --------- | ------------------- | -------------------------------------------------------------------------- |
| input | SharpInput | Image source (see Input Types) |
| options | SharpInputOptions | Optional. Use { raw: { width, height, channels } } for raw pixel buffers |
Returns a SharpWeb instance.
Transformation Methods
All return this for chaining.
| Method | Description |
| ----------------------------------- | ----------------------------------------------------------------------------------------- |
| resize(width?, height?, options?) | Resize image. Options: { fit: "cover" \| "contain" \| "fill" \| "inside" \| "outside" } |
| extract(left, top, width, height) | Crop a rectangular region |
| rotate(angle?) | Rotate clockwise by degrees |
| flip() | Flip vertically |
| flop() | Flip horizontally (mirror) |
| composite(layers) | Overlay images with positioning, blend modes, and opacity |
Format Methods
All return this for chaining.
| Method | Description |
| -------- | ---------------------------------- |
| png() | Set output format to PNG (default) |
| jpeg() | Set output format to JPEG |
| webp() | Set output format to WebP |
| raw() | Output raw RGBA pixel bytes |
Output Methods
| Method | Returns | Description |
| ------------- | --------------------- | --------------------------------------------- |
| toBuffer() | Promise<Uint8Array> | Encoded image bytes (or raw RGBA if .raw()) |
| toBlob() | Promise<Blob> | Encoded image as Blob |
| toDataURL() | Promise<string> | Base64-encoded data URL string |
| metadata() | Promise<Metadata> | Image metadata without processing |
| clone() | SharpWeb | Independent copy of the pipeline |
Exported Types
import type {
SharpInput, // Union of all accepted input types
SharpInputOptions, // Options for raw pixel inputs
RawInputOptions, // { width, height, channels }
Metadata, // Image metadata object
ExtractOptions, // { left, top, width, height }
ResizeOptions, // { fit? }
CompositeInput, // Layer descriptor for composite()
} from "sharp-web";Browser Compatibility
sharp-web relies on the Canvas API, which is supported in all modern browsers. Output format support depends on the browser:
| Format | Chrome | Firefox | Safari | Edge | | ------ | ------ | ------- | ------ | ---- | | PNG | Yes | Yes | Yes | Yes | | JPEG | Yes | Yes | Yes | Yes | | WebP | Yes | Yes | Yes* | Yes |
* Safari 16+ supports WebP encoding.
Differences from Node.js sharp
sharp-web aims for API compatibility with sharp but runs entirely in the browser using Canvas. Key differences:
- No native binaries — no libvips dependency; purely JavaScript + Canvas API
- Browser only — requires DOM APIs (
HTMLCanvasElement,HTMLImageElement,Blob, etc.) - Subset of operations — supports resize, crop, rotate, flip, flop, and composite. Operations like blur, sharpen, colour manipulation, and format-specific encoding options are not yet implemented.
- Canvas quality limits — image processing fidelity depends on the browser's Canvas implementation
Development
# Install dependencies
bun install
# Type-check
bun run test
# Lint
bun run lint
# Build (compile + generate docs)
bun run buildThis project uses:
- TypeScript with strict mode
- ESLint (v9 flat config) with typescript-eslint
- Prettier for formatting (via lint-staged on pre-commit)
- Conventional Commits enforced by commitlint
- Semantic Release for automated versioning and publishing
License
MIT © ByteLand Technology Limited
