diff --git a/src/shared/image_utils.js b/src/shared/image_utils.js index ff8e124d4037f..d234190b5c811 100644 --- a/src/shared/image_utils.js +++ b/src/shared/image_utils.js @@ -14,6 +14,7 @@ */ import { FeatureTest, ImageKind } from "./util.js"; +import { MathClamp } from "./math_clamp.js"; function convertToRGBA(params) { switch (params.kind) { @@ -85,8 +86,12 @@ function convertRGBToRGBA({ let i = 0; const len = width * height * 3; const byteOffset = src.byteOffset + srcPos; - // Use 32-bit reads for aligned sources; otherwise convert pixel by pixel. - const len32 = byteOffset % 4 === 0 ? Math.floor(len / 4) : 0; + // Bound aligned 32-bit reads to the source view. + // The scalar loops treat missing bytes as zeros. + const len32 = + byteOffset % 4 === 0 + ? Math.floor(MathClamp(src.length - srcPos, 0, len) / 4) + : 0; const src32 = len32 > 0 ? new Uint32Array(src.buffer, byteOffset, len32) : null; const alphaMask = FeatureTest.isLittleEndian ? 0xff000000 : 0xff; diff --git a/test/unit/image_utils_spec.js b/test/unit/image_utils_spec.js index e008e17b48c7f..ca52061f65409 100644 --- a/test/unit/image_utils_spec.js +++ b/test/unit/image_utils_spec.js @@ -387,6 +387,51 @@ describe("image_utils", function () { expect(result.destPos).toEqual(5); expect(dest).toEqual(new Uint32Array([WHITE, BLACK, WHITE, BLACK, RED])); }); + + it("pads a truncated source with black for RGB_24BPP", function () { + const src = new Uint8Array(12).fill(255); + const dest = new Uint32Array(6).fill(WHITE); + const result = convertToRGBA({ + src, + dest, + width: 3, + height: 2, + kind: ImageKind.RGB_24BPP, + }); + expect(result).toEqual({ srcPos: 18, destPos: 6 }); + expect(dest).toEqual( + new Uint32Array([WHITE, WHITE, WHITE, WHITE, BLACK, BLACK]) + ); + }); + + it("doesn't read past the end of the source for RGB_24BPP", function () { + const src = new Uint8Array(24).fill(255).subarray(0, 3); + const dest = new Uint32Array(4); + const result = convertToRGBA({ + src, + dest, + width: 4, + height: 1, + kind: ImageKind.RGB_24BPP, + }); + expect(result).toEqual({ srcPos: 12, destPos: 4 }); + expect(dest).toEqual(new Uint32Array([WHITE, BLACK, BLACK, BLACK])); + }); + + it("handles a srcPos past the end of the source for RGB_24BPP", function () { + const src = new Uint8Array([255, 0, 0]); + const dest = new Uint32Array(2).fill(WHITE); + const result = convertToRGBA({ + src, + dest, + srcPos: 6, + width: 2, + height: 1, + kind: ImageKind.RGB_24BPP, + }); + expect(result).toEqual({ srcPos: 12, destPos: 2 }); + expect(dest).toEqual(new Uint32Array([BLACK, BLACK])); + }); }); describe("GlobalImageCache decoded images", function () {