Expose reoptimise palette option for GIF output

This commit is contained in:
Lovell Fuller 2022-07-12 21:12:31 +01:00
parent d247c02762
commit 6288c7bced
7 changed files with 40 additions and 0 deletions

View File

@ -321,10 +321,14 @@ Use these GIF options for the output image.
The first entry in the palette is reserved for transparency. The first entry in the palette is reserved for transparency.
The palette of the input image will be re-used if possible.
### Parameters ### Parameters
* `options` **[Object][6]?** output options * `options` **[Object][6]?** output options
* `options.reoptimise` **[boolean][10]** always generate new palettes (slow), re-use existing by default (optional, default `false`)
* `options.reoptimize` **[boolean][10]** alternative spelling of `options.reoptimise` (optional, default `false`)
* `options.colours` **[number][12]** maximum number of palette entries, including transparency, between 2 and 256 (optional, default `256`) * `options.colours` **[number][12]** maximum number of palette entries, including transparency, between 2 and 256 (optional, default `256`)
* `options.colors` **[number][12]** alternative spelling of `options.colours` (optional, default `256`) * `options.colors` **[number][12]** alternative spelling of `options.colours` (optional, default `256`)
* `options.effort` **[number][12]** CPU effort, between 1 (fastest) and 10 (slowest) (optional, default `7`) * `options.effort` **[number][12]** CPU effort, between 1 (fastest) and 10 (slowest) (optional, default `7`)

View File

@ -8,6 +8,8 @@ Requires libvips v8.13.0
* Drop support for Node.js 12, now requires Node.js >= 14.15.0. * Drop support for Node.js 12, now requires Node.js >= 14.15.0.
* GIF output now re-uses input palette if possible. Use `reoptimise` option to generate a new palette.
* Add WebP `minSize` and `mixed` options for greater control over animation frames. * Add WebP `minSize` and `mixed` options for greater control over animation frames.
* Use combined bounding box of alpha and non-alpha channels for `trim` operation. * Use combined bounding box of alpha and non-alpha channels for `trim` operation.

View File

@ -256,6 +256,7 @@ const Sharp = function (input, options) {
gifBitdepth: 8, gifBitdepth: 8,
gifEffort: 7, gifEffort: 7,
gifDither: 1, gifDither: 1,
gifReoptimise: false,
tiffQuality: 80, tiffQuality: 80,
tiffCompression: 'jpeg', tiffCompression: 'jpeg',
tiffPredictor: 'horizontal', tiffPredictor: 'horizontal',

View File

@ -524,6 +524,8 @@ function webp (options) {
* *
* The first entry in the palette is reserved for transparency. * The first entry in the palette is reserved for transparency.
* *
* The palette of the input image will be re-used if possible.
*
* @since 0.30.0 * @since 0.30.0
* *
* @example * @example
@ -545,6 +547,8 @@ function webp (options) {
* .toBuffer(); * .toBuffer();
* *
* @param {Object} [options] - output options * @param {Object} [options] - output options
* @param {boolean} [options.reoptimise=false] - always generate new palettes (slow), re-use existing by default
* @param {boolean} [options.reoptimize=false] - alternative spelling of `options.reoptimise`
* @param {number} [options.colours=256] - maximum number of palette entries, including transparency, between 2 and 256 * @param {number} [options.colours=256] - maximum number of palette entries, including transparency, between 2 and 256
* @param {number} [options.colors=256] - alternative spelling of `options.colours` * @param {number} [options.colors=256] - alternative spelling of `options.colours`
* @param {number} [options.effort=7] - CPU effort, between 1 (fastest) and 10 (slowest) * @param {number} [options.effort=7] - CPU effort, between 1 (fastest) and 10 (slowest)
@ -557,6 +561,11 @@ function webp (options) {
*/ */
function gif (options) { function gif (options) {
if (is.object(options)) { if (is.object(options)) {
if (is.defined(options.reoptimise)) {
this._setBooleanOption('gifReoptimise', options.reoptimise);
} else if (is.defined(options.reoptimize)) {
this._setBooleanOption('gifReoptimise', options.reoptimize);
}
const colours = options.colours || options.colors; const colours = options.colours || options.colors;
if (is.defined(colours)) { if (is.defined(colours)) {
if (is.integer(colours) && is.inRange(colours, 2, 256)) { if (is.integer(colours) && is.inRange(colours, 2, 256)) {

View File

@ -888,6 +888,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("bitdepth", baton->gifBitdepth) ->set("bitdepth", baton->gifBitdepth)
->set("effort", baton->gifEffort) ->set("effort", baton->gifEffort)
->set("reoptimise", baton->gifReoptimise)
->set("dither", baton->gifDither))); ->set("dither", baton->gifDither)));
baton->bufferOut = static_cast<char*>(area->data); baton->bufferOut = static_cast<char*>(area->data);
baton->bufferOutLength = area->length; baton->bufferOutLength = area->length;
@ -1053,6 +1054,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("bitdepth", baton->gifBitdepth) ->set("bitdepth", baton->gifBitdepth)
->set("effort", baton->gifEffort) ->set("effort", baton->gifEffort)
->set("reoptimise", baton->gifReoptimise)
->set("dither", baton->gifDither)); ->set("dither", baton->gifDither));
baton->formatOut = "gif"; baton->formatOut = "gif";
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) || } else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
@ -1537,6 +1539,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth"); baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth");
baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort"); baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort");
baton->gifDither = sharp::AttrAsDouble(options, "gifDither"); baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
baton->gifReoptimise = sharp::AttrAsBool(options, "gifReoptimise");
baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality"); baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid"); baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid");
baton->tiffBitdepth = sharp::AttrAsUint32(options, "tiffBitdepth"); baton->tiffBitdepth = sharp::AttrAsUint32(options, "tiffBitdepth");

View File

@ -162,6 +162,7 @@ struct PipelineBaton {
int gifBitdepth; int gifBitdepth;
int gifEffort; int gifEffort;
double gifDither; double gifDither;
bool gifReoptimise;
int tiffQuality; int tiffQuality;
VipsForeignTiffCompression tiffCompression; VipsForeignTiffCompression tiffCompression;
VipsForeignTiffPredictor tiffPredictor; VipsForeignTiffPredictor tiffPredictor;
@ -306,6 +307,10 @@ struct PipelineBaton {
webpEffort(4), webpEffort(4),
webpMinSize(false), webpMinSize(false),
webpMixed(false), webpMixed(false),
gifBitdepth(8),
gifEffort(7),
gifDither(1.0),
gifReoptimise(false),
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),

View File

@ -80,6 +80,22 @@ describe('GIF input', () => {
assert.strictEqual(true, reduced.length < original.length); assert.strictEqual(true, reduced.length < original.length);
}); });
it('valid optimise', () => {
assert.doesNotThrow(() => sharp().gif({ reoptimise: true }));
assert.doesNotThrow(() => sharp().gif({ reoptimize: true }));
});
it('invalid reoptimise throws', () => {
assert.throws(
() => sharp().gif({ reoptimise: -1 }),
/Expected boolean for gifReoptimise but received -1 of type number/
);
assert.throws(
() => sharp().gif({ reoptimize: 'fail' }),
/Expected boolean for gifReoptimise but received fail of type string/
);
});
it('invalid loop throws', () => { it('invalid loop throws', () => {
assert.throws(() => { assert.throws(() => {
sharp().gif({ loop: -1 }); sharp().gif({ loop: -1 });