1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-23 08:38:06 +02:00

Rescale the image data when they're really too large

It fixes #17190.
This commit is contained in:
Calixte Denizet 2024-11-23 20:35:30 +01:00
parent 1f6cc85134
commit 1ef670411a
7 changed files with 136 additions and 10 deletions

View file

@ -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;
@ -713,6 +715,8 @@ export {
lookupMatrix,
lookupNormalRect,
lookupRect,
MAX_INT_32,
MIN_INT_32,
MissingDataException,
numberToString,
ParserEOFException,

View file

@ -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;
@ -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;
@ -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,
@ -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;

View file

@ -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";
@ -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) {

View file

@ -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) {
@ -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;
}
@ -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) {