Skip to content

Fast resizing by default: 8-bit SWAR in both passes - #71

Merged
matmen merged 2 commits into
masterfrom
fast-resize
Oct 16, 2026
Merged

matmen merged 2 commits into
masterfrom
fast-resize

Conversation

@matmen

@matmen matmen commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Bilinear and area resizing now use 8-bit SWAR in both passes: the intermediate image is rounded to 8 bits, so every multiplication handles two channels. This is the new default, and ResizeOptions.fast: false keeps the 3.2.1 resampling.

Opaque images

The second pass now also multiplies two channels at once (the first pass already did in 3.2.1).

Resampling a 1920×1080 photo:

no SWAR 1 pass (3.2.1) 2 passes (fast, default)
halving, bilinear 27.4 ms 14.8 ms 12.0 ms
halving, area 15.6 ms 8.9 ms 7.6 ms
doubling, bilinear 106.4 ms 79.9 ms 61.1 ms

Compared with fast: false across 1313 corpus images and 9 sizes, every result is within 1 per channel. 10–25% of the values differ by 1, versus about 1% between fast: false and an exact float reference.

Transparent images

Before, transparent images always took the precise premultiplied path. With fast, they are now premultiplied to 8 bits, the way browsers and Skia do it:

  • Both passes multiply red+blue and green+alpha as 16-bit lanes.
  • A reciprocal table replaces the division when unpremultiplying.

resize() of the same photo with alpha:

3.2.1 fast
halving 42.6 ms 24.1 ms
doubling 260.3 ms 118.5 ms

Compared with fast: false across 178 transparent corpus images and 14 size/mode combinations:

  • What shows once composited (channel × alpha) differs by at most 2.4 steps.
  • Alpha differs by at most 2.
  • Only 1–4% of the values differ at all.
  • Colors of nearly transparent pixels keep less precision: up to 12 steps at alpha 32. At that alpha, 8-bit premultiplication has only about 32 levels per channel. blur() has done the same since 3.2.1.

Limits

  • When a pass needs more than 4 source pixels per output pixel (shrinking below about a third), the rounding of the 8-bit weights adds up. Those resizes keep the precise path.
  • Bicubic resizing is unchanged: its negative weights don't work with packed lanes.

Changes

  • src/util/resample.ts:
    • resampleOpaqueFast and resampleTransparentFast.
    • Tap counts are padded to 2 or 4, so area resizing to a third runs the unrolled loops as well.
  • ResizeOptions.fast is passed through scale, resize, contain, fit, cover and Animation.resize.
  • tests/resize.js checks, on a photo crop and the same crop with an alpha gradient:
    • fast results stay within the limits above;
    • fast results do differ at half size;
    • bicubic results are identical.
  • CHANGELOG: 3.3.0 (new option).

🤖 Generated with Claude Code

https://claude.ai/code/session_01YJgRK5qpwJEbHPuFv6kgAQ

matmen and others added 2 commits October 16, 2026 17:02
Opaque images resized with bilinear or area resizing (down to about a third
of the size, and enlarging) round to 8 bits between the passes, so the second
pass shares its multiplications between two channels too. Halving a
1920x1080 photo: 15 -> 12 ms, doubling it: 80 -> 61 ms. Results stay within
1 of the previous ones (1313 corpus images). `ResizeOptions.fast: false`
keeps the previous resampling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJgRK5qpwJEbHPuFv6kgAQ
With `fast` (the default), transparent images are premultiplied to 8 bits,
like browsers do, and both passes multiply red+blue and green+alpha as
16-bit lanes; a reciprocal table unpremultiplies. Halving a 1920x1080 photo
with alpha: 43 -> 24 ms, doubling it: 260 -> 119 ms. Over 178 transparent
corpus images the composited result stays within about 2 steps of
`fast: false` (alpha within 2); colors of almost transparent pixels keep
less precision.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJgRK5qpwJEbHPuFv6kgAQ
@matmen
matmen merged commit 87af62c into master Oct 16, 2026
13 checks passed
@matmen
matmen deleted the fast-resize branch October 16, 2026 17:15
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