Downscale jpeg2000 images, if needed, while decoding them

It fixes #19517.
This commit is contained in:
Calixte Denizet 2025-05-05 19:50:09 +02:00
parent 06f44916c8
commit ac925f4f1b
9 changed files with 62 additions and 15 deletions

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -112,11 +112,22 @@ class PDFImage {
bitsPerComponent: image.bitsPerComponent, bitsPerComponent: image.bitsPerComponent,
} = JpxImage.parseImageProperties(image.stream)); } = JpxImage.parseImageProperties(image.stream));
image.stream.reset(); image.stream.reset();
const reducePower = ImageResizer.getReducePowerForJPX(
image.width,
image.height,
image.numComps
);
this.jpxDecoderOptions = { this.jpxDecoderOptions = {
numComponents: 0, numComponents: 0,
isIndexedColormap: false, isIndexedColormap: false,
smaskInData: dict.has("SMaskInData"), smaskInData: dict.has("SMaskInData"),
reducePower,
}; };
if (reducePower) {
const factor = 2 ** reducePower;
image.width = Math.ceil(image.width / factor);
image.height = Math.ceil(image.height / factor);
}
break; break;
case "JBIG2Decode": case "JBIG2Decode":
image.bitsPerComponent = 1; image.bitsPerComponent = 1;

View File

@ -96,6 +96,30 @@ class ImageResizer {
return area > maxArea; return area > maxArea;
} }
static getReducePowerForJPX(width, height, componentsCount) {
const area = width * height;
// The maximum memory we've in the wasm runtime is 2GB.
// Each component is 4 bytes and we can't allocate all the memory just for
// the buffers so we limit the size to 1GB / (componentsCount * 4).
// We could use more than 2GB by setting MAXIMUM_MEMORY but it would take
// too much time to decode a big image.
const maxJPXArea = 2 ** 30 / (componentsCount * 4);
if (!this.needsToBeResized(width, height)) {
if (area > maxJPXArea) {
// The image is too large, we need to rescale it.
return Math.ceil(Math.log2(area / maxJPXArea));
}
return 0;
}
const { MAX_DIM, MAX_AREA } = this;
const minFactor = Math.max(
width / MAX_DIM,
height / MAX_DIM,
Math.sqrt(area / Math.min(maxJPXArea, MAX_AREA))
);
return Math.ceil(Math.log2(minFactor));
}
static get MAX_DIM() { static get MAX_DIM() {
return shadow( return shadow(
this, this,

View File

@ -92,7 +92,12 @@ class JpxImage {
static async decode( static async decode(
bytes, bytes,
{ numComponents = 4, isIndexedColormap = false, smaskInData = false } = {} {
numComponents = 4,
isIndexedColormap = false,
smaskInData = false,
reducePower = 0,
} = {}
) { ) {
if (!this.#modulePromise) { if (!this.#modulePromise) {
const { promise, resolve } = Promise.withResolvers(); const { promise, resolve } = Promise.withResolvers();
@ -119,13 +124,14 @@ class JpxImage {
try { try {
const size = bytes.length; const size = bytes.length;
ptr = module._malloc(size); ptr = module._malloc(size);
module.HEAPU8.set(bytes, ptr); module.writeArrayToMemory(bytes, ptr);
const ret = module._jp2_decode( const ret = module._jp2_decode(
ptr, ptr,
size, size,
numComponents > 0 ? numComponents : 0, numComponents > 0 ? numComponents : 0,
!!isIndexedColormap, !!isIndexedColormap,
!!smaskInData !!smaskInData,
reducePower
); );
if (ret) { if (ret) {
const { errorMessages } = module; const { errorMessages } = module;

View File

@ -724,3 +724,4 @@
!issue16742.pdf !issue16742.pdf
!chrome-text-selection-markedContent.pdf !chrome-text-selection-markedContent.pdf
!bug1963407.pdf !bug1963407.pdf
!issue19517.pdf

BIN
test/pdfs/issue19517.pdf Executable file

Binary file not shown.

View File

@ -12107,5 +12107,12 @@
"value": "Hello World" "value": "Hello World"
} }
} }
},
{
"id": "issue19517",
"file": "pdfs/issue19517.pdf",
"md5": "2abe67c8b34522feb6b85d252dde9d3e",
"rounds": 1,
"type": "eq"
} }
] ]