Downscale jpeg2000 images, if needed, while decoding them
It fixes #19517.
This commit is contained in:
parent
06f44916c8
commit
ac925f4f1b
3
external/openjpeg/openjpeg.js
vendored
3
external/openjpeg/openjpeg.js
vendored
File diff suppressed because one or more lines are too long
BIN
external/openjpeg/openjpeg.wasm
vendored
BIN
external/openjpeg/openjpeg.wasm
vendored
Binary file not shown.
19
external/openjpeg/openjpeg_nowasm_fallback.js
vendored
19
external/openjpeg/openjpeg_nowasm_fallback.js
vendored
File diff suppressed because one or more lines are too long
@ -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;
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -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
BIN
test/pdfs/issue19517.pdf
Executable file
Binary file not shown.
@ -12107,5 +12107,12 @@
|
|||||||
"value": "Hello World"
|
"value": "Hello World"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "issue19517",
|
||||||
|
"file": "pdfs/issue19517.pdf",
|
||||||
|
"md5": "2abe67c8b34522feb6b85d252dde9d3e",
|
||||||
|
"rounds": 1,
|
||||||
|
"type": "eq"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user