diff --git a/docs/api-constructor.md b/docs/api-constructor.md index 6604edff..f59661e1 100644 --- a/docs/api-constructor.md +++ b/docs/api-constructor.md @@ -12,7 +12,8 @@ - `options.failOnError` **[Boolean][4]** by default halt processing and raise an error when loading invalid images. Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid. (optional, default `true`) - `options.density` **[Number][5]** number representing the DPI for vector images. (optional, default `72`) - - `options.page` **[Number][5]** page number to extract for multi-page input (GIF, TIFF, PDF) (optional, default `0`) + - `options.pages` **[Number][5]** number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages. (optional, default `1`) + - `options.page` **[Number][5]** page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. (optional, default `0`) - `options.raw` **[Object][3]?** describes raw pixel input image data. See `raw()` for pixel ordering. - `options.raw.width` **[Number][5]?** - `options.raw.height` **[Number][5]?** diff --git a/docs/changelog.md b/docs/changelog.md index 676f0bb4..c4363339 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -12,6 +12,9 @@ Requires libvips v8.7.4. * Add `composite` operation supporting multiple images and blend modes; deprecate `overlayWith`. [#728](https://github.com/lovell/sharp/issues/728) +* Add support for `pages` input option for multi-page input. + [#1566](https://github.com/lovell/sharp/issues/1566) + * Add support for `page` input option to GIF and PDF. [#1595](https://github.com/lovell/sharp/pull/1595) [@ramiel](https://github.com/ramiel) diff --git a/lib/constructor.js b/lib/constructor.js index b156ccdd..5ac27481 100644 --- a/lib/constructor.js +++ b/lib/constructor.js @@ -64,7 +64,8 @@ const debuglog = util.debuglog('sharp'); * @param {Boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images. * Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid. * @param {Number} [options.density=72] - number representing the DPI for vector images. - * @param {Number} [options.page=0] - page number to extract for multi-page input (GIF, TIFF, PDF) + * @param {Number} [options.pages=1] - number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages. + * @param {Number} [options.page=0] - page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. * @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering. * @param {Number} [options.raw.width] * @param {Number} [options.raw.height] diff --git a/lib/input.js b/lib/input.js index ad898e6f..4e1a48bd 100644 --- a/lib/input.js +++ b/lib/input.js @@ -57,7 +57,12 @@ function _createInputDescriptor (input, inputOptions, containerOptions) { throw new Error('Expected width, height and channels for raw pixel input'); } } - // Page input for multi-page images (GIF, TIFF, PDF) + // Multi-page input (GIF, TIFF, PDF) + if (is.defined(inputOptions.pages)) { + if (is.integer(inputOptions.pages) && is.inRange(inputOptions.pages, -1, 100000)) { + inputDescriptor.pages = inputOptions.pages; + } + } if (is.defined(inputOptions.page)) { if (is.integer(inputOptions.page) && is.inRange(inputOptions.page, 0, 100000)) { inputDescriptor.page = inputOptions.page; diff --git a/src/common.cc b/src/common.cc index 6e9b091e..7a43b466 100644 --- a/src/common.cc +++ b/src/common.cc @@ -71,7 +71,10 @@ namespace sharp { descriptor->rawWidth = AttrTo(input, "rawWidth"); descriptor->rawHeight = AttrTo(input, "rawHeight"); } - // Page input for multi-page TIFF, PDF + // Multi-page input (GIF, TIFF, PDF) + if (HasAttr(input, "pages")) { + descriptor->pages = AttrTo(input, "pages"); + } if (HasAttr(input, "page")) { descriptor->page = AttrTo(input, "page"); } @@ -254,7 +257,8 @@ namespace sharp { option->set("density", std::to_string(descriptor->density).data()); } if (ImageTypeSupportsPage(imageType)) { - option->set("page", descriptor->page); + option->set("n", descriptor->pages); + 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) { @@ -299,7 +303,8 @@ namespace sharp { option->set("density", std::to_string(descriptor->density).data()); } if (ImageTypeSupportsPage(imageType)) { - option->set("page", descriptor->page); + option->set("n", descriptor->pages); + option->set("page", descriptor->page); } image = VImage::new_from_file(descriptor->file.data(), option); if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) { diff --git a/src/common.h b/src/common.h index 4da7f361..40705af2 100644 --- a/src/common.h +++ b/src/common.h @@ -53,6 +53,7 @@ namespace sharp { int rawChannels; int rawWidth; int rawHeight; + int pages; int page; int createChannels; int createWidth; @@ -67,6 +68,7 @@ namespace sharp { rawChannels(0), rawWidth(0), rawHeight(0), + pages(1), page(0), createChannels(0), createWidth(0), diff --git a/test/fixtures/index.js b/test/fixtures/index.js index 3b335065..c4f7c37f 100644 --- a/test/fixtures/index.js +++ b/test/fixtures/index.js @@ -100,6 +100,7 @@ module.exports = { inputTiff8BitDepth: getPath('8bit_depth.tiff'), 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 inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg inputSvgWithEmbeddedImages: getPath('struct-image-04-t.svg'), // https://dev.w3.org/SVG/profiles/1.2T/test/svg/struct-image-04-t.svg diff --git a/test/fixtures/rotating-squares.gif b/test/fixtures/rotating-squares.gif new file mode 100644 index 00000000..4d6202c7 Binary files /dev/null and b/test/fixtures/rotating-squares.gif differ diff --git a/test/unit/gif.js b/test/unit/gif.js new file mode 100644 index 00000000..6c69d34c --- /dev/null +++ b/test/unit/gif.js @@ -0,0 +1,64 @@ +'use strict'; + +const fs = require('fs'); +const assert = require('assert'); + +const sharp = require('../../'); +const fixtures = require('../fixtures'); + +describe('GIF input', () => { + it('GIF Buffer to JPEG Buffer', () => + sharp(fs.readFileSync(fixtures.inputGif)) + .resize(8, 4) + .jpeg() + .toBuffer({ resolveWithObject: true }) + .then(({ data, info }) => { + assert.strictEqual(true, data.length > 0); + assert.strictEqual(data.length, info.size); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(8, info.width); + assert.strictEqual(4, info.height); + }) + ); + + it('2 channel GIF file to PNG Buffer', () => + sharp(fixtures.inputGifGreyPlusAlpha) + .resize(8, 4) + .png() + .toBuffer({ resolveWithObject: true }) + .then(({ data, info }) => { + assert.strictEqual(true, data.length > 0); + assert.strictEqual(data.length, info.size); + assert.strictEqual('png', info.format); + assert.strictEqual(8, info.width); + assert.strictEqual(4, info.height); + assert.strictEqual(4, info.channels); + }) + ); + + it('Animated GIF first page to PNG', () => + sharp(fixtures.inputGifAnimated) + .toBuffer({ resolveWithObject: true }) + .then(({ data, info }) => { + assert.strictEqual(true, data.length > 0); + assert.strictEqual(data.length, info.size); + assert.strictEqual('png', info.format); + assert.strictEqual(80, info.width); + assert.strictEqual(80, info.height); + assert.strictEqual(4, info.channels); + }) + ); + + it('Animated GIF all pages to PNG "toilet roll"', () => + sharp(fixtures.inputGifAnimated, { pages: -1 }) + .toBuffer({ resolveWithObject: true }) + .then(({ data, info }) => { + assert.strictEqual(true, data.length > 0); + assert.strictEqual(data.length, info.size); + assert.strictEqual('png', info.format); + assert.strictEqual(80, info.width); + assert.strictEqual(2400, info.height); + assert.strictEqual(4, info.channels); + }) + ); +}); diff --git a/test/unit/io.js b/test/unit/io.js index eb4d05dd..6161ddaf 100644 --- a/test/unit/io.js +++ b/test/unit/io.js @@ -517,37 +517,6 @@ describe('Input/output', function () { }); }); - it('Load GIF from Buffer', function (done) { - const inputGifBuffer = fs.readFileSync(fixtures.inputGif); - sharp(inputGifBuffer) - .resize(320, 240) - .jpeg() - .toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(true, data.length > 0); - assert.strictEqual(data.length, info.size); - assert.strictEqual('jpeg', info.format); - assert.strictEqual(320, info.width); - assert.strictEqual(240, info.height); - done(); - }); - }); - - it('Load GIF grey+alpha from file, auto convert to PNG', function (done) { - sharp(fixtures.inputGifGreyPlusAlpha) - .resize(8, 4) - .toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(true, data.length > 0); - assert.strictEqual(data.length, info.size); - assert.strictEqual('png', info.format); - assert.strictEqual(8, info.width); - assert.strictEqual(4, info.height); - assert.strictEqual(4, info.channels); - done(); - }); - }); - it('Load Vips V file', function (done) { sharp(fixtures.inputV) .jpeg()