mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 02:30:12 +02:00
Expose GIF opts: interFrameMaxError, interPaletteMaxError #3401
This commit is contained in:
parent
a9d692fb43
commit
5740f4545e
@ -334,6 +334,8 @@ The palette of the input image will be re-used if possible.
|
|||||||
* `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`)
|
||||||
* `options.dither` **[number][12]** level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most) (optional, default `1.0`)
|
* `options.dither` **[number][12]** level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most) (optional, default `1.0`)
|
||||||
|
* `options.interFrameMaxError` **[number][12]** maximum inter-frame error for transparency, between 0 (lossless) and 32 (optional, default `0`)
|
||||||
|
* `options.interPaletteMaxError` **[number][12]** maximum inter-palette error for palette reuse, between 0 and 256 (optional, default `3`)
|
||||||
* `options.loop` **[number][12]** number of animation iterations, use 0 for infinite animation (optional, default `0`)
|
* `options.loop` **[number][12]** number of animation iterations, use 0 for infinite animation (optional, default `0`)
|
||||||
* `options.delay` **([number][12] | [Array][13]<[number][12]>)?** delay(s) between animation frames (in milliseconds)
|
* `options.delay` **([number][12] | [Array][13]<[number][12]>)?** delay(s) between animation frames (in milliseconds)
|
||||||
* `options.force` **[boolean][10]** force GIF output, otherwise attempt to use input format (optional, default `true`)
|
* `options.force` **[boolean][10]** force GIF output, otherwise attempt to use input format (optional, default `true`)
|
||||||
@ -361,6 +363,13 @@ const out = await sharp('in.gif', { animated: true })
|
|||||||
.toBuffer();
|
.toBuffer();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Lossy file size reduction of animated GIF
|
||||||
|
await sharp('in.gif', { animated: true })
|
||||||
|
.gif({ interFrameMaxError: 8 })
|
||||||
|
.toFile('optim.gif');
|
||||||
|
```
|
||||||
|
|
||||||
* Throws **[Error][4]** Invalid options
|
* Throws **[Error][4]** Invalid options
|
||||||
|
|
||||||
Returns **Sharp** 
|
Returns **Sharp** 
|
||||||
|
@ -6,6 +6,9 @@ Requires libvips v8.13.3
|
|||||||
|
|
||||||
### v0.31.3 - TBD
|
### v0.31.3 - TBD
|
||||||
|
|
||||||
|
* Expose `interFrameMaxError` and `interPaletteMaxError` GIF optimisation properties.
|
||||||
|
[#3401](https://github.com/lovell/sharp/issues/3401)
|
||||||
|
|
||||||
* Prevent possible race condition awaiting metadata of Stream-based input.
|
* Prevent possible race condition awaiting metadata of Stream-based input.
|
||||||
[#3451](https://github.com/lovell/sharp/issues/3451)
|
[#3451](https://github.com/lovell/sharp/issues/3451)
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
@ -289,6 +289,8 @@ const Sharp = function (input, options) {
|
|||||||
gifBitdepth: 8,
|
gifBitdepth: 8,
|
||||||
gifEffort: 7,
|
gifEffort: 7,
|
||||||
gifDither: 1,
|
gifDither: 1,
|
||||||
|
gifInterFrameMaxError: 0,
|
||||||
|
gifInterPaletteMaxError: 3,
|
||||||
gifReoptimise: false,
|
gifReoptimise: false,
|
||||||
tiffQuality: 80,
|
tiffQuality: 80,
|
||||||
tiffCompression: 'jpeg',
|
tiffCompression: 'jpeg',
|
||||||
|
@ -551,6 +551,12 @@ function webp (options) {
|
|||||||
* .gif({ dither: 0 })
|
* .gif({ dither: 0 })
|
||||||
* .toBuffer();
|
* .toBuffer();
|
||||||
*
|
*
|
||||||
|
* @example
|
||||||
|
* // Lossy file size reduction of animated GIF
|
||||||
|
* await sharp('in.gif', { animated: true })
|
||||||
|
* .gif({ interFrameMaxError: 8 })
|
||||||
|
* .toFile('optim.gif');
|
||||||
|
*
|
||||||
* @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.reoptimise=false] - always generate new palettes (slow), re-use existing by default
|
||||||
* @param {boolean} [options.reoptimize=false] - alternative spelling of `options.reoptimise`
|
* @param {boolean} [options.reoptimize=false] - alternative spelling of `options.reoptimise`
|
||||||
@ -558,6 +564,8 @@ function webp (options) {
|
|||||||
* @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)
|
||||||
* @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most)
|
* @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most)
|
||||||
|
* @param {number} [options.interFrameMaxError=0] - maximum inter-frame error for transparency, between 0 (lossless) and 32
|
||||||
|
* @param {number} [options.interPaletteMaxError=3] - maximum inter-palette error for palette reuse, between 0 and 256
|
||||||
* @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
|
* @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
|
||||||
* @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds)
|
* @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds)
|
||||||
* @param {boolean} [options.force=true] - force GIF output, otherwise attempt to use input format
|
* @param {boolean} [options.force=true] - force GIF output, otherwise attempt to use input format
|
||||||
@ -593,6 +601,20 @@ function gif (options) {
|
|||||||
throw is.invalidParameterError('dither', 'number between 0.0 and 1.0', options.dither);
|
throw is.invalidParameterError('dither', 'number between 0.0 and 1.0', options.dither);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (is.defined(options.interFrameMaxError)) {
|
||||||
|
if (is.number(options.interFrameMaxError) && is.inRange(options.interFrameMaxError, 0, 32)) {
|
||||||
|
this.options.gifInterFrameMaxError = options.interFrameMaxError;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('interFrameMaxError', 'number between 0.0 and 32.0', options.interFrameMaxError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.interPaletteMaxError)) {
|
||||||
|
if (is.number(options.interPaletteMaxError) && is.inRange(options.interPaletteMaxError, 0, 256)) {
|
||||||
|
this.options.gifInterPaletteMaxError = options.interPaletteMaxError;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('interPaletteMaxError', 'number between 0.0 and 256.0', options.interPaletteMaxError);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
trySetAnimationOptions(options, this.options);
|
trySetAnimationOptions(options, this.options);
|
||||||
return this._updateFormatOut('gif', options);
|
return this._updateFormatOut('gif', options);
|
||||||
|
@ -869,6 +869,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
->set("bitdepth", baton->gifBitdepth)
|
->set("bitdepth", baton->gifBitdepth)
|
||||||
->set("effort", baton->gifEffort)
|
->set("effort", baton->gifEffort)
|
||||||
->set("reoptimise", baton->gifReoptimise)
|
->set("reoptimise", baton->gifReoptimise)
|
||||||
|
->set("interframe_maxerror", baton->gifInterFrameMaxError)
|
||||||
|
->set("interpalette_maxerror", baton->gifInterPaletteMaxError)
|
||||||
->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;
|
||||||
@ -1549,6 +1551,8 @@ 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->gifInterFrameMaxError = sharp::AttrAsDouble(options, "gifInterFrameMaxError");
|
||||||
|
baton->gifInterPaletteMaxError = sharp::AttrAsDouble(options, "gifInterPaletteMaxError");
|
||||||
baton->gifReoptimise = sharp::AttrAsBool(options, "gifReoptimise");
|
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");
|
||||||
|
@ -163,6 +163,8 @@ struct PipelineBaton {
|
|||||||
int gifBitdepth;
|
int gifBitdepth;
|
||||||
int gifEffort;
|
int gifEffort;
|
||||||
double gifDither;
|
double gifDither;
|
||||||
|
double gifInterFrameMaxError;
|
||||||
|
double gifInterPaletteMaxError;
|
||||||
bool gifReoptimise;
|
bool gifReoptimise;
|
||||||
int tiffQuality;
|
int tiffQuality;
|
||||||
VipsForeignTiffCompression tiffCompression;
|
VipsForeignTiffCompression tiffCompression;
|
||||||
@ -314,6 +316,8 @@ struct PipelineBaton {
|
|||||||
gifBitdepth(8),
|
gifBitdepth(8),
|
||||||
gifEffort(7),
|
gifEffort(7),
|
||||||
gifDither(1.0),
|
gifDither(1.0),
|
||||||
|
gifInterFrameMaxError(0.0),
|
||||||
|
gifInterPaletteMaxError(3.0),
|
||||||
gifReoptimise(false),
|
gifReoptimise(false),
|
||||||
tiffQuality(80),
|
tiffQuality(80),
|
||||||
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
|
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
|
||||||
|
@ -141,6 +141,28 @@ describe('GIF input', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('invalid interFrameMaxError throws', () => {
|
||||||
|
assert.throws(
|
||||||
|
() => sharp().gif({ interFrameMaxError: 33 }),
|
||||||
|
/Expected number between 0.0 and 32.0 for interFrameMaxError but received 33 of type number/
|
||||||
|
);
|
||||||
|
assert.throws(
|
||||||
|
() => sharp().gif({ interFrameMaxError: 'fail' }),
|
||||||
|
/Expected number between 0.0 and 32.0 for interFrameMaxError but received fail of type string/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid interPaletteMaxError throws', () => {
|
||||||
|
assert.throws(
|
||||||
|
() => sharp().gif({ interPaletteMaxError: 257 }),
|
||||||
|
/Expected number between 0.0 and 256.0 for interPaletteMaxError but received 257 of type number/
|
||||||
|
);
|
||||||
|
assert.throws(
|
||||||
|
() => sharp().gif({ interPaletteMaxError: 'fail' }),
|
||||||
|
/Expected number between 0.0 and 256.0 for interPaletteMaxError but received fail of type string/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('should work with streams when only animated is set', function (done) {
|
it('should work with streams when only animated is set', function (done) {
|
||||||
fs.createReadStream(fixtures.inputGifAnimated)
|
fs.createReadStream(fixtures.inputGifAnimated)
|
||||||
.pipe(sharp({ animated: true }))
|
.pipe(sharp({ animated: true }))
|
||||||
@ -164,4 +186,18 @@ describe('GIF input', () => {
|
|||||||
fixtures.assertSimilar(fixtures.inputGifAnimated, data, done);
|
fixtures.assertSimilar(fixtures.inputGifAnimated, data, done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should optimise file size via interFrameMaxError', async () => {
|
||||||
|
const input = sharp(fixtures.inputGifAnimated, { animated: true });
|
||||||
|
const before = await input.gif({ interFrameMaxError: 0 }).toBuffer();
|
||||||
|
const after = await input.gif({ interFrameMaxError: 10 }).toBuffer();
|
||||||
|
assert.strict(before.length > after.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should optimise file size via interPaletteMaxError', async () => {
|
||||||
|
const input = sharp(fixtures.inputGifAnimated, { animated: true });
|
||||||
|
const before = await input.gif({ interPaletteMaxError: 0 }).toBuffer();
|
||||||
|
const after = await input.gif({ interPaletteMaxError: 100 }).toBuffer();
|
||||||
|
assert.strict(before.length > after.length);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user