mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 18:40:16 +02:00
Expose TIFF compression and predictor options (#738)
This commit is contained in:
parent
5e015cc3ca
commit
f8e72f443d
@ -169,6 +169,8 @@ const Sharp = function (input, options) {
|
|||||||
webpLossless: false,
|
webpLossless: false,
|
||||||
webpNearLossless: false,
|
webpNearLossless: false,
|
||||||
tiffQuality: 80,
|
tiffQuality: 80,
|
||||||
|
tiffCompression: 'jpeg',
|
||||||
|
tiffPredictor: 'none',
|
||||||
tileSize: 256,
|
tileSize: 256,
|
||||||
tileOverlap: 0,
|
tileOverlap: 0,
|
||||||
// Function to notify of queue length changes
|
// Function to notify of queue length changes
|
||||||
|
@ -211,6 +211,8 @@ const webp = function webp (options) {
|
|||||||
* @param {Object} [options] - output options
|
* @param {Object} [options] - output options
|
||||||
* @param {Number} [options.quality=80] - quality, integer 1-100
|
* @param {Number} [options.quality=80] - quality, integer 1-100
|
||||||
* @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.predictor='none'] - compression predictor options: none, horizontal, float
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid options
|
* @throws {Error} Invalid options
|
||||||
*/
|
*/
|
||||||
@ -222,6 +224,24 @@ const tiff = function tiff (options) {
|
|||||||
throw new Error('Invalid quality (integer, 1-100) ' + options.quality);
|
throw new Error('Invalid quality (integer, 1-100) ' + options.quality);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// compression
|
||||||
|
if (is.defined(options) && is.defined(options.compression)) {
|
||||||
|
if (is.string(options.compression) && is.inArray(options.compression, ['lzw', 'deflate', 'jpeg', 'none'])) {
|
||||||
|
this.options.tiffCompression = options.compression;
|
||||||
|
} else {
|
||||||
|
const message = `Invalid compression option "${options.compression}". Should be one of: lzw, deflate, jpeg, none`;
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// predictor
|
||||||
|
if (is.defined(options) && is.defined(options.predictor)) {
|
||||||
|
if (is.string(options.predictor) && is.inArray(options.predictor, ['none', 'horizontal', 'float'])) {
|
||||||
|
this.options.tiffPredictor = options.predictor;
|
||||||
|
} else {
|
||||||
|
const message = `Invalid predictor option "${options.predictor}". Should be one of: none, horizontal, float`;
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
return this._updateFormatOut('tiff', options);
|
return this._updateFormatOut('tiff', options);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -842,7 +842,8 @@ 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("compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG));
|
->set("compression", baton->tiffCompression)
|
||||||
|
->set("predictor", baton->tiffPredictor) );
|
||||||
baton->formatOut = "tiff";
|
baton->formatOut = "tiff";
|
||||||
baton->channels = std::min(baton->channels, 3);
|
baton->channels = std::min(baton->channels, 3);
|
||||||
} else if (baton->formatOut == "dz" || isDz || isDzZip) {
|
} else if (baton->formatOut == "dz" || isDz || isDzZip) {
|
||||||
@ -1199,6 +1200,14 @@ 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");
|
||||||
|
// tiff compression options
|
||||||
|
baton->tiffCompression = static_cast<VipsForeignTiffCompression>(
|
||||||
|
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION,
|
||||||
|
AttrAsStr(options, "tiffCompression").data()));
|
||||||
|
baton->tiffPredictor = static_cast<VipsForeignTiffPredictor>(
|
||||||
|
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_PREDICTOR,
|
||||||
|
AttrAsStr(options, "tiffPredictor").data()));
|
||||||
|
|
||||||
// Tile output
|
// Tile output
|
||||||
baton->tileSize = AttrTo<uint32_t>(options, "tileSize");
|
baton->tileSize = AttrTo<uint32_t>(options, "tileSize");
|
||||||
baton->tileOverlap = AttrTo<uint32_t>(options, "tileOverlap");
|
baton->tileOverlap = AttrTo<uint32_t>(options, "tileOverlap");
|
||||||
|
@ -104,6 +104,8 @@ struct PipelineBaton {
|
|||||||
bool webpNearLossless;
|
bool webpNearLossless;
|
||||||
bool webpLossless;
|
bool webpLossless;
|
||||||
int tiffQuality;
|
int tiffQuality;
|
||||||
|
VipsForeignTiffCompression tiffCompression;
|
||||||
|
VipsForeignTiffPredictor tiffPredictor;
|
||||||
std::string err;
|
std::string err;
|
||||||
bool withMetadata;
|
bool withMetadata;
|
||||||
int withMetadataOrientation;
|
int withMetadataOrientation;
|
||||||
@ -172,6 +174,8 @@ struct PipelineBaton {
|
|||||||
pngAdaptiveFiltering(true),
|
pngAdaptiveFiltering(true),
|
||||||
webpQuality(80),
|
webpQuality(80),
|
||||||
tiffQuality(80),
|
tiffQuality(80),
|
||||||
|
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
|
||||||
|
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_NONE),
|
||||||
withMetadata(false),
|
withMetadata(false),
|
||||||
withMetadataOrientation(-1),
|
withMetadataOrientation(-1),
|
||||||
convKernelWidth(0),
|
convKernelWidth(0),
|
||||||
|
2
test/fixtures/index.js
vendored
2
test/fixtures/index.js
vendored
@ -84,6 +84,7 @@ module.exports = {
|
|||||||
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.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
|
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
|
||||||
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
|
||||||
@ -102,6 +103,7 @@ module.exports = {
|
|||||||
outputPng: getPath('output.png'),
|
outputPng: getPath('output.png'),
|
||||||
outputWebP: getPath('output.webp'),
|
outputWebP: getPath('output.webp'),
|
||||||
outputV: getPath('output.v'),
|
outputV: getPath('output.v'),
|
||||||
|
outputTiff: getPath('output.tiff'),
|
||||||
outputZoinks: getPath('output.zoinks'), // an 'unknown' file extension
|
outputZoinks: getPath('output.zoinks'), // an 'unknown' file extension
|
||||||
|
|
||||||
// Path for tests requiring human inspection
|
// Path for tests requiring human inspection
|
||||||
|
BIN
test/fixtures/uncompressed_tiff.tiff
vendored
Normal file
BIN
test/fixtures/uncompressed_tiff.tiff
vendored
Normal file
Binary file not shown.
124
test/unit/io.js
124
test/unit/io.js
@ -861,6 +861,130 @@ describe('Input/output', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('TIFF lzw compression with horizontal predictor shrinks test file', function (done) {
|
||||||
|
const startSize = fs.statSync(fixtures.inputTiffUncompressed).size;
|
||||||
|
sharp(fixtures.inputTiffUncompressed)
|
||||||
|
.tiff({
|
||||||
|
compression: 'lzw',
|
||||||
|
force: true,
|
||||||
|
// note: lzw compression is imperfect and sometimes
|
||||||
|
// generates larger files, as it does with this input
|
||||||
|
// if no predictor is used.
|
||||||
|
predictor: 'horizontal'
|
||||||
|
})
|
||||||
|
.toFile(fixtures.outputTiff, (err, info) => {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('tiff', info.format);
|
||||||
|
assert(info.size < startSize);
|
||||||
|
fs.unlinkSync(fixtures.outputTiff);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TIFF deflate compression with hoizontal predictor shrinks test file', function (done) {
|
||||||
|
const startSize = fs.statSync(fixtures.inputTiffUncompressed).size;
|
||||||
|
sharp(fixtures.inputTiffUncompressed)
|
||||||
|
.tiff({
|
||||||
|
compression: 'deflate',
|
||||||
|
force: true,
|
||||||
|
predictor: 'horizontal'
|
||||||
|
})
|
||||||
|
.toFile(fixtures.outputTiff, (err, info) => {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('tiff', info.format);
|
||||||
|
assert(info.size < startSize);
|
||||||
|
fs.unlinkSync(fixtures.outputTiff);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TIFF deflate compression without predictor shrinks test file', function (done) {
|
||||||
|
const startSize = fs.statSync(fixtures.inputTiffUncompressed).size;
|
||||||
|
sharp(fixtures.inputTiffUncompressed)
|
||||||
|
.tiff({
|
||||||
|
compression: 'deflate',
|
||||||
|
force: true,
|
||||||
|
predictor: 'none'
|
||||||
|
})
|
||||||
|
.toFile(fixtures.outputTiff, (err, info) => {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('tiff', info.format);
|
||||||
|
assert(info.size < startSize);
|
||||||
|
fs.unlinkSync(fixtures.outputTiff);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TIFF jpeg compression shrinks test file', function (done) {
|
||||||
|
const startSize = fs.statSync(fixtures.inputTiffUncompressed).size;
|
||||||
|
sharp(fixtures.inputTiffUncompressed)
|
||||||
|
.tiff({
|
||||||
|
compression: 'jpeg',
|
||||||
|
force: true
|
||||||
|
})
|
||||||
|
.toFile(fixtures.outputTiff, (err, info) => {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('tiff', info.format);
|
||||||
|
assert(info.size < startSize);
|
||||||
|
fs.unlinkSync(fixtures.outputTiff);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TIFF none compression does not throw error', function () {
|
||||||
|
assert.doesNotThrow(function () {
|
||||||
|
sharp().tiff({ compression: 'none' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TIFF lzw compression does not throw error', function () {
|
||||||
|
assert.doesNotThrow(function () {
|
||||||
|
sharp().tiff({ compression: 'lzw' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TIFF deflate compression does not throw error', function () {
|
||||||
|
assert.doesNotThrow(function () {
|
||||||
|
sharp().tiff({ compression: 'deflate' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TIFF invalid compression option throws', function () {
|
||||||
|
assert.throws(function () {
|
||||||
|
sharp().tiff({ compression: 0 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TIFF invalid compression option throws', function () {
|
||||||
|
assert.throws(function () {
|
||||||
|
sharp().tiff({ compression: 'a' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TIFF invalid predictor option throws', function () {
|
||||||
|
assert.throws(function () {
|
||||||
|
sharp().tiff({ predictor: 'a' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TIFF horizontal predictor does not throw error', function () {
|
||||||
|
assert.doesNotThrow(function () {
|
||||||
|
sharp().tiff({ predictor: 'horizontal' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TIFF float predictor does not throw error', function () {
|
||||||
|
assert.doesNotThrow(function () {
|
||||||
|
sharp().tiff({ predictor: 'float' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TIFF none predictor does not throw error', function () {
|
||||||
|
assert.doesNotThrow(function () {
|
||||||
|
sharp().tiff({ predictor: 'none' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user