Merge pull request #19893 from calixteman/issue19517

Downscale jpeg2000 images, if needed, while decoding them
This commit is contained in:
calixteman 2025-05-05 22:53:49 +02:00 committed by GitHub
commit 04400c588f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
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,
} = JpxImage.parseImageProperties(image.stream));
image.stream.reset();
const reducePower = ImageResizer.getReducePowerForJPX(
image.width,
image.height,
image.numComps
);
this.jpxDecoderOptions = {
numComponents: 0,
isIndexedColormap: false,
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;
case "JBIG2Decode":
image.bitsPerComponent = 1;

View File

@ -96,6 +96,30 @@ class ImageResizer {
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() {
return shadow(
this,

View File

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

View File

@ -724,3 +724,4 @@
!issue16742.pdf
!chrome-text-selection-markedContent.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"
}
}
},
{
"id": "issue19517",
"file": "pdfs/issue19517.pdf",
"md5": "2abe67c8b34522feb6b85d252dde9d3e",
"rounds": 1,
"type": "eq"
}
]