Skip to content

Commit

Permalink
Merge pull request #19095 from calixteman/issue17190
Browse files Browse the repository at this point in the history
Rescale the image data when they're really too large
  • Loading branch information
calixteman authored Nov 24, 2024
2 parents f911635 + 1ef6704 commit 9017e80
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 10 deletions.
4 changes: 4 additions & 0 deletions src/core/core_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import { Dict, isName, Ref, RefSet } from "./primitives.js";
import { BaseStream } from "./base_stream.js";

const PDF_VERSION_REGEXP = /^[1-9]\.\d$/;
const MAX_INT_32 = 2 ** 31 - 1;
const MIN_INT_32 = -(2 ** 31);

function getLookupTableFactory(initializer) {
let lookup;
Expand Down Expand Up @@ -713,6 +715,8 @@ export {
lookupMatrix,
lookupNormalRect,
lookupRect,
MAX_INT_32,
MIN_INT_32,
MissingDataException,
numberToString,
ParserEOFException,
Expand Down
101 changes: 99 additions & 2 deletions src/core/image_resizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
*/

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

const MIN_IMAGE_DIM = 2048;

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

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

if (width * height * 4 > MAX_INT_32) {
// 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 +220,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 +280,91 @@ class ImageResizer {
return imgData;
}

#rescaleImageData() {
const { _imgData: imgData } = this;
const { data, width, height, kind } = imgData;
const rgbaSize = width * height * 4;
// K is such as width * height * 4 / 2 ** K <= 2 ** 31 - 1
const K = Math.ceil(Math.log2(rgbaSize / MAX_INT_32));
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(rgbaSize);
} catch {
// n is such as 2 ** n - 1 > width * height * 4
let n = Math.floor(Math.log2(rgbaSize + 1));

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

maxHeight = Math.floor((2 ** n - 1) / (width * 4));
const newSize = width * maxHeight * 4;
if (newSize < rgbaData.length) {
rgbaData = new Uint8Array(newSize);
}
}

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
12 changes: 8 additions & 4 deletions src/core/jbig2.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@
*/

import { BaseException, shadow } from "../shared/util.js";
import { log2, readInt8, readUint16, readUint32 } from "./core_utils.js";
import {
log2,
MAX_INT_32,
MIN_INT_32,
readInt8,
readUint16,
readUint32,
} from "./core_utils.js";
import { ArithmeticDecoder } from "./arithmetic_decoder.js";
import { CCITTFaxDecoder } from "./ccitt.js";

Expand Down Expand Up @@ -52,9 +59,6 @@ class DecodingContext {
}
}

const MAX_INT_32 = 2 ** 31 - 1;
const MIN_INT_32 = -(2 ** 31);

// Annex A. Arithmetic Integer Decoding Procedure
// A.2 Procedure for decoding values
function decodeInteger(contextCache, procedure, decoder) {
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 9017e80

Please sign in to comment.