Expose libvips pyramid/tile options for TIFF output (#1483)

This commit is contained in:
Michael B. Klein 2018-12-06 15:33:46 -06:00 committed by Lovell Fuller
parent fd1ca1dbb2
commit c695c40abc
6 changed files with 180 additions and 41 deletions

View File

@ -177,7 +177,11 @@ const Sharp = function (input, options) {
tiffQuality: 80, tiffQuality: 80,
tiffCompression: 'jpeg', tiffCompression: 'jpeg',
tiffPredictor: 'horizontal', tiffPredictor: 'horizontal',
tiffPyramid: false,
tiffSquash: false, tiffSquash: false,
tiffTile: false,
tiffTileHeight: 256,
tiffTileWidth: 256,
tiffXres: 1.0, tiffXres: 1.0,
tiffYres: 1.0, tiffYres: 1.0,
tileSize: 256, tileSize: 256,

View File

@ -304,6 +304,10 @@ 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, ccittfax4 * @param {Boolean} [options.compression='jpeg'] - compression options: lzw, deflate, jpeg, ccittfax4
* @param {Boolean} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float * @param {Boolean} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float
* @param {Boolean} [options.pyramid=false] - write an image pyramid
* @param {Boolean} [options.tile=false] - write a tiled tiff
* @param {Boolean} [options.tileWidth=256] - horizontal tile size
* @param {Boolean} [options.tileHeight=256] - vertical tile size
* @param {Number} [options.xres=1.0] - horizontal resolution in pixels/mm * @param {Number} [options.xres=1.0] - horizontal resolution in pixels/mm
* @param {Number} [options.yres=1.0] - vertical resolution in pixels/mm * @param {Number} [options.yres=1.0] - vertical resolution in pixels/mm
* @param {Boolean} [options.squash=false] - squash 8-bit images down to 1 bit * @param {Boolean} [options.squash=false] - squash 8-bit images down to 1 bit
@ -311,29 +315,60 @@ function webp (options) {
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
function tiff (options) { function tiff (options) {
if (is.object(options) && is.defined(options.quality)) { if (is.object(options)) {
if (is.defined(options.quality)) {
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) { if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
this.options.tiffQuality = options.quality; this.options.tiffQuality = options.quality;
} else { } else {
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.defined(options.squash)) {
if (is.bool(options.squash)) { if (is.bool(options.squash)) {
this.options.tiffSquash = options.squash; this.options.tiffSquash = options.squash;
} else { } else {
throw new Error('Invalid Value for squash ' + options.squash + ' Only Boolean Values allowed for options.squash.'); throw new Error('Invalid Value for squash ' + options.squash + ' Only Boolean Values allowed for options.squash.');
} }
} }
// tiling
if (is.defined(options.tile)) {
if (is.bool(options.tile)) {
this.options.tiffTile = options.tile;
} else {
throw new Error('Invalid Value for tile ' + options.tile + ' Only Boolean values allowed for options.tile');
}
}
if (is.defined(options.tileWidth)) {
if (is.number(options.tileWidth) && options.tileWidth > 0) {
this.options.tiffTileWidth = options.tileWidth;
} else {
throw new Error('Invalid Value for tileWidth ' + options.tileWidth + ' Only positive numeric values allowed for options.tileWidth');
}
}
if (is.defined(options.tileHeight)) {
if (is.number(options.tileHeight) && options.tileHeight > 0) {
this.options.tiffTileHeight = options.tileHeight;
} else {
throw new Error('Invalid Value for tileHeight ' + options.tileHeight + ' Only positive numeric values allowed for options.tileHeight');
}
}
// pyramid
if (is.defined(options.pyramid)) {
if (is.bool(options.pyramid)) {
this.options.tiffPyramid = options.pyramid;
} else {
throw new Error('Invalid Value for pyramid ' + options.pyramid + ' Only Boolean values allowed for options.pyramid');
}
}
// resolution // resolution
if (is.object(options) && is.defined(options.xres)) { if (is.defined(options.xres)) {
if (is.number(options.xres)) { if (is.number(options.xres)) {
this.options.tiffXres = options.xres; this.options.tiffXres = options.xres;
} else { } else {
throw new Error('Invalid Value for xres ' + options.xres + ' Only numeric values allowed for options.xres'); throw new Error('Invalid Value for xres ' + options.xres + ' Only numeric values allowed for options.xres');
} }
} }
if (is.object(options) && is.defined(options.yres)) { if (is.defined(options.yres)) {
if (is.number(options.yres)) { if (is.number(options.yres)) {
this.options.tiffYres = options.yres; this.options.tiffYres = options.yres;
} else { } else {
@ -341,7 +376,7 @@ function tiff (options) {
} }
} }
// compression // compression
if (is.defined(options) && is.defined(options.compression)) { if (is.defined(options.compression)) {
if (is.string(options.compression) && is.inArray(options.compression, ['lzw', 'deflate', 'jpeg', 'ccittfax4', 'none'])) { if (is.string(options.compression) && is.inArray(options.compression, ['lzw', 'deflate', 'jpeg', 'ccittfax4', 'none'])) {
this.options.tiffCompression = options.compression; this.options.tiffCompression = options.compression;
} else { } else {
@ -350,7 +385,7 @@ function tiff (options) {
} }
} }
// predictor // predictor
if (is.defined(options) && is.defined(options.predictor)) { if (is.defined(options.predictor)) {
if (is.string(options.predictor) && is.inArray(options.predictor, ['none', 'horizontal', 'float'])) { if (is.string(options.predictor) && is.inArray(options.predictor, ['none', 'horizontal', 'float'])) {
this.options.tiffPredictor = options.predictor; this.options.tiffPredictor = options.predictor;
} else { } else {
@ -358,6 +393,7 @@ function tiff (options) {
throw new Error(message); throw new Error(message);
} }
} }
}
return this._updateFormatOut('tiff', options); return this._updateFormatOut('tiff', options);
} }

View File

@ -58,7 +58,8 @@
"Freezy <freezy@xbmc.org>", "Freezy <freezy@xbmc.org>",
"Daiz <taneli.vatanen@gmail.com>", "Daiz <taneli.vatanen@gmail.com>",
"Julian Aubourg <j@ubourg.net>", "Julian Aubourg <j@ubourg.net>",
"Keith Belovay <keith@picthrive.com>" "Keith Belovay <keith@picthrive.com>",
"Michael B. Klein <mbklein@gmail.com>"
], ],
"scripts": { "scripts": {
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)", "install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",

View File

@ -763,6 +763,10 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("squash", baton->tiffSquash) ->set("squash", baton->tiffSquash)
->set("compression", baton->tiffCompression) ->set("compression", baton->tiffCompression)
->set("predictor", baton->tiffPredictor) ->set("predictor", baton->tiffPredictor)
->set("pyramid", baton->tiffPyramid)
->set("tile", baton->tiffTile)
->set("tile_height", baton->tiffTileHeight)
->set("tile_width", baton->tiffTileWidth)
->set("xres", baton->tiffXres) ->set("xres", baton->tiffXres)
->set("yres", baton->tiffYres))); ->set("yres", baton->tiffYres)));
baton->bufferOut = static_cast<char*>(area->data); baton->bufferOut = static_cast<char*>(area->data);
@ -862,6 +866,10 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("squash", baton->tiffSquash) ->set("squash", baton->tiffSquash)
->set("compression", baton->tiffCompression) ->set("compression", baton->tiffCompression)
->set("predictor", baton->tiffPredictor) ->set("predictor", baton->tiffPredictor)
->set("pyramid", baton->tiffPyramid)
->set("tile", baton->tiffTile)
->set("tile_height", baton->tiffTileHeight)
->set("tile_width", baton->tiffTileWidth)
->set("xres", baton->tiffXres) ->set("xres", baton->tiffXres)
->set("yres", baton->tiffYres)); ->set("yres", baton->tiffYres));
baton->formatOut = "tiff"; baton->formatOut = "tiff";
@ -1272,7 +1280,11 @@ 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->tiffPyramid = AttrTo<bool>(options, "tiffPyramid");
baton->tiffSquash = AttrTo<bool>(options, "tiffSquash"); baton->tiffSquash = AttrTo<bool>(options, "tiffSquash");
baton->tiffTile = AttrTo<bool>(options, "tiffTile");
baton->tiffTileWidth = AttrTo<uint32_t>(options, "tiffTileWidth");
baton->tiffTileHeight = AttrTo<uint32_t>(options, "tiffTileHeight");
baton->tiffXres = AttrTo<double>(options, "tiffXres"); baton->tiffXres = AttrTo<double>(options, "tiffXres");
baton->tiffYres = AttrTo<double>(options, "tiffYres"); baton->tiffYres = AttrTo<double>(options, "tiffYres");
// tiff compression options // tiff compression options

View File

@ -122,7 +122,11 @@ struct PipelineBaton {
int tiffQuality; int tiffQuality;
VipsForeignTiffCompression tiffCompression; VipsForeignTiffCompression tiffCompression;
VipsForeignTiffPredictor tiffPredictor; VipsForeignTiffPredictor tiffPredictor;
bool tiffPyramid;
bool tiffSquash; bool tiffSquash;
bool tiffTile;
int tiffTileHeight;
int tiffTileWidth;
double tiffXres; double tiffXres;
double tiffYres; double tiffYres;
std::string err; std::string err;
@ -215,7 +219,11 @@ struct PipelineBaton {
tiffQuality(80), tiffQuality(80),
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG), tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL), tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL),
tiffPyramid(false),
tiffSquash(false), tiffSquash(false),
tiffTile(false),
tiffTileHeight(256),
tiffTileWidth(256),
tiffXres(1.0), tiffXres(1.0),
tiffYres(1.0), tiffYres(1.0),
withMetadata(false), withMetadata(false),

View File

@ -1285,6 +1285,84 @@ describe('Input/output', function () {
}); });
}); });
it('TIFF tiled pyramid image without compression enlarges test file', function (done) {
const startSize = fs.statSync(fixtures.inputTiffUncompressed).size;
sharp(fixtures.inputTiffUncompressed)
.tiff({
compression: 'none',
pyramid: true,
tile: true,
tileHeight: 256,
tileWidth: 256
})
.toFile(fixtures.outputTiff, (err, info) => {
if (err) throw err;
assert.strictEqual('tiff', info.format);
assert(info.size > startSize);
rimraf(fixtures.outputTiff, done);
});
});
it('TIFF pyramid true value does not throw error', function () {
assert.doesNotThrow(function () {
sharp().tiff({ pyramid: true });
});
});
it('Invalid TIFF pyramid value throws error', function () {
assert.throws(function () {
sharp().tiff({ pyramid: 'true' });
});
});
it('Invalid TIFF tile value throws error', function () {
assert.throws(function () {
sharp().tiff({ tile: 'true' });
});
});
it('TIFF tile true value does not throw error', function () {
assert.doesNotThrow(function () {
sharp().tiff({ tile: true });
});
});
it('Valid TIFF tileHeight value does not throw error', function () {
assert.doesNotThrow(function () {
sharp().tiff({ tileHeight: 512 });
});
});
it('Valid TIFF tileWidth value does not throw error', function () {
assert.doesNotThrow(function () {
sharp().tiff({ tileWidth: 512 });
});
});
it('Invalid TIFF tileHeight value throws error', function () {
assert.throws(function () {
sharp().tiff({ tileHeight: '256' });
});
});
it('Invalid TIFF tileWidth value throws error', function () {
assert.throws(function () {
sharp().tiff({ tileWidth: '256' });
});
});
it('Invalid TIFF tileHeight value throws error', function () {
assert.throws(function () {
sharp().tiff({ tileHeight: 0 });
});
});
it('Invalid TIFF tileWidth value throws error', function () {
assert.throws(function () {
sharp().tiff({ tileWidth: 0 });
});
});
it('Input and output formats match when not forcing', function (done) { it('Input and output formats match when not forcing', function (done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)