Skip to content

Sharp-equivalent API on the GPU - #1

Open
roprgm wants to merge 7 commits into
mainfrom
feat/sharp-api
Open

Sharp-equivalent API on the GPU#1
roprgm wants to merge 7 commits into
mainfrom
feat/sharp-api

Conversation

@roprgm

@roprgm roprgm commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Brings the public API up to parity with the parts of sharp that make sense on a GPU, and fixes the bugs found along the way.

Fixes

  • Framebuffer leak: render() allocated two framebuffers on every call and never freed them, so each slider drag in the example leaked GPU memory. The renderer now keeps one reusable ping-pong pair.
  • Programs never freed: dispose() iterated Object.values() over a WeakMap, which is always empty.
  • Textures never freed: input, overlay and LUT textures outlived destroy(). The renderer now owns every texture it hands out.
  • img.decode() hangs for images that are never attached to the document. Inputs are decoded with createImageBitmap, which also fixed the orientation flip WebGL introduces by ignoring UNPACK_FLIP_Y_WEBGL for ImageBitmap uploads.
  • Washed-out transparency: the canvas requested premultiplied alpha while the shaders produce straight alpha.
  • Fractional texture sizes from aspect-ratio resizes are now rounded and clamped to at least 1px.
  • toBlob rejected with undefined instead of an Error.
  • Uniforms were written by guessing from JS array length, so a float[9] and a mat3 were indistinguishable. They are now dispatched by the type declared in the shader.

New API

Geometryextract, extend, flip, flop, rotate (any angle, canvas grows to the bounding box), and resize with fit (cover/contain/fill/inside/outside), position and background.

Effectsconvolve (up to 7x7), sharpen, median.

Colorthreshold, recomb, normalize, extractChannel, removeAlpha, flatten. grayscale now uses BT.709 luma to match sharp instead of zeroing HSL saturation.

Compositioncomposite with PDF blend modes (over, multiply, screen, overlay, darken, lighten, difference, exclusion) placed by gravity or offset.

I/Ofrom() accepts Blob, File, ImageData, ImageBitmap, canvas and <img> in addition to URLs; new metadata() and toImageData().

Structure

Every geometry operation is now a pure plan — a target size plus an affine map from target uv back to source uv — rendered by one shader, so resize, crop, pad, mirror and rotate share a single code path and are unit-testable without a GPU. gl.ts (497 lines, four concepts) is split into texture / framebuffer / program / renderer. The unused buffer, blending, attribute and indexed-draw abstractions are gone, since every draw is a full-screen quad.

Tests

  • bun test — 49 tests over the plan math: sizes, transforms, kernels, curves, luminance ranges, placement.
  • test/browser — 49 tests rendering each operation on a real GPU and asserting on pixels, including a check that destroy() frees every allocated texture.

Both suites pass, along with typecheck, build and the example app (verified interactively).

Also

MIT LICENSE file (the package claimed MIT with no license text), CI running format/typecheck/test/build, README rewritten around sharp parity, and .DS_Store untracked.

roprgm and others added 6 commits July 29, 2026 04:29
- Reuse a single framebuffer pair instead of allocating two per render
- Dispose cached programs (WeakMap iteration was a no-op)
- Decode inputs via createImageBitmap: img.decode() never resolves for
  detached images, and WebGL ignores UNPACK_FLIP_Y_WEBGL for ImageBitmap
- Accept Blob/ImageData/ImageBitmap/canvas/img as input, not just URLs
- Round derived resize dimensions and keep them >= 1
- Reject toBlob with an Error instead of undefined
- Collapse the operation hierarchy into ShaderOperation + program consts
- Drop unused GL abstractions (buffers, blending, attributes, elements)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add extract, extend, flip, flop and rotate, plus resize fit modes
(cover/contain/fill/inside/outside) with gravity and background.

Every geometry operation is a pure plan — a target size plus an affine
map from target uv back to source uv — rendered by a single shader.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add convolve, sharpen, median, threshold, recomb, extractChannel,
flatten, removeAlpha and normalize. grayscale now uses BT.709 luma to
match sharp instead of zeroing HSL saturation.

Uniforms are written according to the type declared in the shader, so a
float[49] kernel is no longer mistaken for a matrix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add composite() with PDF blend modes and gravity/offset placement,
metadata() for the loaded image, and toImageData() for pixel access.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Break gl.ts into texture/framebuffer/program/renderer so each file
answers one question, and flatten resizePlan into named helpers.

Rewrite the README around sharp parity, add the MIT license file and a
CI workflow running format, typecheck, tests and build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The renderer now owns every texture it hands out, so destroy() frees
them instead of leaking the ones held by operations.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 29, 2026

Copy link
Copy Markdown

Deploying sharp-gpu with  Cloudflare Pages  Cloudflare Pages

Latest commit: aeeca9a
Status: ✅  Deploy successful!
Preview URL: https://cac6c132.sharp-gpu.pages.dev
Branch Preview URL: https://feat-sharp-api.sharp-gpu.pages.dev

View logs

Fit, Gravity, BlendMode, ImageInput and the parameter types were only
reachable through the class, so consumers could not import them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant