From f554d0c9c89b8e4bb837f2193c767f63ea8869a0 Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Thu, 16 Jul 2026 21:04:45 +0200 Subject: [PATCH] Implement SmaskInData == 2 for JPX images --- src/core/image.js | 70 +++++++++++++++++++++++++--------- test/pdfs/.gitignore | 1 + test/pdfs/jpx_smaskindata.pdf | Bin 0 -> 2515 bytes test/test_manifest.json | 7 ++++ 4 files changed, 61 insertions(+), 17 deletions(-) create mode 100644 test/pdfs/jpx_smaskindata.pdf diff --git a/src/core/image.js b/src/core/image.js index 8861461d2..fe3883a2f 100644 --- a/src/core/image.js +++ b/src/core/image.js @@ -119,7 +119,7 @@ class PDFImage { this.jpxDecoderOptions = { numComponents: 0, isIndexedColormap: false, - smaskInData: dict.has("SMaskInData"), + smaskInData: dict.get("SMaskInData") >= 1, reducePower, }; if (reducePower) { @@ -196,6 +196,25 @@ class PDFImage { if (!this.imageMask) { let colorSpace = dict.getRaw("CS") || dict.getRaw("ColorSpace"); const hasColorSpace = !!colorSpace; + + if ( + this.jpxDecoderOptions?.smaskInData && + dict.get("SMaskInData") === 2 + ) { + this.jpxPremultiplied = true; + if (this.matte) { + const matteColorSpace = ColorSpaceUtils.parse({ + cs: hasColorSpace ? colorSpace : Name.get("DeviceRGB"), + xref, + resources: isInline ? res : null, + pdfFunctionFactory, + globalColorSpaceCache, + localColorSpaceCache, + }); + this.preblendMatte = matteColorSpace.getRgb(this.matte, 0); + } + } + if (!hasColorSpace) { if (this.jpxDecoderOptions) { colorSpace = Name.get("DeviceRGBA"); @@ -654,22 +673,7 @@ class PDFImage { }); } - undoPreblend(buffer, width, height) { - if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { - assert( - buffer instanceof Uint8ClampedArray, - 'PDFImage.undoPreblend: Unsupported "buffer" type.' - ); - } - const matte = this.smask?.matte; - if (!matte) { - return; - } - const matteRgb = this.colorSpace.getRgb(matte, 0); - const matteR = matteRgb[0]; - const matteG = matteRgb[1]; - const matteB = matteRgb[2]; - const length = width * height * 4; + static #undoPreblend(buffer, length, matteR, matteG, matteB) { for (let i = 0; i < length; i += 4) { const alpha = buffer[i + 3]; if (alpha === 0) { @@ -687,6 +691,27 @@ class PDFImage { } } + undoPreblend(buffer, width, height) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + buffer instanceof Uint8ClampedArray, + 'PDFImage.undoPreblend: Unsupported "buffer" type.' + ); + } + const matte = this.smask?.matte; + if (!matte) { + return; + } + const matteRgb = this.colorSpace.getRgb(matte, 0); + PDFImage.#undoPreblend( + buffer, + width * height * 4, + matteRgb[0], + matteRgb[1], + matteRgb[2] + ); + } + async createImageData(forceRGBA = false, isOffscreenCanvasSupported = false) { const drawWidth = this.drawWidth; const drawHeight = this.drawHeight; @@ -717,6 +742,17 @@ class PDFImage { { internal: isOffscreenCanvasSupported && mustBeResized } )); + if (this.jpxPremultiplied) { + const matteRgb = this.preblendMatte; + PDFImage.#undoPreblend( + imgArray, + imgArray.length, + matteRgb?.[0] ?? 0, + matteRgb?.[1] ?? 0, + matteRgb?.[2] ?? 0 + ); + } + if (isOffscreenCanvasSupported) { if (!mustBeResized) { return this.createBitmap( diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 19c9ff2ad..e25516a81 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -945,3 +945,4 @@ !issue20504_skia.pdf !issue21579.pdf !saslprep-r6.pdf +!jpx_smaskindata.pdf diff --git a/test/pdfs/jpx_smaskindata.pdf b/test/pdfs/jpx_smaskindata.pdf new file mode 100644 index 0000000000000000000000000000000000000000..26d47af90272df6ff4589bdcbeffeaa8881f2eb4 GIT binary patch literal 2515 zcmeHJPj3=I6dx2*-PYKHQI9Vgjs@K*7W&wK2=?0E@7LyVIui(%wuw znRwP1zkt#BJ$mru!8Fkif^T+NN})}>G3+ghc!X2m`&<~h6Yv0=Oyae!cg$19YQ;~z;;{?D#(u<+lQ@` z@Y^176oN@d5yqqF7{{=^(DWPHo3rVtcTqpg>J4kXE)qu2P@vcoya-65lM> z+tlI#UFzzfhrXq51~L(ug=|C`U<717kLitMC_+XVKBjJyw=ke441DfUvnwe{9hztq zB?2CWhV&9rLZpaB#J0L1i!6!^Hclxq`AD0Y)Mih7vy)m?J#jWdwLoN$*Z>b#rzu}uQ(k}J&@Nbn?-6d8_k|NHqL?u@|+j literal 0 HcmV?d00001 diff --git a/test/test_manifest.json b/test/test_manifest.json index b2607f01d..c60a97fdf 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -14479,5 +14479,12 @@ "rounds": 1, "type": "eq", "password": "S\u00AASL\u00ADprep" + }, + { + "id": "jpx_smaskindata", + "file": "pdfs/jpx_smaskindata.pdf", + "md5": "f7a8628b14ca176700247f106c16812a", + "rounds": 1, + "type": "eq" } ]