Add support for squashing TIFF output to 1-bit (#783)

This commit is contained in:
YvesBos 2017-04-27 00:47:29 +08:00 committed by Lovell Fuller
parent 567e3dd258
commit 4cd3b66761
7 changed files with 53 additions and 1 deletions

View File

@ -171,6 +171,7 @@ const Sharp = function (input, options) {
tiffQuality: 80, tiffQuality: 80,
tiffCompression: 'jpeg', tiffCompression: 'jpeg',
tiffPredictor: 'none', tiffPredictor: 'none',
tiffSquash: false,
tileSize: 256, tileSize: 256,
tileOverlap: 0, tileOverlap: 0,
// Function to notify of queue length changes // Function to notify of queue length changes

View File

@ -213,6 +213,7 @@ function webp (options) {
* @param {Boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format * @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.compression='jpeg'] - compression options: lzw, deflate, jpeg
* @param {Boolean} [options.predictor='none'] - compression predictor options: none, horizontal, float * @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} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
@ -224,6 +225,13 @@ function tiff (options) {
throw new Error('Invalid quality (integer, 1-100) ' + options.quality); 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 // compression
if (is.defined(options) && is.defined(options.compression)) { if (is.defined(options) && is.defined(options.compression)) {
if (is.string(options.compression) && is.inArray(options.compression, ['lzw', 'deflate', 'jpeg', 'none'])) { if (is.string(options.compression) && is.inArray(options.compression, ['lzw', 'deflate', 'jpeg', 'none'])) {

View File

@ -846,6 +846,7 @@ class PipelineWorker : public Nan::AsyncWorker {
image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->tiffQuality) ->set("Q", baton->tiffQuality)
->set("squash", baton->tiffSquash)
->set("compression", baton->tiffCompression) ->set("compression", baton->tiffCompression)
->set("predictor", baton->tiffPredictor)); ->set("predictor", baton->tiffPredictor));
baton->formatOut = "tiff"; baton->formatOut = "tiff";
@ -1204,6 +1205,7 @@ NAN_METHOD(pipeline) {
baton->webpLossless = AttrTo<bool>(options, "webpLossless"); baton->webpLossless = AttrTo<bool>(options, "webpLossless");
baton->webpNearLossless = AttrTo<bool>(options, "webpNearLossless"); baton->webpNearLossless = AttrTo<bool>(options, "webpNearLossless");
baton->tiffQuality = AttrTo<uint32_t>(options, "tiffQuality"); baton->tiffQuality = AttrTo<uint32_t>(options, "tiffQuality");
baton->tiffSquash = AttrTo<bool>(options, "tiffSquash");
// tiff compression options // tiff compression options
baton->tiffCompression = static_cast<VipsForeignTiffCompression>( baton->tiffCompression = static_cast<VipsForeignTiffCompression>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION, vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION,

View File

@ -106,6 +106,7 @@ struct PipelineBaton {
int tiffQuality; int tiffQuality;
VipsForeignTiffCompression tiffCompression; VipsForeignTiffCompression tiffCompression;
VipsForeignTiffPredictor tiffPredictor; VipsForeignTiffPredictor tiffPredictor;
bool tiffSquash;
std::string err; std::string err;
bool withMetadata; bool withMetadata;
int withMetadataOrientation; int withMetadataOrientation;
@ -176,6 +177,7 @@ struct PipelineBaton {
tiffQuality(80), tiffQuality(80),
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG), tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_NONE), tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_NONE),
tiffSquash(false),
withMetadata(false), withMetadata(false),
withMetadataOrientation(-1), withMetadataOrientation(-1),
convKernelWidth(0), convKernelWidth(0),

BIN
test/fixtures/8bit_depth.tiff vendored Normal file

Binary file not shown.

View File

@ -85,6 +85,7 @@ module.exports = {
inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm 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 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 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 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 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 inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg

View File

@ -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) { it('TIFF lzw compression with horizontal predictor shrinks test file', function (done) {
const startSize = fs.statSync(fixtures.inputTiffUncompressed).size; const startSize = fs.statSync(fixtures.inputTiffUncompressed).size;
sharp(fixtures.inputTiffUncompressed) sharp(fixtures.inputTiffUncompressed)