From 751f9992c4d1e6bba33ff2a90caae427197a25e5 Mon Sep 17 00:00:00 2001 From: "Michael B. Klein" Date: Tue, 12 Nov 2024 11:32:41 -0600 Subject: [PATCH] Expose JPEG 2000 oneshot decoder option #4262 Requires libvips compiled with support for JP2 images Co-authored-by: Kleis Auke Wolthuizen --- docs/src/content/docs/api-constructor.md | 1 + docs/src/content/docs/changelog.md | 4 +++ lib/constructor.js | 1 + lib/index.d.ts | 2 ++ lib/input.js | 14 ++++++++-- src/common.cc | 10 +++++++ src/common.h | 4 ++- test/fixtures/index.js | 1 + test/fixtures/relax_tileparts.jp2 | Bin 0 -> 10218 bytes test/types/sharp.test-d.ts | 3 ++ test/unit/jp2.js | 34 +++++++++++++++++++++-- 11 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 test/fixtures/relax_tileparts.jp2 diff --git a/docs/src/content/docs/api-constructor.md b/docs/src/content/docs/api-constructor.md index 8010b58b..b5b7a9c8 100644 --- a/docs/src/content/docs/api-constructor.md +++ b/docs/src/content/docs/api-constructor.md @@ -47,6 +47,7 @@ where the overall height is the `pageHeight` multiplied by the number of `pages` | [options.subifd] | number | -1 | subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image. | | [options.level] | number | 0 | level to extract from a multi-level input (OpenSlide), zero based. | | [options.pdfBackground] | string \| Object | | Background colour to use when PDF is partially transparent. Parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. Requires the use of a globally-installed libvips compiled with support for PDFium, Poppler, ImageMagick or GraphicsMagick. | +| [options.jp2Oneshot] | boolean | false | Set to `true` to decode tiled JPEG 2000 images in a single operation, improving compatibility. | | [options.animated] | boolean | false | Set to `true` to read all frames/pages of an animated image (GIF, WebP, TIFF), equivalent of setting `pages` to `-1`. | | [options.raw] | Object | | describes raw pixel input image data. See `raw()` for pixel ordering. | | [options.raw.width] | number | | integral number of pixels wide. | diff --git a/docs/src/content/docs/changelog.md b/docs/src/content/docs/changelog.md index 191a0a72..f84859d8 100644 --- a/docs/src/content/docs/changelog.md +++ b/docs/src/content/docs/changelog.md @@ -10,6 +10,10 @@ Requires libvips v8.17.0 * Upgrade to libvips v8.17.0 for upstream bug fixes. +* Expose JPEG 2000 `oneshot` decoder option. + [#4262](https://github.com/lovell/sharp/pull/4262) + [@mbklein](https://github.com/mbklein) + * Support composite operation with non-sRGB pipeline colourspace. [#4412](https://github.com/lovell/sharp/pull/4412) [@kleisauke](https://github.com/kleisauke) diff --git a/lib/constructor.js b/lib/constructor.js index d9490068..c70bbf1e 100644 --- a/lib/constructor.js +++ b/lib/constructor.js @@ -156,6 +156,7 @@ const debuglog = util.debuglog('sharp'); * @param {number} [options.subifd=-1] - subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image. * @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based. * @param {string|Object} [options.pdfBackground] - Background colour to use when PDF is partially transparent. Parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. Requires the use of a globally-installed libvips compiled with support for PDFium, Poppler, ImageMagick or GraphicsMagick. + * @param {boolean} [options.jp2Oneshot=false] - Set to `true` to decode tiled JPEG 2000 images in a single operation, improving compatibility. * @param {boolean} [options.animated=false] - Set to `true` to read all frames/pages of an animated image (GIF, WebP, TIFF), equivalent of setting `pages` to `-1`. * @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering. * @param {number} [options.raw.width] - integral number of pixels wide. diff --git a/lib/index.d.ts b/lib/index.d.ts index a8f1d98d..9ed7190d 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -1009,6 +1009,8 @@ declare namespace sharp { level?: number | undefined; /** Background colour to use when PDF is partially transparent. Requires the use of a globally-installed libvips compiled with support for PDFium, Poppler, ImageMagick or GraphicsMagick. */ pdfBackground?: Colour | Color | undefined; + /** Set to `true` to load JPEG 2000 images using [oneshot mode](https://github.com/libvips/libvips/issues/4205) */ + jp2Oneshot?: boolean | undefined; /** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default false) */ animated?: boolean | undefined; /** Describes raw pixel input image data. See raw() for pixel ordering. */ diff --git a/lib/input.js b/lib/input.js index d9d29f3a..59de9a6c 100644 --- a/lib/input.js +++ b/lib/input.js @@ -27,9 +27,9 @@ const align = { * @private */ function _inputOptionsFromObject (obj) { - const { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient } = obj; - return [raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient].some(is.defined) - ? { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient } + const { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient, jp2Oneshot } = obj; + return [raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient, jp2Oneshot].some(is.defined) + ? { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient, jp2Oneshot } : undefined; } @@ -250,6 +250,14 @@ function _createInputDescriptor (input, inputOptions, containerOptions) { if (is.defined(inputOptions.pdfBackground)) { inputDescriptor.pdfBackground = this._getBackgroundColourOption(inputOptions.pdfBackground); } + // JP2 oneshot + if (is.defined(inputOptions.jp2Oneshot)) { + if (is.bool(inputOptions.jp2Oneshot)) { + inputDescriptor.jp2Oneshot = inputOptions.jp2Oneshot; + } else { + throw is.invalidParameterError('jp2Oneshot', 'boolean', inputOptions.jp2Oneshot); + } + } // Create new image if (is.defined(inputOptions.create)) { if ( diff --git a/src/common.cc b/src/common.cc index abbfe705..57ae4aef 100644 --- a/src/common.cc +++ b/src/common.cc @@ -113,6 +113,10 @@ namespace sharp { if (HasAttr(input, "pdfBackground")) { descriptor->pdfBackground = AttrAsVectorOfDouble(input, "pdfBackground"); } + // Use JPEG 2000 oneshot mode? + if (HasAttr(input, "jp2Oneshot")) { + descriptor->jp2Oneshot = AttrAsBool(input, "jp2Oneshot"); + } // Create new image if (HasAttr(input, "createChannels")) { descriptor->createChannels = AttrAsUint32(input, "createChannels"); @@ -434,6 +438,9 @@ namespace sharp { if (imageType == ImageType::PDF) { option->set("background", descriptor->pdfBackground); } + if (imageType == ImageType::JP2) { + option->set("oneshot", descriptor->jp2Oneshot); + } image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option); if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) { image = SetDensity(image, descriptor->density); @@ -541,6 +548,9 @@ namespace sharp { if (imageType == ImageType::PDF) { option->set("background", descriptor->pdfBackground); } + if (imageType == ImageType::JP2) { + option->set("oneshot", descriptor->jp2Oneshot); + } image = VImage::new_from_file(descriptor->file.data(), option); if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) { image = SetDensity(image, descriptor->density); diff --git a/src/common.h b/src/common.h index 3031c629..b926972b 100644 --- a/src/common.h +++ b/src/common.h @@ -78,6 +78,7 @@ namespace sharp { VipsAlign joinHalign; VipsAlign joinValign; std::vector pdfBackground; + bool jp2Oneshot; InputDescriptor(): autoOrient(false), @@ -120,7 +121,8 @@ namespace sharp { joinBackground{ 0.0, 0.0, 0.0, 255.0 }, joinHalign(VIPS_ALIGN_LOW), joinValign(VIPS_ALIGN_LOW), - pdfBackground{ 255.0, 255.0, 255.0, 255.0 } {} + pdfBackground{ 255.0, 255.0, 255.0, 255.0 }, + jp2Oneshot(false) {} }; // Convenience methods to access the attributes of a Napi::Object diff --git a/test/fixtures/index.js b/test/fixtures/index.js index f5b686ad..b69d6465 100644 --- a/test/fixtures/index.js +++ b/test/fixtures/index.js @@ -117,6 +117,7 @@ module.exports = { inputTiffFogra: getPath('fogra-0-100-100-0.tif'), // https://github.com/lovell/sharp/issues/4045 inputJp2: getPath('relax.jp2'), // https://www.fnordware.com/j2k/relax.jp2 + inputJp2TileParts: getPath('relax_tileparts.jp2'), // kdu_expand -i relax.jp2 -o relax-tmp.tif ; kdu_compress -i relax-tmp.tif -o relax_tileparts.jp2 -jp2_space sRGB Clayers=8 -rate 1.0,0.04 Stiles='{128,128}' ORGtparts=L ; rm relax-tmp.tif inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif inputGifGreyPlusAlpha: getPath('grey-plus-alpha.gif'), // http://i.imgur.com/gZ5jlmE.gif inputGifAnimated: getPath('rotating-squares.gif'), // CC0 https://loading.io/spinner/blocks/-rotating-squares-preloader-gif diff --git a/test/fixtures/relax_tileparts.jp2 b/test/fixtures/relax_tileparts.jp2 new file mode 100644 index 0000000000000000000000000000000000000000..62621c6890a56b6541ea625962e72beb8f18ad6d GIT binary patch literal 10218 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 { }); }); - it('Invalid JP2 chromaSubsampling value throws error', function () { - assert.throws(function () { - sharp().jpeg({ chromaSubsampling: '4:2:2' }); + it('can use the jp2Oneshot option to handle multi-part tiled JPEG 2000 file', async () => { + const outputJpg = fixtures.path('output.jpg'); + await assert.rejects( + () => sharp(fixtures.inputJp2TileParts).toFile(outputJpg) + ); + await assert.doesNotReject(async () => { + await sharp(fixtures.inputJp2TileParts, { jp2Oneshot: true }).toFile(outputJpg); + const { format, width, height } = await sharp(outputJpg).metadata(); + assert.strictEqual(format, 'jpeg'); + assert.strictEqual(width, 320); + assert.strictEqual(height, 240); }); }); + + it('Invalid JP2 chromaSubsampling value throws error', () => { + assert.throws( + () => sharp().jp2({ chromaSubsampling: '4:2:2' }), + /Expected one of 4:2:0, 4:4:4 but received 4:2:2 of type string/ + ); + }); } + + it('valid JP2 oneshot value does not throw error', () => { + assert.doesNotThrow( + () => sharp(fixtures.inputJp2TileParts, { jp2Oneshot: true }) + ); + }); + + it('invalid JP2 oneshot value throws error', () => { + assert.throws( + () => sharp(fixtures.inputJp2TileParts, { jp2Oneshot: 'fail' }), + /Expected boolean for jp2Oneshot but received fail of type string/ + ); + }); });