Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .claude/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "browser-tests",
"runtimeExecutable": "bunx",
"runtimeArgs": ["vite", "test/browser", "--port", "5183", "--strictPort"],
"port": 5183
},
{
"name": "example",
"runtimeExecutable": "bun",
"runtimeArgs": ["run", "dev:example"],
"port": 5173
}
]
}
21 changes: 21 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest

- run: bun install --frozen-lockfile
- run: bunx prettier --check "src/**/*.ts" "test/**/*.ts"
- run: bun run typecheck
- run: bun test
- run: bun run build
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ bun.lockb
*~

# OS
Thumbs.db
Thumbs.db
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Rodrigo

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
210 changes: 107 additions & 103 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,151 +1,155 @@
# sharp-gpu

A GPU-accelerated image processing library for the web, powered by WebGL and REGL. Perform real-time image transformations with blur, color adjustments, LUTs, and more.
[sharp](https://www.npmjs.com/package/sharp)-style image processing for the browser, running on the GPU through WebGL.

> ⚠️ **Beta software**: SharpGPU is actively under development and not yet ready for production workloads.
Same chainable pipeline you would write in Node, executed as fragment shaders: every operation is one full-screen pass, and nothing touches the CPU until you ask for the result.

## Features
> ⚠️ **Beta**: the API is still moving. Pin an exact version.

- 🚀 GPU-accelerated image processing using WebGL
- 🎨 Color adjustments (brightness, saturation, hue, lightness, tint)
- 🌫️ Gaussian blur with variable radius
- 🎭 LUT (Look-Up Table) support for custom color grading
- ⛓️ Chainable API for complex image pipelines
- 📦 Built with TypeScript for full type safety
```bash
bun add sharp-gpu
```

## Roadmap
## Usage

### Core Pipeline & I/O
```typescript
import { SharpGPU } from "sharp-gpu";

- [ ] Multi-source inputs (`Buffer`, `ReadableStream`, filesystem paths)
- [x] Load from image URL/path via `SharpGPU.from`
- [ ] Metadata inspection (`metadata()`)
- [ ] File/`Buffer` outputs (`toFile`, `toBuffer`)
- [x] Canvas output (`toCanvas`)
- [x] Browser blob export (`toBlob`)
const image = await SharpGPU.from("/photo.jpg");

### Geometry & Resizing
await image
.resize({ width: 800, height: 600, fit: "cover" })
.modulate({ brightness: 1.1, saturation: 1.3 })
.sharpen()
.toCanvas(document.querySelector("canvas"));
```

- [x] Basic resize by width/height with aspect preservation
- [ ] Resize fit/cover/fill strategies (e.g. `fit: cover`, `background`)
- [ ] Crop/extract, extend/pad, trim
- [ ] Rotate, flip, flop
- [ ] Affine/projective transforms
Operations are queued, not executed. The pipeline runs once, on the GPU, when
you call an output method — so `clone()` is cheap and re-rendering with new
parameters costs a single pass per operation.

### Color & Tone
```typescript
const base = await SharpGPU.from(file); // a Blob from an <input type="file">

- [x] Modulate brightness, saturation, hue, lightness
- [x] Tint
- [x] Grayscale
- [x] LUT-based grading (`lut()`)
- [x] Linear, gamma, negate
- [ ] Histogram-based normalize
- [ ] Thresholding
- [ ] Channel operations (remove/ensure alpha, join/extract channel)
const thumbnail = await base.clone().resize({ width: 200 }).toBlob("image/webp");
const preview = await base.clone().blur(8).toImageData();
```

### Effects & Convolution
## Differences from sharp

- [x] Gaussian blur (single-pass separable)
- [ ] Median blur
- [ ] Sharpen
- [ ] Custom convolution kernels
- [ ] Composite/overlay operations
| | sharp | sharp-gpu |
| ---------- | ---------------------------- | ------------------------------ |
| Runs on | Node, libvips | Browser, WebGL2 |
| Colors | 0–255 integers | 0–1 floats, `[r, g, b, a]` |
| Output | files and buffers | canvas, `Blob`, `ImageData` |
| Encoding | jpeg/png/webp/avif options | whatever `canvas.toBlob` takes |

### Pipeline Composition
Format encoding, EXIF, ICC profiles and multi-page images are out of scope:
they are decoder concerns, and the browser already owns them.

- [x] Chainable operation builder
- [ ] Stream-based piping (`pipeline()`, `clone()` semantics for concurrency)
- [ ] Queued/concurrent job control (`queue()`, `limitInputPixels`)
## API

## Installation
### Input

```bash
bun add sharp-gpu
```typescript
SharpGPU.from(input); // URL string, Blob, File, ImageData, ImageBitmap,
// HTMLImageElement, HTMLCanvasElement, OffscreenCanvas
image.metadata(); // { width, height } of the loaded image
image.clone(); // fork the pipeline, sharing the GPU context
image.destroy(); // release GPU resources
```

## Usage
### Geometry

```typescript
import { SharpGPU } from "sharp-gpu";
.resize({ width, height, fit, position, background })
.extract({ left, top, width, height })
.extend({ top, bottom, left, right, background })
.rotate(degrees, background?) // clockwise, canvas grows to the bounding box
.flip() // mirror vertically
.flop() // mirror horizontally
```

// Load an image and apply transformations
const canvas = document.getElementById("output") as HTMLCanvasElement;
`fit` is `cover` (default), `contain`, `fill`, `inside` or `outside`.
`position` is `center` (default), `top`, `right`, `bottom`, `left` or a corner
such as `top-left`. Passing only `width` or only `height` preserves the aspect
ratio and ignores `fit`.

const image = await SharpGPU.from("/path/to/image.png");
### Color and tone

await image
.blur(10)
.modulate({
brightness: 1.2,
saturation: 1.5,
hue: 30,
})
.toCanvas(canvas);

// Or export as a blob
const blob = await image.blur(5).grayscale().toBlob("image/png");
```typescript
.modulate({ brightness, saturation, hue, lightness })
.linear(multiply, add?) // number | [r, g, b] | [r, g, b, a]
.gamma(gamma?, gammaOut?) // defaults to 2.2 and 1.0
.negate()
.grayscale() // BT.709 luma, alias: greyscale()
.tint([r, g, b])
.threshold(value?, { grayscale })
.normalize() // stretch luminance, alias: normalise()
.recomb([[r], [g], [b]]) // 3x3 channel matrix
.lut(values | ((x) => number)) // tone curve over luminance
```

### Available Operations
### Effects

```typescript
// Blur with radius
.blur(radius: number)
.blur(radius) // separable gaussian
.sharpen(amount?)
.median() // 3x3, removes salt-and-pepper noise
.convolve({ width, height, kernel, scale?, offset? }) // up to 7x7
```

// Color modulation
.modulate({
brightness?: number, // 0-2 (default: 1)
saturation?: number, // 0-2 (default: 1)
hue?: number, // 0-360 degrees (default: 0)
lightness?: number, // -1 to 1 (default: 0)
tint?: [r, g, b] // 0-1 normalized RGB (default: [1, 1, 1])
})
### Channels and composition

// Linear adjustment (scale + offset per channel, optional alpha)
.linear(multiply: number | [r, g, b] | [r, g, b, a], add?: number | [r, g, b] | [r, g, b, a])
```typescript
.extractChannel("red" | "green" | "blue" | "alpha")
.removeAlpha()
.flatten(background?) // composite over a color, drop transparency
.composite([{ input, blend?, gravity?, top?, left? }])
```

// Gamma correction (default gamma=2.2, gammaOut=1.0)
.gamma(gamma?: number | [r, g, b], gammaOut?: number | [r, g, b])
`blend` is `over` (default), `multiply`, `screen`, `overlay`, `darken`,
`lighten`, `difference` or `exclusion`. `input` accepts anything
`SharpGPU.from` accepts.

// Channel inversion
.negate()
### Output

// Grayscale (shorthand for saturation: 0)
.grayscale()
```typescript
await image.toCanvas(canvas);
await image.toBlob(type?, quality?);
await image.toImageData();
```

// Tint (helper around linear scaling)
.tint([r, g, b])
## Notes

// Look-Up Table (custom color grading)
.lut((x: number) => number) // Function mapping
.lut([...values]) // Array of values
- Colors are `[r, g, b, a]` with components in `0..1`, not `0..255`.
- `normalize()` and `toImageData()` read pixels back from the GPU, which stalls
the pipeline. Everything else stays on the GPU.
- `blur` caps its radius at 32 px per pass; chain calls for stronger blurs.
- `metadata()` describes the loaded image, like sharp — queued operations do
not change it.

// Resize
.resize({ width: number, height: number })
```
## Roadmap

- [ ] Trim uniform borders (`trim`)
- [ ] Histogram and image statistics (`stats`)
- [ ] Affine and perspective transforms
- [ ] Lanczos/mitchell resampling kernels
- [ ] WebGPU backend

## Development

```bash
# Install dependencies
bun install

# Build the library
bun run build

# Watch mode for development
bun run dev

# Type checking
bun test # pure logic
bun run test:browser # GPU tests, open the printed URL
bun run typecheck

# Run example
cd example && bun install && bun run dev
bun run build
bun run dev:example # interactive playground
```

## Example

Check out the [`example/`](./example) folder for a working interactive demo with Tweakpane controls.
`bun test` covers the plan math (sizes, transforms, kernels, curves).
`test/browser` renders each operation on a real GPU and asserts on pixels.

## License

Expand Down
Loading
Loading