Skip to content

Faster blur: box blurs on 32-bit words - #69

Merged
matmen merged 1 commit into
masterfrom
faster-blur
Oct 16, 2026
Merged

matmen merged 1 commit into
masterfrom
faster-blur

Conversation

@matmen

@matmen matmen commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator

blur() was the operation where the fastest pure-JS alternative beat us: stackblur took 127 ms on the benchmark photo (1920×1080), or 83 ms without alpha. We took 205 ms.

Changes

  • Pixels as 32-bit words: the three box blurs run directly on the pixels. Red and blue share a running sum as two 16-bit lanes, green and alpha share another, which fits a window of up to 257 pixels. The average is a multiplication by 65536 / size, and each box rounds to 8 bits.
  • Opaque images need no conversion at all.
  • Transparent images are premultiplied to 8 bits first, the same way browsers (Skia) blur.
  • Unchanged: radii below 2 keep the exact gaussian kernel on floats, and so does sharpen().

Results

photo, sigma 5 ms
before 205
this PR 86
stackblur (RGBA / RGB) 127 / 83

Checked on 1298 corpus images at sigma 2, 3.5, 5, 12 and 40:

  • Opaque: within 1 of before, or 2 for sigma 12 and 40.
  • With alpha: the premultiplied values, which are what compositing shows, are within 2 of before. The unpremultiplied color of almost transparent pixels (alpha below 10) can be further off, as in any 8-bit premultiplied blur.

tests/targets/effects.png is regenerated: its blur tile differs by at most 1.

The changelog entry goes under 3.2.1, the same section #68 uses, so whichever is merged second has a trivial conflict there.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YJgRK5qpwJEbHPuFv6kgAQ

blur() premultiplied the image into floats, ran the three box blurs on
four float channels (clamping the window to the image for every pixel)
and converted back: 205 ms for a 1920x1080 photo, slower than stackblur
(127 ms, 83 ms without alpha).

The box blurs now run on the pixels as 32-bit words: red and blue share a
sum as two 16-bit lanes, green and alpha another (a window of up to 257
pixels fits), and the average is a multiplication by 65536 / size. Each
box rounds to 8 bits. Opaque images need no conversion, transparent ones
are premultiplied in 8 bits first, as browsers (Skia) blur them. Radii
below 2 keep the exact gaussian kernel on floats, as does sharpen().

86 ms for the photo. Over 1298 corpus images at 5 radii: opaque results
within 1 of before (2 above radius 10); with alpha, the premultiplied
values (what compositing shows) within 2, the unpremultiplied color of
almost transparent pixels further off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJgRK5qpwJEbHPuFv6kgAQ
@matmen
matmen merged commit 3a9fb0d into master Oct 16, 2026
13 checks passed
@matmen
matmen deleted the faster-blur branch October 16, 2026 09:50
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