Merge pull request #19095 from calixteman/issue17190
Rescale the image data when they're really too large
This commit is contained in:
commit
9017e80d5a
@ -27,6 +27,8 @@ import { Dict, isName, Ref, RefSet } from "./primitives.js";
|
|||||||
import { BaseStream } from "./base_stream.js";
|
import { BaseStream } from "./base_stream.js";
|
||||||
|
|
||||||
const PDF_VERSION_REGEXP = /^[1-9]\.\d$/;
|
const PDF_VERSION_REGEXP = /^[1-9]\.\d$/;
|
||||||
|
const MAX_INT_32 = 2 ** 31 - 1;
|
||||||
|
const MIN_INT_32 = -(2 ** 31);
|
||||||
|
|
||||||
function getLookupTableFactory(initializer) {
|
function getLookupTableFactory(initializer) {
|
||||||
let lookup;
|
let lookup;
|
||||||
@ -713,6 +715,8 @@ export {
|
|||||||
lookupMatrix,
|
lookupMatrix,
|
||||||
lookupNormalRect,
|
lookupNormalRect,
|
||||||
lookupRect,
|
lookupRect,
|
||||||
|
MAX_INT_32,
|
||||||
|
MIN_INT_32,
|
||||||
MissingDataException,
|
MissingDataException,
|
||||||
numberToString,
|
numberToString,
|
||||||
ParserEOFException,
|
ParserEOFException,
|
||||||
|
|||||||
@ -14,6 +14,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { FeatureTest, ImageKind, shadow, warn } from "../shared/util.js";
|
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;
|
const MIN_IMAGE_DIM = 2048;
|
||||||
|
|
||||||
@ -172,6 +174,18 @@ class ImageResizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _createImage() {
|
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();
|
const data = this._encodeBMP();
|
||||||
let decoder, imagePromise;
|
let decoder, imagePromise;
|
||||||
|
|
||||||
@ -206,8 +220,6 @@ class ImageResizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { MAX_AREA, MAX_DIM } = ImageResizer;
|
const { MAX_AREA, MAX_DIM } = ImageResizer;
|
||||||
const { _imgData: imgData } = this;
|
|
||||||
const { width, height } = imgData;
|
|
||||||
const minFactor = Math.max(
|
const minFactor = Math.max(
|
||||||
width / MAX_DIM,
|
width / MAX_DIM,
|
||||||
height / MAX_DIM,
|
height / MAX_DIM,
|
||||||
@ -268,6 +280,91 @@ class ImageResizer {
|
|||||||
return imgData;
|
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() {
|
_encodeBMP() {
|
||||||
const { width, height, kind } = this._imgData;
|
const { width, height, kind } = this._imgData;
|
||||||
let data = this._imgData.data;
|
let data = this._imgData.data;
|
||||||
|
|||||||
@ -14,7 +14,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { BaseException, shadow } from "../shared/util.js";
|
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 { ArithmeticDecoder } from "./arithmetic_decoder.js";
|
||||||
import { CCITTFaxDecoder } from "./ccitt.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
|
// Annex A. Arithmetic Integer Decoding Procedure
|
||||||
// A.2 Procedure for decoding values
|
// A.2 Procedure for decoding values
|
||||||
function decodeInteger(contextCache, procedure, decoder) {
|
function decodeInteger(contextCache, procedure, decoder) {
|
||||||
|
|||||||
@ -77,7 +77,8 @@ function convertRGBToRGBA({
|
|||||||
height,
|
height,
|
||||||
}) {
|
}) {
|
||||||
let i = 0;
|
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);
|
const src32 = new Uint32Array(src.buffer, srcPos, len32);
|
||||||
|
|
||||||
if (FeatureTest.isLittleEndian) {
|
if (FeatureTest.isLittleEndian) {
|
||||||
@ -94,7 +95,7 @@ function convertRGBToRGBA({
|
|||||||
dest[destPos + 3] = (s3 >>> 8) | 0xff000000;
|
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++] =
|
dest[destPos++] =
|
||||||
src[j] | (src[j + 1] << 8) | (src[j + 2] << 16) | 0xff000000;
|
src[j] | (src[j + 1] << 8) | (src[j + 2] << 16) | 0xff000000;
|
||||||
}
|
}
|
||||||
@ -110,13 +111,13 @@ function convertRGBToRGBA({
|
|||||||
dest[destPos + 3] = (s3 << 8) | 0xff;
|
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++] =
|
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) | 0xff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { srcPos, destPos };
|
return { srcPos: srcPos + len, destPos };
|
||||||
}
|
}
|
||||||
|
|
||||||
function grayToRGBA(src, dest) {
|
function grayToRGBA(src, dest) {
|
||||||
|
|||||||
1
test/pdfs/issue17190.pdf.link
Normal file
1
test/pdfs/issue17190.pdf.link
Normal file
@ -0,0 +1 @@
|
|||||||
|
https://github.com/mozilla/pdf.js/files/13180762/org_AVA89V01U0.Black.pdf
|
||||||
1
test/pdfs/issue17190_1.pdf.link
Normal file
1
test/pdfs/issue17190_1.pdf.link
Normal file
@ -0,0 +1 @@
|
|||||||
|
https://github.com/mozilla/pdf.js/files/13670664/abc.pdf
|
||||||
@ -10775,5 +10775,23 @@
|
|||||||
"forms": true,
|
"forms": true,
|
||||||
"talos": false,
|
"talos": false,
|
||||||
"type": "eq"
|
"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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user