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

[api-minor] Add a jpx decoder based on OpenJPEG 2.5.2

The decoder is compiled in WASM:
https://github.com/mozilla/pdf.js.openjpeg

Fixes #17289, #17061, #16485, #13051, #6365, #4648, #12213.
This commit is contained in:
Calixte Denizet 2024-04-15 10:12:05 +02:00
parent 2e94511330
commit 2e83cfbbc1
20 changed files with 172 additions and 2347 deletions

View file

@ -6,6 +6,7 @@ external/bcmaps/
external/builder/fixtures/
external/builder/fixtures_esprima/
external/quickjs/
external/openjpeg/
test/tmp/
test/pdfs/
web/locale/

12
external/openjpeg/README.md vendored Normal file
View file

@ -0,0 +1,12 @@
## Build
In order to generate the file `openjpeg.js`:
* git clone https://github.com/mozilla/pdf.js.openjpeg/
* the build requires to have a [Docker](https://www.docker.com/) setup and then:
* `node build.js -C` to build the Docker image
* `node build.js -co /pdf.js/external/openjpeg/` to compile the decoder
## Licensing
[OpenJPEG](https://www.openjpeg.org/) is under [BSD 2-clause "Simplified" License](https://github.com/uclouvain/openjpeg/blob/master/LICENSE)
and [pdf.js.openjpeg](https://github.com/mozilla/pdf.js.openjpeg/) is released under [Apache 2](https://github.com/mozilla/pdf.js.openjpeg/blob/main/LICENSE) license so `openjpeg.js` is released under [Apache 2](https://github.com/mozilla/pdf.js.openjpeg/blob/main/LICENSE) license too.

15
external/openjpeg/openjpeg.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -1603,6 +1603,7 @@ function buildLib(defines, dir) {
),
gulp.src(["web/*.js", "!web/{pdfjs,viewer}.js"], { base: "." }),
gulp.src("test/unit/*.js", { base: "." }),
gulp.src("external/openjpeg/*.js", { base: "openjpeg/" }),
]);
return buildLibHelper(bundleDefines, inputStream, dir);

View file

@ -16,11 +16,16 @@
import { clearPatternCaches } from "./pattern.js";
import { clearPrimitiveCaches } from "./primitives.js";
import { clearUnicodeCaches } from "./unicode.js";
import { JpxImage } from "./jpx.js";
function clearGlobalCaches() {
clearPatternCaches();
clearPrimitiveCaches();
clearUnicodeCaches();
// Remove the global `JpxImage` instance, since it may hold a reference to
// the WebAssembly module.
JpxImage.cleanup();
}
export { clearGlobalCaches };

View file

@ -73,7 +73,7 @@ class DecodeStream extends BaseStream {
return this.buffer[this.pos++];
}
getBytes(length) {
getBytes(length, ignoreColorSpace = false) {
const pos = this.pos;
let end;
@ -82,7 +82,7 @@ class DecodeStream extends BaseStream {
end = pos + length;
while (!this.eof && this.bufferLength < end) {
this.readBlock();
this.readBlock(ignoreColorSpace);
}
const bufEnd = this.bufferLength;
if (end > bufEnd) {
@ -90,7 +90,7 @@ class DecodeStream extends BaseStream {
}
} else {
while (!this.eof) {
this.readBlock();
this.readBlock(ignoreColorSpace);
}
end = this.bufferLength;
}

View file

@ -104,6 +104,7 @@ class PDFImage {
localColorSpaceCache,
}) {
this.image = image;
let jpxDecode = false;
const dict = image.dict;
const filter = dict.get("F", "Filter");
@ -118,14 +119,14 @@ class PDFImage {
}
switch (filterName) {
case "JPXDecode":
const jpxImage = new JpxImage();
jpxImage.parseImageProperties(image.stream);
({
width: image.width,
height: image.height,
componentsCount: image.numComps,
bitsPerComponent: image.bitsPerComponent,
} = JpxImage.parseImageProperties(image.stream));
image.stream.reset();
image.width = jpxImage.width;
image.height = jpxImage.height;
image.bitsPerComponent = jpxImage.bitsPerComponent;
image.numComps = jpxImage.componentsCount;
jpxDecode = true;
break;
case "JBIG2Decode":
image.bitsPerComponent = 1;
@ -197,6 +198,7 @@ class PDFImage {
);
}
}
this.colorSpace = ColorSpace.parse({
cs: colorSpace,
xref,
@ -205,6 +207,10 @@ class PDFImage {
localColorSpaceCache,
});
this.numComps = this.colorSpace.numComps;
// If the jpx image has a color space then it musn't be used in order to
// be able to use the color space that comes from the pdf.
this.ignoreColorSpace = jpxDecode && this.colorSpace.name === "Indexed";
}
this.decode = dict.getArray("D", "Decode");
@ -984,7 +990,7 @@ class PDFImage {
this.image.drawHeight = drawHeight || this.height;
this.image.forceRGBA = !!forceRGBA;
this.image.forceRGB = !!forceRGB;
const imageBytes = this.image.getBytes(length);
const imageBytes = this.image.getBytes(length, this.ignoreColorSpace);
// If imageBytes came from a DecodeStream, we're safe to transfer it
// (and thus detach its underlying buffer) because it will constitute

File diff suppressed because it is too large Load diff

View file

@ -41,44 +41,12 @@ class JpxStream extends DecodeStream {
// directly insert all of its data into `this.buffer`.
}
readBlock() {
readBlock(ignoreColorSpace) {
if (this.eof) {
return;
}
const jpxImage = new JpxImage();
jpxImage.parse(this.bytes);
const width = jpxImage.width;
const height = jpxImage.height;
const componentsCount = jpxImage.componentsCount;
const tileCount = jpxImage.tiles.length;
if (tileCount === 1) {
this.buffer = jpxImage.tiles[0].items;
} else {
const data = new Uint8ClampedArray(width * height * componentsCount);
for (let k = 0; k < tileCount; k++) {
const tileComponents = jpxImage.tiles[k];
const tileWidth = tileComponents.width;
const tileHeight = tileComponents.height;
const tileLeft = tileComponents.left;
const tileTop = tileComponents.top;
const src = tileComponents.items;
let srcPosition = 0;
let dataPosition = (width * tileTop + tileLeft) * componentsCount;
const imgRowSize = width * componentsCount;
const tileRowSize = tileWidth * componentsCount;
for (let j = 0; j < tileHeight; j++) {
const rowBytes = src.subarray(srcPosition, srcPosition + tileRowSize);
data.set(rowBytes, dataPosition);
srcPosition += tileRowSize;
dataPosition += imgRowSize;
}
}
this.buffer = data;
}
this.buffer = JpxImage.decode(this.bytes, ignoreColorSpace);
this.bufferLength = this.buffer.length;
this.eof = true;
}

View file

@ -642,3 +642,4 @@
!issue17871_top_right.pdf
!bug1889122.pdf
!issue17929.pdf
!issue12213.pdf

View file

@ -0,0 +1,2 @@
https://bugzilla.mozilla.org/attachment.cgi?id=9254200

View file

@ -0,0 +1,2 @@
https://github.com/mozilla/pdf.js/files/6600376/JPXimageprobem.pdf

BIN
test/pdfs/issue12213.pdf Executable file

Binary file not shown.

View file

@ -0,0 +1,2 @@
https://github.com/mozilla/pdf.js/files/6083044/njp.32101064480005_page_013.2.pdf

View file

@ -0,0 +1,2 @@
https://web.archive.org/web/20230617061919/https://cdn.dealereprocess.org/cdn/servicemanuals/nissan/2023-ariya.pdf

View file

@ -0,0 +1,2 @@
https://web.archive.org/web/20240229104747/https://btohq.sgp1.cdn.digitaloceanspaces.com/bto/nov-2019-bto/plantation-village.pdf

View file

@ -0,0 +1,2 @@
https://github.com/mozilla/pdf.js/files/13391396/example.pdf

View file

@ -0,0 +1,2 @@
https://web.archive.org/web/20240415131452/http://www.braidense.it/rd/03595.pdf

View file

@ -0,0 +1 @@
https://web.archive.org/web/20210403114737/http://waltercosand.com/CosandScores/Composers%20A-D/Chopin,%20Frederic/Find_by_Category/Preludes/Preludes-Op28-edDebussy.pdf

View file

@ -9840,5 +9840,84 @@
"md5": "2f5686f77f73fb99037f5c2c7e48ce53",
"rounds": 1,
"type": "eq"
},
{
"id": "issue17289",
"file": "pdfs/issue17289.pdf",
"md5": "acf5bb9e4309ba1c4b3568e7c4a7dd73",
"rounds": 1,
"link": true,
"type": "eq"
},
{
"id": "issue17061",
"file": "pdfs/issue17061.pdf",
"md5": "21a1b887b7e657b01e4205b22d1f4378",
"rounds": 1,
"link": true,
"firstPage": 1,
"lastPage": 5,
"type": "eq"
},
{
"id": "issue16485",
"file": "pdfs/issue16485.pdf",
"md5": "8a2c43161e0d89469fc7ed345c7bd3b1",
"rounds": 1,
"link": true,
"firstPage": 266,
"lastPage": 266,
"type": "eq"
},
{
"id": "issue13051",
"file": "pdfs/issue13051.pdf",
"md5": "ead6598c79641a0d266a224160e764d8",
"rounds": 1,
"link": true,
"type": "eq"
},
{
"id": "issue6365",
"file": "pdfs/issue6365.pdf",
"md5": "fd567f3b4f7c896b5b8071ea762af069",
"rounds": 1,
"link": true,
"firstPage": 2,
"lastPage": 5,
"type": "eq"
},
{
"id": "issue4648",
"file": "pdfs/issue4648.pdf",
"md5": "9906cb55f190b8a2d340ea34093ed763",
"rounds": 1,
"link": true,
"firstPage": 1,
"lastPage": 4,
"type": "eq"
},
{
"id": "bug1659412",
"file": "pdfs/bug1659412.pdf",
"md5": "0a3220e322d7b899e7604cea947d424c",
"rounds": 1,
"link": true,
"type": "eq"
},
{
"id": "issue12213",
"file": "pdfs/issue12213.pdf",
"md5": "84df1ce3e3d96e793a2e339aaba138e9",
"rounds": 1,
"type": "eq"
},
{
"id": "issue12213.1",
"file": "pdfs/issue12213.1.pdf",
"md5": "8ff4af8d7fd391b7f6601406e490d865",
"rounds": 1,
"link": true,
"type": "eq"
}
]