From a9870adfa3ab252c1b7be73a0b4dfef761e21511 Mon Sep 17 00:00:00 2001 From: calixteman Date: Sun, 8 Feb 2026 18:33:13 +0100 Subject: [PATCH] Avoid branching in convertBlackAndWhiteToRGBA The function is now almost 8x faster than before. And make the code in the file slightly more readable. --- src/shared/image_utils.js | 65 ++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/src/shared/image_utils.js b/src/shared/image_utils.js index b88342ccb..abb3b0849 100644 --- a/src/shared/image_utils.js +++ b/src/shared/image_utils.js @@ -41,30 +41,36 @@ function convertBlackAndWhiteToRGBA({ : [black, nonBlackColor]; const widthInSource = width >> 3; const widthRemainder = width & 7; + const xorMask = zeroMapping ^ oneMapping; const srcLength = src.length; dest = new Uint32Array(dest.buffer); let destPos = 0; - for (let i = 0; i < height; i++) { - for (const max = srcPos + widthInSource; srcPos < max; srcPos++) { - const elem = srcPos < srcLength ? src[srcPos] : 255; - dest[destPos++] = elem & 0b10000000 ? oneMapping : zeroMapping; - dest[destPos++] = elem & 0b1000000 ? oneMapping : zeroMapping; - dest[destPos++] = elem & 0b100000 ? oneMapping : zeroMapping; - dest[destPos++] = elem & 0b10000 ? oneMapping : zeroMapping; - dest[destPos++] = elem & 0b1000 ? oneMapping : zeroMapping; - dest[destPos++] = elem & 0b100 ? oneMapping : zeroMapping; - dest[destPos++] = elem & 0b10 ? oneMapping : zeroMapping; - dest[destPos++] = elem & 0b1 ? oneMapping : zeroMapping; + for (let i = 0; i < height; ++i) { + for ( + const max = srcPos + widthInSource; + srcPos < max; + ++srcPos, destPos += 8 + ) { + const elem = src[srcPos]; + dest[destPos] = zeroMapping ^ (-((elem >> 7) & 1) & xorMask); + dest[destPos + 1] = zeroMapping ^ (-((elem >> 6) & 1) & xorMask); + dest[destPos + 2] = zeroMapping ^ (-((elem >> 5) & 1) & xorMask); + dest[destPos + 3] = zeroMapping ^ (-((elem >> 4) & 1) & xorMask); + dest[destPos + 4] = zeroMapping ^ (-((elem >> 3) & 1) & xorMask); + dest[destPos + 5] = zeroMapping ^ (-((elem >> 2) & 1) & xorMask); + dest[destPos + 6] = zeroMapping ^ (-((elem >> 1) & 1) & xorMask); + dest[destPos + 7] = zeroMapping ^ (-(elem & 1) & xorMask); } if (widthRemainder === 0) { continue; } const elem = srcPos < srcLength ? src[srcPos++] : 255; - for (let j = 0; j < widthRemainder; j++) { - dest[destPos++] = elem & (1 << (7 - j)) ? oneMapping : zeroMapping; + for (let j = 0; j < widthRemainder; ++j, ++destPos) { + dest[destPos] = zeroMapping ^ (-((elem >> (7 - j)) & 1) & xorMask); } } + return { srcPos, destPos }; } @@ -80,40 +86,41 @@ function convertRGBToRGBA({ const len = width * height * 3; const len32 = len >> 2; const src32 = new Uint32Array(src.buffer, srcPos, len32); + const alphaMask = FeatureTest.isLittleEndian ? 0xff000000 : 0xff; if (FeatureTest.isLittleEndian) { // It's a way faster to do the shuffle manually instead of working // component by component with some Uint8 arrays. for (; i < len32 - 2; i += 3, destPos += 4) { - const s1 = src32[i]; // R2B1G1R1 - const s2 = src32[i + 1]; // G3R3B2G2 - const s3 = src32[i + 2]; // B4G4R4B3 + const s1 = src32[i], // R2B1G1R1 + s2 = src32[i + 1], // G3R3B2G2 + s3 = src32[i + 2]; // B4G4R4B3 - dest[destPos] = s1 | 0xff000000; - dest[destPos + 1] = (s1 >>> 24) | (s2 << 8) | 0xff000000; - dest[destPos + 2] = (s2 >>> 16) | (s3 << 16) | 0xff000000; - dest[destPos + 3] = (s3 >>> 8) | 0xff000000; + dest[destPos] = s1 | alphaMask; + dest[destPos + 1] = (s1 >>> 24) | (s2 << 8) | alphaMask; + dest[destPos + 2] = (s2 >>> 16) | (s3 << 16) | alphaMask; + dest[destPos + 3] = (s3 >>> 8) | alphaMask; } for (let j = i * 4, jj = srcPos + len; j < jj; j += 3) { dest[destPos++] = - src[j] | (src[j + 1] << 8) | (src[j + 2] << 16) | 0xff000000; + src[j] | (src[j + 1] << 8) | (src[j + 2] << 16) | alphaMask; } } else { for (; i < len32 - 2; i += 3, destPos += 4) { - const s1 = src32[i]; // R1G1B1R2 - const s2 = src32[i + 1]; // G2B2R3G3 - const s3 = src32[i + 2]; // B3R4G4B4 + const s1 = src32[i], // R1G1B1R2 + s2 = src32[i + 1], // G2B2R3G3 + s3 = src32[i + 2]; // B3R4G4B4 - dest[destPos] = s1 | 0xff; - dest[destPos + 1] = (s1 << 24) | (s2 >>> 8) | 0xff; - dest[destPos + 2] = (s2 << 16) | (s3 >>> 16) | 0xff; - dest[destPos + 3] = (s3 << 8) | 0xff; + dest[destPos] = s1 | alphaMask; + dest[destPos + 1] = (s1 << 24) | (s2 >>> 8) | alphaMask; + dest[destPos + 2] = (s2 << 16) | (s3 >>> 16) | alphaMask; + dest[destPos + 3] = (s3 << 8) | alphaMask; } for (let j = i * 4, jj = srcPos + len; j < jj; j += 3) { dest[destPos++] = - (src[j] << 24) | (src[j + 1] << 16) | (src[j + 2] << 8) | 0xff; + (src[j] << 24) | (src[j + 1] << 16) | (src[j + 2] << 8) | alphaMask; } }