Skip to content

Commit 8b8da51

Browse files
matmenclaude
andcommitted
faster resizing
The horizontal pass reads the coefficients outside the pixel loop, the vertical pass sums each value over its rows in a variable (rounded to float32 after every row, as the Float32Array did) instead of reading and writing the output once per row, with the weights of up to 4 rows in variables. The results are bit-identical: checked against master on 1593 images (every mode, shrinking and enlarging). About 10% faster for halving a 1920×1080 photo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJgRK5qpwJEbHPuFv6kgAQ
1 parent 8fa8efd commit 8b8da51

2 files changed

Lines changed: 42 additions & 27 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
computed without branches, 8-bit RGB images are converted without per-sample function calls.
3535
- Lossless WebP encoding is about 15% faster, with identical files: backward reference candidates that can't be
3636
longer are skipped, the per-channel predictor helpers are unrolled.
37+
- Resizing (bilinear, bicubic and area) is about 10% faster, with identical results.
3738

3839
### Deprecated
3940

‎src/util/resample.ts‎

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -69,34 +69,34 @@ function coefficients(inSize: number, outSize: number, filter: Filter): Coeffici
6969
export function resample(src: Uint8ClampedArray, width: number, height: number, outWidth: number, outHeight: number, filter: Filter, out: Uint8ClampedArray): void {
7070
// premultiplied float rows, scaled horizontally (premultiplying on the fly)
7171
let plane = new Float32Array(outWidth * height * 4);
72-
const horizontal = outWidth !== width ? coefficients(width, outWidth, filter) : null;
73-
74-
for (let y = 0; y < height; y++) {
75-
const row = y * width * 4;
76-
for (let x = 0; x < outWidth; x++) {
77-
let r = 0, g = 0, b = 0, a = 0;
78-
if (horizontal) {
79-
const { start, count, weights, size } = horizontal;
80-
for (let k = 0, s = row + start[x] * 4; k < count[x]; k++, s += 4) {
81-
const wa = weights[x * size + k] * src[s + 3];
72+
73+
if (outWidth !== width) {
74+
const { start, count, weights, size } = coefficients(width, outWidth, filter);
75+
for (let y = 0, o = 0; y < height; y++) {
76+
const row = y * width * 4;
77+
for (let x = 0; x < outWidth; x++, o += 4) {
78+
let r = 0, g = 0, b = 0, a = 0;
79+
for (let k = 0, s = row + start[x] * 4, w = x * size, n = count[x]; k < n; k++, s += 4) {
80+
const wa = weights[w + k] * src[s + 3];
8281
r += src[s] * wa;
8382
g += src[s + 1] * wa;
8483
b += src[s + 2] * wa;
8584
a += wa;
8685
}
87-
} else {
88-
const s = row + x * 4;
89-
a = src[s + 3];
90-
r = src[s] * a;
91-
g = src[s + 1] * a;
92-
b = src[s + 2] * a;
93-
}
9486

95-
const o = (y * outWidth + x) * 4;
96-
plane[o] = r / 255;
97-
plane[o + 1] = g / 255;
98-
plane[o + 2] = b / 255;
99-
plane[o + 3] = a;
87+
plane[o] = r / 255;
88+
plane[o + 1] = g / 255;
89+
plane[o + 2] = b / 255;
90+
plane[o + 3] = a;
91+
}
92+
}
93+
} else {
94+
for (let s = 0; s < plane.length; s += 4) {
95+
const a = src[s + 3];
96+
plane[s] = src[s] * a / 255;
97+
plane[s + 1] = src[s + 1] * a / 255;
98+
plane[s + 2] = src[s + 2] * a / 255;
99+
plane[s + 3] = a;
100100
}
101101
}
102102

@@ -105,12 +105,26 @@ export function resample(src: Uint8ClampedArray, width: number, height: number,
105105
const next = new Float32Array(outWidth * outHeight * 4);
106106
const stride = outWidth * 4;
107107

108+
// every sum is rounded to float32 after each row it adds, as accumulating in a Float32Array would. Up to 4 rows
109+
// (bilinear and bicubic, from half the size up) are added with the weights in variables, more in a loop
108110
for (let y = 0; y < outHeight; y++) {
109-
const o = y * stride;
110-
for (let k = 0; k < count[y]; k++) {
111-
const w = weights[y * size + k];
112-
const s = (start[y] + k) * stride;
113-
for (let x = 0; x < stride; x++) next[o + x] += plane[s + x] * w;
111+
const o = y * stride, n = count[y], w = y * size;
112+
const s0 = start[y] * stride, s1 = s0 + stride, s2 = s1 + stride, s3 = s2 + stride;
113+
const w0 = weights[w], w1 = weights[w + 1], w2 = weights[w + 2], w3 = weights[w + 3];
114+
if (n === 1) for (let x = 0; x < stride; x++) next[o + x] = 0 + plane[s0 + x] * w0;
115+
else if (n === 2) for (let x = 0; x < stride; x++) next[o + x] = Math.fround(0 + plane[s0 + x] * w0) + plane[s1 + x] * w1;
116+
else if (n === 3) {
117+
for (let x = 0; x < stride; x++)
118+
next[o + x] = Math.fround(Math.fround(0 + plane[s0 + x] * w0) + plane[s1 + x] * w1) + plane[s2 + x] * w2;
119+
} else if (n === 4) {
120+
for (let x = 0; x < stride; x++)
121+
next[o + x] = Math.fround(Math.fround(Math.fround(0 + plane[s0 + x] * w0) + plane[s1 + x] * w1) + plane[s2 + x] * w2) + plane[s3 + x] * w3;
122+
} else {
123+
for (let x = 0; x < stride; x++) {
124+
let sum = 0;
125+
for (let k = 0, s = s0 + x; k < n; k++, s += stride) sum = Math.fround(sum + plane[s] * weights[w + k]);
126+
next[o + x] = sum;
127+
}
114128
}
115129
}
116130

0 commit comments

Comments
 (0)