diff --git a/lib/input.js b/lib/input.js index d0436f61..f29e24e6 100644 --- a/lib/input.js +++ b/lib/input.js @@ -57,6 +57,12 @@ function _createInputDescriptor (input, inputOptions, containerOptions) { throw new Error('Expected width, height and channels for raw pixel input'); } } + // Page input for multi-page TIFF + if (is.defined(inputOptions.page)) { + if (is.integer(inputOptions.page) && is.inRange(inputOptions.page, 0, 100000)) { + inputDescriptor.page = inputOptions.page; + } + } // Create new image if (is.defined(inputOptions.create)) { if ( diff --git a/package.json b/package.json index 6d00e105..c96d766c 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,8 @@ "Marcel Bretschneider ", "Andrea Bianco ", "Rik Heywood ", - "Thomas Parisot " + "Thomas Parisot ", + "Nathan Graves " ], "scripts": { "install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)", diff --git a/src/common.cc b/src/common.cc index 3060ad2a..543f80b8 100644 --- a/src/common.cc +++ b/src/common.cc @@ -63,6 +63,10 @@ namespace sharp { descriptor->rawWidth = AttrTo(input, "rawWidth"); descriptor->rawHeight = AttrTo(input, "rawHeight"); } + // Page input for multi-page TIFF + if (HasAttr(input, "page")) { + descriptor->page = AttrTo(input, "page"); + } // Create new image if (HasAttr(input, "createChannels")) { descriptor->createChannels = AttrTo(input, "createChannels"); @@ -229,6 +233,9 @@ namespace sharp { if (imageType == ImageType::MAGICK) { option->set("density", std::to_string(descriptor->density).data()); } + if (imageType == ImageType::TIFF) { + option->set("page", descriptor->page); + } image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option); if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) { SetDensity(image, descriptor->density); @@ -268,6 +275,9 @@ namespace sharp { if (imageType == ImageType::MAGICK) { option->set("density", std::to_string(descriptor->density).data()); } + if (imageType == ImageType::TIFF) { + option->set("page", descriptor->page); + } image = VImage::new_from_file(descriptor->file.data(), option); if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) { SetDensity(image, descriptor->density); diff --git a/src/common.h b/src/common.h index c5cccb63..6a788f10 100644 --- a/src/common.h +++ b/src/common.h @@ -53,6 +53,7 @@ namespace sharp { int rawChannels; int rawWidth; int rawHeight; + int page; int createChannels; int createWidth; int createHeight; @@ -66,6 +67,7 @@ namespace sharp { rawChannels(0), rawWidth(0), rawHeight(0), + page(0), createChannels(0), createWidth(0), createHeight(0) { diff --git a/test/fixtures/G31D_MULTI.TIF b/test/fixtures/G31D_MULTI.TIF new file mode 100644 index 00000000..017195d9 Binary files /dev/null and b/test/fixtures/G31D_MULTI.TIF differ diff --git a/test/fixtures/index.js b/test/fixtures/index.js index efbbb916..2504af75 100644 --- a/test/fixtures/index.js +++ b/test/fixtures/index.js @@ -94,6 +94,7 @@ module.exports = { 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 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'), diff --git a/test/unit/io.js b/test/unit/io.js index 7c64d934..64d5d496 100644 --- a/test/unit/io.js +++ b/test/unit/io.js @@ -528,6 +528,22 @@ describe('Input/output', function () { }); }); + it('TIFF file input with invalid page fails gracefully', function (done) { + sharp(fixtures.inputTiffMultipage, { page: 2 }) + .toBuffer(function (err) { + assert.strictEqual(true, !!err); + done(); + }); + }); + + it('TIFF buffer input with invalid page fails gracefully', function (done) { + sharp(fs.readFileSync(fixtures.inputTiffMultipage), { page: 2 }) + .toBuffer(function (err) { + assert.strictEqual(true, !!err); + done(); + }); + }); + describe('Output filename with unknown extension', function () { it('Match JPEG input', function (done) { sharp(fixtures.inputJpg) @@ -880,6 +896,53 @@ describe('Input/output', function () { }); }); + it('Load multi-page TIFF\'s from file', function (done) { + sharp(fixtures.inputTiffMultipage) // defaults to page 0 + .jpeg() + .toBuffer(function (err, defaultData, defaultInfo) { + if (err) throw err; + assert.strictEqual(true, defaultData.length > 0); + assert.strictEqual(defaultData.length, defaultInfo.size); + assert.strictEqual('jpeg', defaultInfo.format); + + sharp(fixtures.inputTiffMultipage, { page: 1 }) // 50%-scale copy of page 0 + .jpeg() + .toBuffer(function (err, scaledData, scaledInfo) { + if (err) throw err; + assert.strictEqual(true, scaledData.length > 0); + assert.strictEqual(scaledData.length, scaledInfo.size); + assert.strictEqual('jpeg', scaledInfo.format); + assert.strictEqual(defaultInfo.width, scaledInfo.width * 2); + assert.strictEqual(defaultInfo.height, scaledInfo.height * 2); + done(); + }); + }); + }); + + it('Load multi-page TIFF\'s from Buffer', function (done) { + const inputTiffBuffer = fs.readFileSync(fixtures.inputTiffMultipage); + sharp(inputTiffBuffer) // defaults to page 0 + .jpeg() + .toBuffer(function (err, defaultData, defaultInfo) { + if (err) throw err; + assert.strictEqual(true, defaultData.length > 0); + assert.strictEqual(defaultData.length, defaultInfo.size); + assert.strictEqual('jpeg', defaultInfo.format); + + sharp(inputTiffBuffer, { page: 1 }) // 50%-scale copy of page 0 + .jpeg() + .toBuffer(function (err, scaledData, scaledInfo) { + if (err) throw err; + assert.strictEqual(true, scaledData.length > 0); + assert.strictEqual(scaledData.length, scaledInfo.size); + assert.strictEqual('jpeg', scaledInfo.format); + assert.strictEqual(defaultInfo.width, scaledInfo.width * 2); + assert.strictEqual(defaultInfo.height, scaledInfo.height * 2); + done(); + }); + }); + }); + it('Save TIFF to Buffer', function (done) { sharp(fixtures.inputTiff) .resize(320, 240)