Skip to content

Commit

Permalink
Rescale the image data when they're really too large
Browse files Browse the repository at this point in the history
It fixes #17190.
  • Loading branch information
calixteman committed Nov 23, 2024
1 parent 1f6cc85 commit 0b5dfeb
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 6 deletions.
96 changes: 94 additions & 2 deletions src/core/image_resizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*/

import { FeatureTest, ImageKind, shadow, warn } from "../shared/util.js";
import { convertToRGBA } from "../shared/image_utils.js";

const MIN_IMAGE_DIM = 2048;

Expand Down Expand Up @@ -172,6 +173,18 @@ class ImageResizer {
}

async _createImage() {
const { _imgData: imgData } = this;
const { width, height } = imgData;

if (width * height * 4 > 2 ** 31 - 1) {
// The resulting RGBA image is too large.
// We just rescale the data.
const result = this._rescaleImageData();
if (result) {
return result;
}
}

const data = this._encodeBMP();
let decoder, imagePromise;

Expand Down Expand Up @@ -206,8 +219,6 @@ class ImageResizer {
}

const { MAX_AREA, MAX_DIM } = ImageResizer;
const { _imgData: imgData } = this;
const { width, height } = imgData;
const minFactor = Math.max(
width / MAX_DIM,
height / MAX_DIM,
Expand Down Expand Up @@ -268,6 +279,87 @@ class ImageResizer {
return imgData;
}

_rescaleImageData() {
const { _imgData: imgData } = this;
const { data, width, height, kind } = imgData;
// K is such as width * height * 4 / 2 ** K <= 2 ** 31 - 1
const K = Math.ceil(Math.log2((width * height * 4) / (2 ** 31 - 1)));
const newWidth = width >> K;
const newHeight = height >> K;
let rgbaData;
let maxHeight = height;

// We try to allocate the buffer with the maximum size but it can fail.
try {
rgbaData = new Uint8Array(width * height * 4);
} catch {
// n is such as 2 ** n - 1 > width * height * 4
let n = Math.floor(Math.log2(width * height * 4 + 1));

while (true) {
try {
rgbaData = new Uint8Array(2 ** n - 1);

Check warning

Code scanning / CodeQL

Useless assignment to local variable Warning

The value assigned to rgbaData here is unused.
break;
} catch {
n -= 1;
}
}

maxHeight = Math.floor((2 ** n - 1) / (width * 4));
rgbaData = new Uint8Array(width * maxHeight * 4);
}

const src32 = new Uint32Array(rgbaData.buffer);
const dest32 = new Uint32Array(newWidth * newHeight);

let srcPos = 0;
let newIndex = 0;
const step = Math.ceil(height / maxHeight);
const remainder = height % maxHeight === 0 ? height : height % maxHeight;
for (let k = 0; k < step; k++) {
const h = k < step - 1 ? maxHeight : remainder;
({ srcPos } = convertToRGBA({
kind,
src: data,
dest: src32,
width,
height: h,
inverseDecode: this._isMask,
srcPos,
}));

for (let i = 0, ii = h >> K; i < ii; i++) {
const buf = src32.subarray((i << K) * width);
for (let j = 0; j < newWidth; j++) {
dest32[newIndex++] = buf[j << K];
}
}
}

if (ImageResizer.needsToBeResized(newWidth, newHeight)) {
imgData.data = dest32;
imgData.width = newWidth;
imgData.height = newHeight;
imgData.kind = ImageKind.RGBA_32BPP;

return null;
}

const canvas = new OffscreenCanvas(newWidth, newHeight);
const ctx = canvas.getContext("2d", { willReadFrequently: true });
ctx.putImageData(
new ImageData(new Uint8ClampedArray(dest32.buffer), newWidth, newHeight),
0,
0
);
imgData.data = null;
imgData.bitmap = canvas.transferToImageBitmap();
imgData.width = newWidth;
imgData.height = newHeight;

return imgData;
}

_encodeBMP() {
const { width, height, kind } = this._imgData;
let data = this._imgData.data;
Expand Down
9 changes: 5 additions & 4 deletions src/shared/image_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ function convertRGBToRGBA({
height,
}) {
let i = 0;
const len32 = src.length >> 2;
const len = width * height * 3;
const len32 = len >> 2;
const src32 = new Uint32Array(src.buffer, srcPos, len32);

if (FeatureTest.isLittleEndian) {
Expand All @@ -94,7 +95,7 @@ function convertRGBToRGBA({
dest[destPos + 3] = (s3 >>> 8) | 0xff000000;
}

for (let j = i * 4, jj = src.length; j < jj; j += 3) {
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;
}
Expand All @@ -110,13 +111,13 @@ function convertRGBToRGBA({
dest[destPos + 3] = (s3 << 8) | 0xff;
}

for (let j = i * 4, jj = src.length; j < jj; j += 3) {
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;
}
}

return { srcPos, destPos };
return { srcPos: srcPos + len, destPos };
}

function grayToRGBA(src, dest) {
Expand Down
1 change: 1 addition & 0 deletions test/pdfs/issue17190.pdf.link
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://github.com/mozilla/pdf.js/files/13180762/org_AVA89V01U0.Black.pdf
1 change: 1 addition & 0 deletions test/pdfs/issue17190_1.pdf.link
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://github.com/mozilla/pdf.js/files/13670664/abc.pdf
18 changes: 18 additions & 0 deletions test/test_manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -10775,5 +10775,23 @@
"forms": true,
"talos": false,
"type": "eq"
},
{
"id": "issue17190",
"file": "pdfs/issue17190.pdf",
"md5": "06e3ce6492dc0b5815a63965b5d7144c",
"rounds": 1,
"talos": false,
"type": "eq",
"link": true
},
{
"id": "issue17190_1",
"file": "pdfs/issue17190_1.pdf",
"md5": "63bbc71a6c2cdec11bb20c5744232c47",
"rounds": 1,
"talos": false,
"type": "eq",
"link": true
}
]

0 comments on commit 0b5dfeb

Please sign in to comment.