From 4cd3b66761325f6b8155f7ac777f957df622d9c7 Mon Sep 17 00:00:00 2001 From: YvesBos Date: Thu, 27 Apr 2017 00:47:29 +0800 Subject: [PATCH] Add support for squashing TIFF output to 1-bit (#783) --- lib/constructor.js | 1 + lib/output.js | 8 +++++++ src/pipeline.cc | 4 +++- src/pipeline.h | 2 ++ test/fixtures/8bit_depth.tiff | Bin 0 -> 652 bytes test/fixtures/index.js | 1 + test/unit/io.js | 38 ++++++++++++++++++++++++++++++++++ 7 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/8bit_depth.tiff diff --git a/lib/constructor.js b/lib/constructor.js index 96d6c0b7..af289602 100644 --- a/lib/constructor.js +++ b/lib/constructor.js @@ -171,6 +171,7 @@ const Sharp = function (input, options) { tiffQuality: 80, tiffCompression: 'jpeg', tiffPredictor: 'none', + tiffSquash: false, tileSize: 256, tileOverlap: 0, // Function to notify of queue length changes diff --git a/lib/output.js b/lib/output.js index d464c48b..f6b8afbe 100644 --- a/lib/output.js +++ b/lib/output.js @@ -213,6 +213,7 @@ function webp (options) { * @param {Boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format * @param {Boolean} [options.compression='jpeg'] - compression options: lzw, deflate, jpeg * @param {Boolean} [options.predictor='none'] - compression predictor options: none, horizontal, float + * @param {Boolean} [options.squash=false] - squash 8-bit images down to 1 bit * @returns {Sharp} * @throws {Error} Invalid options */ @@ -224,6 +225,13 @@ function tiff (options) { throw new Error('Invalid quality (integer, 1-100) ' + options.quality); } } + if (is.object(options) && is.defined(options.squash)) { + if (is.bool(options.squash)) { + this.options.tiffSquash = options.squash; + } else { + throw new Error('Invalid Value for squash ' + options.squash + ' Only Boolean Values allowed for options.squash.'); + } + } // compression if (is.defined(options) && is.defined(options.compression)) { if (is.string(options.compression) && is.inArray(options.compression, ['lzw', 'deflate', 'jpeg', 'none'])) { diff --git a/src/pipeline.cc b/src/pipeline.cc index 4856782f..b7968a0e 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -846,8 +846,9 @@ class PipelineWorker : public Nan::AsyncWorker { image.tiffsave(const_cast(baton->fileOut.data()), VImage::option() ->set("strip", !baton->withMetadata) ->set("Q", baton->tiffQuality) + ->set("squash", baton->tiffSquash) ->set("compression", baton->tiffCompression) - ->set("predictor", baton->tiffPredictor) ); + ->set("predictor", baton->tiffPredictor)); baton->formatOut = "tiff"; baton->channels = std::min(baton->channels, 3); } else if (baton->formatOut == "dz" || isDz || isDzZip) { @@ -1204,6 +1205,7 @@ NAN_METHOD(pipeline) { baton->webpLossless = AttrTo(options, "webpLossless"); baton->webpNearLossless = AttrTo(options, "webpNearLossless"); baton->tiffQuality = AttrTo(options, "tiffQuality"); + baton->tiffSquash = AttrTo(options, "tiffSquash"); // tiff compression options baton->tiffCompression = static_cast( vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION, diff --git a/src/pipeline.h b/src/pipeline.h index 029750a8..a7469395 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -106,6 +106,7 @@ struct PipelineBaton { int tiffQuality; VipsForeignTiffCompression tiffCompression; VipsForeignTiffPredictor tiffPredictor; + bool tiffSquash; std::string err; bool withMetadata; int withMetadataOrientation; @@ -176,6 +177,7 @@ struct PipelineBaton { tiffQuality(80), tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG), tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_NONE), + tiffSquash(false), withMetadata(false), withMetadataOrientation(-1), convKernelWidth(0), diff --git a/test/fixtures/8bit_depth.tiff b/test/fixtures/8bit_depth.tiff new file mode 100644 index 0000000000000000000000000000000000000000..6a50fbe5d814e03979e3468a158bb2800e11b165 GIT binary patch literal 652 zcmZ9JQ4T>t3`IL0B7TGf2^JuB^1f}yhYy?bYhzFEX)6;G)49DpZC_#>Z}w?cXPTr4 zh{+ErFIl8kTCCrAlFF>i1d60nq|acC3LLG9bxOr?I#~qWBAB5CB1!a+%Z*k*6$2o$ zAqU8l*wL%Ug%gKTy-(QD(}&bgXO79gebA{b=zYx0&Z%FnFJ137_29kt&iB;T Je)T=|egR!n-`M~F literal 0 HcmV?d00001 diff --git a/test/fixtures/index.js b/test/fixtures/index.js index eee48a16..5c092be0 100644 --- a/test/fixtures/index.js +++ b/test/fixtures/index.js @@ -85,6 +85,7 @@ module.exports = { inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm 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'), 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 inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg diff --git a/test/unit/io.js b/test/unit/io.js index 668fc720..800d90cc 100644 --- a/test/unit/io.js +++ b/test/unit/io.js @@ -861,6 +861,44 @@ describe('Input/output', function () { }); }); + it('Not squashing TIFF to a bit depth of 1 should not change the file size', function (done) { + const startSize = fs.statSync(fixtures.inputTiff8BitDepth).size; + sharp(fixtures.inputTiff8BitDepth) + .toColourspace('b-w') // can only squash 1 band uchar images + .tiff({ + squash: false, + compression: 'none' + }) + .toFile(fixtures.outputTiff, (err, info) => { + if (err) throw err; + assert.strictEqual('tiff', info.format); + assert(info.size === startSize); + fs.unlink(fixtures.outputTiff, done); + }); + }); + + it('Squashing TIFF to a bit depth of 1 should significantly reduce file size', function (done) { + const startSize = fs.statSync(fixtures.inputTiff8BitDepth).size; + sharp(fixtures.inputTiff8BitDepth) + .toColourspace('b-w') // can only squash 1 band uchar images + .tiff({ + squash: true, + compression: 'none' + }) + .toFile(fixtures.outputTiff, (err, info) => { + if (err) throw err; + assert.strictEqual('tiff', info.format); + assert(info.size < (startSize / 2)); + fs.unlink(fixtures.outputTiff, done); + }); + }); + + it('Invalid TIFF squash value throws error', function () { + assert.throws(function () { + sharp().tiff({ squash: 'true' }); + }); + }); + it('TIFF lzw compression with horizontal predictor shrinks test file', function (done) { const startSize = fs.statSync(fixtures.inputTiffUncompressed).size; sharp(fixtures.inputTiffUncompressed)