diff --git a/docs/api-constructor.md b/docs/api-constructor.md
index 32a79a011..65c0cd90a 100644
--- a/docs/api-constructor.md
+++ b/docs/api-constructor.md
@@ -41,6 +41,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 load JPEG 2000 images using [oneshot mode](https://github.com/libvips/libvips/issues/4205)|
| [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/lib/constructor.js b/lib/constructor.js
index 0ff85c667..93eb81ac1 100644
--- a/lib/constructor.js
+++ b/lib/constructor.js
@@ -140,6 +140,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 load JPEG 2000 images using [oneshot mode](https://github.com/libvips/libvips/issues/4205)
* @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.
@@ -295,6 +296,7 @@ const Sharp = function (input, options) {
jp2TileWidth: 512,
jp2Lossless: false,
jp2ChromaSubsampling: '4:4:4',
+ jp2Oneshot: false,
webpQuality: 80,
webpAlphaQuality: 100,
webpLossless: false,
diff --git a/lib/index.d.ts b/lib/index.d.ts
index 77415e235..cd9e78128 100644
--- a/lib/index.d.ts
+++ b/lib/index.d.ts
@@ -938,6 +938,8 @@ declare namespace sharp {
/** 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 read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default false) */
+ jp2Oneshot?: boolean | undefined;
+ /** Set to `true` to load JPEG 2000 images using [oneshot mode](https://github.com/libvips/libvips/issues/4205) **/
animated?: boolean | undefined;
/** Describes raw pixel input image data. See raw() for pixel ordering. */
raw?: CreateRaw | undefined;
diff --git a/lib/input.js b/lib/input.js
index abb10e53a..e5cca83da 100644
--- a/lib/input.js
+++ b/lib/input.js
@@ -24,9 +24,9 @@ const align = {
* @private
*/
function _inputOptionsFromObject (obj) {
- const { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground } = obj;
- return [raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground].some(is.defined)
- ? { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground }
+ const { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, jp2Oneshot } = obj;
+ return [raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, jp2Oneshot].some(is.defined)
+ ? { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, jp2Oneshot }
: undefined;
}
@@ -40,7 +40,8 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
limitInputPixels: Math.pow(0x3FFF, 2),
ignoreIcc: false,
unlimited: false,
- sequentialRead: true
+ sequentialRead: true,
+ jp2Oneshot: false
};
if (is.string(input)) {
// filesystem
@@ -226,6 +227,15 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
if (is.defined(inputOptions.pdfBackground)) {
this._setBackgroundColourOption('pdfBackground', inputOptions.pdfBackground);
}
+
+ 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 1d6a8f632..d7a635d65 100644
--- a/src/common.cc
+++ b/src/common.cc
@@ -113,6 +113,11 @@ 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");
@@ -409,6 +414,9 @@ namespace sharp {
if (imageType == ImageType::PDF) {
option->set("background", descriptor->pdfBackground);
}
+ if (imageType == ImageType::JP2 && descriptor->jp2Oneshot) {
+ option->set("oneshot", 1);
+ }
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);
@@ -516,6 +524,9 @@ namespace sharp {
if (imageType == ImageType::PDF) {
option->set("background", descriptor->pdfBackground);
}
+ if (imageType == ImageType::JP2 && descriptor->jp2Oneshot) {
+ option->set("oneshot", 1);
+ }
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 2f983d9db..a8748bf66 100644
--- a/src/common.h
+++ b/src/common.h
@@ -75,6 +75,7 @@ namespace sharp {
VipsTextWrap textWrap;
int textAutofitDpi;
std::vector pdfBackground;
+ bool jp2Oneshot;
InputDescriptor():
buffer(nullptr),
@@ -110,7 +111,8 @@ namespace sharp {
textSpacing(0),
textWrap(VIPS_TEXT_WRAP_WORD),
textAutofitDpi(0),
- 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 e4b8e266e..24796a634 100644
--- a/test/fixtures/index.js
+++ b/test/fixtures/index.js
@@ -36,138 +36,154 @@ async function fingerprint (image) {
}
module.exports = {
-
- inputJpgWithLandscapeExif1: getPath('Landscape_1.jpg'), // https://github.com/recurser/exif-orientation-examples
- inputJpgWithLandscapeExif2: getPath('Landscape_2.jpg'), // https://github.com/recurser/exif-orientation-examples
- inputJpgWithLandscapeExif3: getPath('Landscape_3.jpg'), // https://github.com/recurser/exif-orientation-examples
- inputJpgWithLandscapeExif4: getPath('Landscape_4.jpg'), // https://github.com/recurser/exif-orientation-examples
- inputJpgWithLandscapeExif5: getPath('Landscape_5.jpg'), // https://github.com/recurser/exif-orientation-examples
- inputJpgWithLandscapeExif6: getPath('Landscape_6.jpg'), // https://github.com/recurser/exif-orientation-examples
- inputJpgWithLandscapeExif7: getPath('Landscape_7.jpg'), // https://github.com/recurser/exif-orientation-examples
- inputJpgWithLandscapeExif8: getPath('Landscape_8.jpg'), // https://github.com/recurser/exif-orientation-examples
-
- inputJpgWithPortraitExif1: getPath('Portrait_1.jpg'), // https://github.com/recurser/exif-orientation-examples
- inputJpgWithPortraitExif2: getPath('Portrait_2.jpg'), // https://github.com/recurser/exif-orientation-examples
- inputJpgWithPortraitExif3: getPath('Portrait_3.jpg'), // https://github.com/recurser/exif-orientation-examples
- inputJpgWithPortraitExif4: getPath('Portrait_4.jpg'), // https://github.com/recurser/exif-orientation-examples
- inputJpgWithPortraitExif5: getPath('Portrait_5.jpg'), // https://github.com/recurser/exif-orientation-examples
- inputJpgWithPortraitExif6: getPath('Portrait_6.jpg'), // https://github.com/recurser/exif-orientation-examples
- inputJpgWithPortraitExif7: getPath('Portrait_7.jpg'), // https://github.com/recurser/exif-orientation-examples
- inputJpgWithPortraitExif8: getPath('Portrait_8.jpg'), // https://github.com/recurser/exif-orientation-examples
-
- inputJpg: getPath('2569067123_aca715a2ee_o.jpg'), // http://www.flickr.com/photos/grizdave/2569067123/
- inputJpgWithExif: getPath('Landscape_8.jpg'), // https://github.com/recurser/exif-orientation-examples/blob/master/Landscape_8.jpg
- inputJpgWithIptcAndXmp: getPath('Landscape_9.jpg'), // https://unsplash.com/photos/RWAIyGmgHTQ
- inputJpgWithExifMirroring: getPath('Landscape_5.jpg'), // https://github.com/recurser/exif-orientation-examples/blob/master/Landscape_5.jpg
- inputJpgWithGammaHoliness: getPath('gamma_dalai_lama_gray.jpg'), // http://www.4p8.com/eric.brasseur/gamma.html
- inputJpgWithCmykProfile: getPath('Channel_digital_image_CMYK_color.jpg'), // http://en.wikipedia.org/wiki/File:Channel_digital_image_CMYK_color.jpg
- inputJpgWithCmykNoProfile: getPath('Channel_digital_image_CMYK_color_no_profile.jpg'),
- inputJpgWithCorruptHeader: getPath('corrupt-header.jpg'),
- inputJpgWithLowContrast: getPath('low-contrast.jpg'), // http://www.flickr.com/photos/grizdave/2569067123/
- inputJpgLarge: getPath('giant-image.jpg'),
- inputJpg320x240: getPath('320x240.jpg'), // http://www.andrewault.net/2010/01/26/create-a-test-pattern-video-with-perl/
- inputJpgOverlayLayer2: getPath('alpha-layer-2-ink.jpg'),
- inputJpgTruncated: getPath('truncated.jpg'), // head -c 10000 2569067123_aca715a2ee_o.jpg > truncated.jpg
- inputJpgCenteredImage: getPath('centered_image.jpeg'),
- inputJpgRandom: getPath('random.jpg'), // convert -size 200x200 xc: +noise Random random.jpg
- inputJpgThRandom: getPath('thRandom.jpg'), // convert random.jpg -channel G -threshold 5% -separate +channel -negate thRandom.jpg
- inputJpgLossless: getPath('testimgl.jpg'), // Lossless JPEG from ftp://ftp.fu-berlin.de/unix/X11/graphics/ImageMagick/delegates/ljpeg-6b.tar.gz
-
- inputPng: getPath('50020484-00001.png'), // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
- inputPngGradients: getPath('gradients-rgb8.png'),
- inputPngWithTransparency: getPath('blackbug.png'), // public domain
- inputPngCompleteTransparency: getPath('full-transparent.png'),
- inputPngWithGreyAlpha: getPath('grey-8bit-alpha.png'),
- inputPngWithOneColor: getPath('2x2_fdcce6.png'),
- inputPngWithTransparency16bit: getPath('tbgn2c16.png'), // http://www.schaik.com/pngsuite/tbgn2c16.png
- inputPng16BitGreyAlpha: getPath('16-bit-grey-alpha.png'), // CC-BY-NC-SA florc http://www.colourlovers.com/pattern/50713/pat
- inputPngOverlayLayer0: getPath('alpha-layer-0-background.png'),
- inputPngOverlayLayer1: getPath('alpha-layer-1-fill.png'),
- inputPngAlphaPremultiplicationSmall: getPath('alpha-premultiply-1024x768-paper.png'),
- inputPngAlphaPremultiplicationLarge: getPath('alpha-premultiply-2048x1536-paper.png'),
- inputPngBooleanNoAlpha: getPath('bandbool.png'),
- inputPngTestJoinChannel: getPath('testJoinChannel.png'),
- inputPngTruncated: getPath('truncated.png'), // gm convert 2569067123_aca715a2ee_o.jpg -resize 320x240 saw.png ; head -c 10000 saw.png > truncated.png
- inputPngEmbed: getPath('embedgravitybird.png'), // Released to sharp under a CC BY 4.0
- inputPngRGBWithAlpha: getPath('2569067123_aca715a2ee_o.png'), // http://www.flickr.com/photos/grizdave/2569067123/ (same as inputJpg)
- inputPngImageInAlpha: getPath('image-in-alpha.png'), // https://github.com/lovell/sharp/issues/1597
- inputPngSolidAlpha: getPath('with-alpha.png'), // https://github.com/lovell/sharp/issues/1599
- inputPngP3: getPath('p3.png'), // https://github.com/lovell/sharp/issues/2862
- inputPngPalette: getPath('swiss.png'), // https://github.com/randy408/libspng/issues/188
- inputPngTrimIncludeAlpha: getPath('trim-mc.png'), // https://github.com/lovell/sharp/issues/2166
- inputPngTrimSpecificColour: getPath('Flag_of_the_Netherlands.png'), // https://commons.wikimedia.org/wiki/File:Flag_of_the_Netherlands.svg
- inputPngTrimSpecificColour16bit: getPath('Flag_of_the_Netherlands-16bit.png'), // convert Flag_of_the_Netherlands.png -depth 16 Flag_of_the_Netherlands-16bit.png
- inputPngTrimSpecificColourIncludeAlpha: getPath('Flag_of_the_Netherlands-alpha.png'), // convert Flag_of_the_Netherlands.png -alpha set -background none -channel A -evaluate multiply 0.5 +channel Flag_of_the_Netherlands-alpha.png
- inputPngUint32Limit: getPath('65536-uint32-limit.png'), // https://alexandre.alapetite.fr/doc-alex/large-image/
- inputPngWithProPhotoProfile: getPath('prophoto.png'),
-
- inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp
- inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
- inputWebPAnimated: getPath('rotating-squares.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
- inputWebPAnimatedLoop3: getPath('animated-loop-3.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
- inputWebPAnimatedBigHeight: getPath('big-height.webp'),
- inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
- inputTiffMultipage: getPath('G31D_MULTI.TIF'), // gm convert G31D.TIF -resize 50% G31D_2.TIF ; tiffcp G31D.TIF G31D_2.TIF G31D_MULTI.TIF
- inputTiffCielab: getPath('cielab-dagams.tiff'), // https://github.com/lovell/sharp/issues/646
- inputTiffUncompressed: getPath('uncompressed_tiff.tiff'), // https://code.google.com/archive/p/imagetestsuite/wikis/TIFFTestSuite.wiki file: 0c84d07e1b22b76f24cccc70d8788e4a.tif
- inputTiff8BitDepth: getPath('8bit_depth.tiff'),
- inputTifftagPhotoshop: getPath('tifftag-photoshop.tiff'), // https://github.com/lovell/sharp/issues/1600
- 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
- 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
- inputGifAnimatedLoop3: getPath('animated-loop-3.gif'), // CC-BY-SA-4.0 Petrus3743 https://commons.wikimedia.org/wiki/File:01-Goldener_Schnitt_Formel-Animation.gif
- inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg
- inputSvgSmallViewBox: getPath('circle.svg'),
- inputSvgWithEmbeddedImages: getPath('struct-image-04-t.svg'), // https://dev.w3.org/SVG/profiles/1.2T/test/svg/struct-image-04-t.svg
- inputAvif: getPath('sdr_cosmos12920_cicp1-13-6_yuv444_full_qp10.avif'), // CC by-nc-nd https://github.com/AOMediaCodec/av1-avif/tree/master/testFiles/Netflix
-
- inputJPGBig: getPath('flowers.jpeg'),
-
- inputPngStripesV: getPath('stripesV.png'),
- inputPngStripesH: getPath('stripesH.png'),
-
- inputJpgBooleanTest: getPath('booleanTest.jpg'),
-
- inputV: getPath('vfile.v'),
-
- inputJpgClahe: getPath('concert.jpg'), // public domain - https://www.flickr.com/photos/mars_/14389236779/
-
- testPattern: getPath('test-pattern.png'),
-
- inputPngWithTransparent: getPath('d.png'),
+ inputJpgWithLandscapeExif1: getPath("Landscape_1.jpg"), // https://github.com/recurser/exif-orientation-examples
+ inputJpgWithLandscapeExif2: getPath("Landscape_2.jpg"), // https://github.com/recurser/exif-orientation-examples
+ inputJpgWithLandscapeExif3: getPath("Landscape_3.jpg"), // https://github.com/recurser/exif-orientation-examples
+ inputJpgWithLandscapeExif4: getPath("Landscape_4.jpg"), // https://github.com/recurser/exif-orientation-examples
+ inputJpgWithLandscapeExif5: getPath("Landscape_5.jpg"), // https://github.com/recurser/exif-orientation-examples
+ inputJpgWithLandscapeExif6: getPath("Landscape_6.jpg"), // https://github.com/recurser/exif-orientation-examples
+ inputJpgWithLandscapeExif7: getPath("Landscape_7.jpg"), // https://github.com/recurser/exif-orientation-examples
+ inputJpgWithLandscapeExif8: getPath("Landscape_8.jpg"), // https://github.com/recurser/exif-orientation-examples
+
+ inputJpgWithPortraitExif1: getPath("Portrait_1.jpg"), // https://github.com/recurser/exif-orientation-examples
+ inputJpgWithPortraitExif2: getPath("Portrait_2.jpg"), // https://github.com/recurser/exif-orientation-examples
+ inputJpgWithPortraitExif3: getPath("Portrait_3.jpg"), // https://github.com/recurser/exif-orientation-examples
+ inputJpgWithPortraitExif4: getPath("Portrait_4.jpg"), // https://github.com/recurser/exif-orientation-examples
+ inputJpgWithPortraitExif5: getPath("Portrait_5.jpg"), // https://github.com/recurser/exif-orientation-examples
+ inputJpgWithPortraitExif6: getPath("Portrait_6.jpg"), // https://github.com/recurser/exif-orientation-examples
+ inputJpgWithPortraitExif7: getPath("Portrait_7.jpg"), // https://github.com/recurser/exif-orientation-examples
+ inputJpgWithPortraitExif8: getPath("Portrait_8.jpg"), // https://github.com/recurser/exif-orientation-examples
+
+ inputJpg: getPath("2569067123_aca715a2ee_o.jpg"), // http://www.flickr.com/photos/grizdave/2569067123/
+ inputJpgWithExif: getPath("Landscape_8.jpg"), // https://github.com/recurser/exif-orientation-examples/blob/master/Landscape_8.jpg
+ inputJpgWithIptcAndXmp: getPath("Landscape_9.jpg"), // https://unsplash.com/photos/RWAIyGmgHTQ
+ inputJpgWithExifMirroring: getPath("Landscape_5.jpg"), // https://github.com/recurser/exif-orientation-examples/blob/master/Landscape_5.jpg
+ inputJpgWithGammaHoliness: getPath("gamma_dalai_lama_gray.jpg"), // http://www.4p8.com/eric.brasseur/gamma.html
+ inputJpgWithCmykProfile: getPath("Channel_digital_image_CMYK_color.jpg"), // http://en.wikipedia.org/wiki/File:Channel_digital_image_CMYK_color.jpg
+ inputJpgWithCmykNoProfile: getPath(
+ "Channel_digital_image_CMYK_color_no_profile.jpg"
+ ),
+ inputJpgWithCorruptHeader: getPath("corrupt-header.jpg"),
+ inputJpgWithLowContrast: getPath("low-contrast.jpg"), // http://www.flickr.com/photos/grizdave/2569067123/
+ inputJpgLarge: getPath("giant-image.jpg"),
+ inputJpg320x240: getPath("320x240.jpg"), // http://www.andrewault.net/2010/01/26/create-a-test-pattern-video-with-perl/
+ inputJpgOverlayLayer2: getPath("alpha-layer-2-ink.jpg"),
+ inputJpgTruncated: getPath("truncated.jpg"), // head -c 10000 2569067123_aca715a2ee_o.jpg > truncated.jpg
+ inputJpgCenteredImage: getPath("centered_image.jpeg"),
+ inputJpgRandom: getPath("random.jpg"), // convert -size 200x200 xc: +noise Random random.jpg
+ inputJpgThRandom: getPath("thRandom.jpg"), // convert random.jpg -channel G -threshold 5% -separate +channel -negate thRandom.jpg
+ inputJpgLossless: getPath("testimgl.jpg"), // Lossless JPEG from ftp://ftp.fu-berlin.de/unix/X11/graphics/ImageMagick/delegates/ljpeg-6b.tar.gz
+
+ inputPng: getPath("50020484-00001.png"), // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
+ inputPngGradients: getPath("gradients-rgb8.png"),
+ inputPngWithTransparency: getPath("blackbug.png"), // public domain
+ inputPngCompleteTransparency: getPath("full-transparent.png"),
+ inputPngWithGreyAlpha: getPath("grey-8bit-alpha.png"),
+ inputPngWithOneColor: getPath("2x2_fdcce6.png"),
+ inputPngWithTransparency16bit: getPath("tbgn2c16.png"), // http://www.schaik.com/pngsuite/tbgn2c16.png
+ inputPng16BitGreyAlpha: getPath("16-bit-grey-alpha.png"), // CC-BY-NC-SA florc http://www.colourlovers.com/pattern/50713/pat
+ inputPngOverlayLayer0: getPath("alpha-layer-0-background.png"),
+ inputPngOverlayLayer1: getPath("alpha-layer-1-fill.png"),
+ inputPngAlphaPremultiplicationSmall: getPath(
+ "alpha-premultiply-1024x768-paper.png"
+ ),
+ inputPngAlphaPremultiplicationLarge: getPath(
+ "alpha-premultiply-2048x1536-paper.png"
+ ),
+ inputPngBooleanNoAlpha: getPath("bandbool.png"),
+ inputPngTestJoinChannel: getPath("testJoinChannel.png"),
+ inputPngTruncated: getPath("truncated.png"), // gm convert 2569067123_aca715a2ee_o.jpg -resize 320x240 saw.png ; head -c 10000 saw.png > truncated.png
+ inputPngEmbed: getPath("embedgravitybird.png"), // Released to sharp under a CC BY 4.0
+ inputPngRGBWithAlpha: getPath("2569067123_aca715a2ee_o.png"), // http://www.flickr.com/photos/grizdave/2569067123/ (same as inputJpg)
+ inputPngImageInAlpha: getPath("image-in-alpha.png"), // https://github.com/lovell/sharp/issues/1597
+ inputPngSolidAlpha: getPath("with-alpha.png"), // https://github.com/lovell/sharp/issues/1599
+ inputPngP3: getPath("p3.png"), // https://github.com/lovell/sharp/issues/2862
+ inputPngPalette: getPath("swiss.png"), // https://github.com/randy408/libspng/issues/188
+ inputPngTrimIncludeAlpha: getPath("trim-mc.png"), // https://github.com/lovell/sharp/issues/2166
+ inputPngTrimSpecificColour: getPath("Flag_of_the_Netherlands.png"), // https://commons.wikimedia.org/wiki/File:Flag_of_the_Netherlands.svg
+ inputPngTrimSpecificColour16bit: getPath("Flag_of_the_Netherlands-16bit.png"), // convert Flag_of_the_Netherlands.png -depth 16 Flag_of_the_Netherlands-16bit.png
+ inputPngTrimSpecificColourIncludeAlpha: getPath(
+ "Flag_of_the_Netherlands-alpha.png"
+ ), // convert Flag_of_the_Netherlands.png -alpha set -background none -channel A -evaluate multiply 0.5 +channel Flag_of_the_Netherlands-alpha.png
+ inputPngUint32Limit: getPath("65536-uint32-limit.png"), // https://alexandre.alapetite.fr/doc-alex/large-image/
+ inputPngWithProPhotoProfile: getPath("prophoto.png"),
+
+ inputWebP: getPath("4.webp"), // http://www.gstatic.com/webp/gallery/4.webp
+ inputWebPWithTransparency: getPath("5_webp_a.webp"), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
+ inputWebPAnimated: getPath("rotating-squares.webp"), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
+ inputWebPAnimatedLoop3: getPath("animated-loop-3.webp"), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
+ inputWebPAnimatedBigHeight: getPath("big-height.webp"),
+ inputTiff: getPath("G31D.TIF"), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
+ inputTiffMultipage: getPath("G31D_MULTI.TIF"), // gm convert G31D.TIF -resize 50% G31D_2.TIF ; tiffcp G31D.TIF G31D_2.TIF G31D_MULTI.TIF
+ inputTiffCielab: getPath("cielab-dagams.tiff"), // https://github.com/lovell/sharp/issues/646
+ inputTiffUncompressed: getPath("uncompressed_tiff.tiff"), // https://code.google.com/archive/p/imagetestsuite/wikis/TIFFTestSuite.wiki file: 0c84d07e1b22b76f24cccc70d8788e4a.tif
+ inputTiff8BitDepth: getPath("8bit_depth.tiff"),
+ inputTifftagPhotoshop: getPath("tifftag-photoshop.tiff"), // https://github.com/lovell/sharp/issues/1600
+ 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
+ inputGifAnimatedLoop3: getPath("animated-loop-3.gif"), // CC-BY-SA-4.0 Petrus3743 https://commons.wikimedia.org/wiki/File:01-Goldener_Schnitt_Formel-Animation.gif
+ inputSvg: getPath("check.svg"), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg
+ inputSvgSmallViewBox: getPath("circle.svg"),
+ inputSvgWithEmbeddedImages: getPath("struct-image-04-t.svg"), // https://dev.w3.org/SVG/profiles/1.2T/test/svg/struct-image-04-t.svg
+ inputAvif: getPath("sdr_cosmos12920_cicp1-13-6_yuv444_full_qp10.avif"), // CC by-nc-nd https://github.com/AOMediaCodec/av1-avif/tree/master/testFiles/Netflix
+
+ inputJPGBig: getPath("flowers.jpeg"),
+
+ inputPngStripesV: getPath("stripesV.png"),
+ inputPngStripesH: getPath("stripesH.png"),
+
+ inputJpgBooleanTest: getPath("booleanTest.jpg"),
+
+ inputV: getPath("vfile.v"),
+
+ inputJpgClahe: getPath("concert.jpg"), // public domain - https://www.flickr.com/photos/mars_/14389236779/
+
+ testPattern: getPath("test-pattern.png"),
+
+ inputPngWithTransparent: getPath("d.png"),
// Path for tests requiring human inspection
path: getPath,
// Path for expected output images
expected: function (filename) {
- return getPath(path.join('expected', filename));
+ return getPath(path.join("expected", filename));
},
// Verify similarity of expected vs actual images via fingerprint
// Specify distance threshold using `options={threshold: 42}`, default
// `threshold` is 5;
- assertSimilar: async function (expectedImage, actualImage, options, callback) {
- if (typeof options === 'function') {
+ assertSimilar: async function (
+ expectedImage,
+ actualImage,
+ options,
+ callback
+ ) {
+ if (typeof options === "function") {
callback = options;
options = {};
}
- if (typeof options === 'undefined' || options === null) {
+ if (typeof options === "undefined" || options === null) {
options = {};
}
- if (options.threshold === null || typeof options.threshold === 'undefined') {
+ if (
+ options.threshold === null ||
+ typeof options.threshold === "undefined"
+ ) {
options.threshold = 5; // ~7% threshold
}
- if (typeof options.threshold !== 'number') {
- throw new TypeError('`options.threshold` must be a number');
+ if (typeof options.threshold !== "number") {
+ throw new TypeError("`options.threshold` must be a number");
}
try {
const [expectedFingerprint, actualFingerprint] = await Promise.all([
fingerprint(expectedImage),
- fingerprint(actualImage)
+ fingerprint(actualImage),
]);
let distance = 0;
for (let i = 0; i < 64; i++) {
@@ -176,7 +192,9 @@ module.exports = {
}
}
if (distance > options.threshold) {
- throw new Error(`Expected maximum similarity distance: ${options.threshold}. Actual: ${distance}.`);
+ throw new Error(
+ `Expected maximum similarity distance: ${options.threshold}. Actual: ${distance}.`
+ );
}
} catch (err) {
if (callback) {
@@ -189,21 +207,33 @@ module.exports = {
}
},
- assertMaxColourDistance: function (actualImagePath, expectedImagePath, acceptedDistance) {
- if (typeof actualImagePath !== 'string') {
- throw new TypeError('`actualImagePath` must be a string; got ' + actualImagePath);
+ assertMaxColourDistance: function (
+ actualImagePath,
+ expectedImagePath,
+ acceptedDistance
+ ) {
+ if (typeof actualImagePath !== "string") {
+ throw new TypeError(
+ "`actualImagePath` must be a string; got " + actualImagePath
+ );
}
- if (typeof expectedImagePath !== 'string') {
- throw new TypeError('`expectedImagePath` must be a string; got ' + expectedImagePath);
+ if (typeof expectedImagePath !== "string") {
+ throw new TypeError(
+ "`expectedImagePath` must be a string; got " + expectedImagePath
+ );
}
- if (typeof acceptedDistance !== 'number') {
+ if (typeof acceptedDistance !== "number") {
// Default threshold
acceptedDistance = 1;
}
const distance = maxColourDistance(actualImagePath, expectedImagePath);
if (distance > acceptedDistance) {
- throw new Error('Expected maximum absolute distance of ' + acceptedDistance + ', actual ' + distance);
+ throw new Error(
+ "Expected maximum absolute distance of " +
+ acceptedDistance +
+ ", actual " +
+ distance
+ );
}
- }
-
+ },
};
diff --git a/test/fixtures/relax_tileparts.jp2 b/test/fixtures/relax_tileparts.jp2
new file mode 100644
index 000000000..62621c689
Binary files /dev/null and b/test/fixtures/relax_tileparts.jp2 differ
diff --git a/test/unit/io.js b/test/unit/io.js
index 98b96a571..07256a7aa 100644
--- a/test/unit/io.js
+++ b/test/unit/io.js
@@ -722,6 +722,23 @@ describe('Input/output', function () {
b1: 20,
b2: 25
});
+
+ });
+
+ it('can use the jp2Oneshot option to handle multi-part tiled JPEG 2000 file', async () => {
+ assert.rejects(async function() {
+ await sharp(fixtures.inputJp2TileParts)
+ .toFile(outputJpg);
+ });
+
+ await assert.doesNotReject(async function() {
+ await sharp(fixtures.inputJp2TileParts, { jp2Oneshot: true })
+ .toFile(outputJpg);
+ const meta = await sharp(outputJpg).metadata();
+ assert.strictEqual(meta.format, 'jpeg');
+ assert.strictEqual(meta.width, 320);
+ assert.strictEqual(meta.height, 240);
+ });
});
describe('Switch off safety limits for certain formats', () => {
@@ -914,6 +931,15 @@ describe('Input/output', function () {
sharp({ pdfBackground: { red: 0, green: 255, blue: 0 } });
}, /Unable to parse color from object/);
});
+ it('Valid jp2Oneshot property (bool)', function () {
+ sharp({ jp2Oneshot: true });
+ sharp({ jp2Oneshot: false });
+ })
+ it('Invalid jp2Oneshot property (string) throws', function () {
+ assert.throws(function () {
+ sharp({ jp2Oneshot: 'true' });
+ }, /Expected boolean for jp2Oneshot but received true of type string/);
+ });
});
it('Fails when writing to missing directory', async () => {