mirror of
https://github.com/lovell/sharp.git
synced 2025-07-13 20:30:14 +02:00
Compare commits
2 Commits
8669fbc936
...
8c53d499f7
Author | SHA1 | Date | |
---|---|---|---|
|
8c53d499f7 | ||
|
9392b8702b |
@ -496,6 +496,7 @@ The palette of the input image will be re-used if possible.
|
|||||||
| [options.dither] | <code>number</code> | <code>1.0</code> | level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most) |
|
| [options.dither] | <code>number</code> | <code>1.0</code> | level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most) |
|
||||||
| [options.interFrameMaxError] | <code>number</code> | <code>0</code> | maximum inter-frame error for transparency, between 0 (lossless) and 32 |
|
| [options.interFrameMaxError] | <code>number</code> | <code>0</code> | maximum inter-frame error for transparency, between 0 (lossless) and 32 |
|
||||||
| [options.interPaletteMaxError] | <code>number</code> | <code>3</code> | maximum inter-palette error for palette reuse, between 0 and 256 |
|
| [options.interPaletteMaxError] | <code>number</code> | <code>3</code> | maximum inter-palette error for palette reuse, between 0 and 256 |
|
||||||
|
| [options.keepDuplicateFrames] | <code>boolean</code> | <code>false</code> | keep duplicate frames in the output instead of combining them |
|
||||||
| [options.loop] | <code>number</code> | <code>0</code> | number of animation iterations, use 0 for infinite animation |
|
| [options.loop] | <code>number</code> | <code>0</code> | number of animation iterations, use 0 for infinite animation |
|
||||||
| [options.delay] | <code>number</code> \| <code>Array.<number></code> | | delay(s) between animation frames (in milliseconds) |
|
| [options.delay] | <code>number</code> \| <code>Array.<number></code> | | delay(s) between animation frames (in milliseconds) |
|
||||||
| [options.force] | <code>boolean</code> | <code>true</code> | force GIF output, otherwise attempt to use input format |
|
| [options.force] | <code>boolean</code> | <code>true</code> | force GIF output, otherwise attempt to use input format |
|
||||||
|
@ -38,6 +38,8 @@ Possible downsizing kernels are:
|
|||||||
- `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf).
|
- `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf).
|
||||||
- `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
|
- `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
|
||||||
- `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
|
- `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
|
||||||
|
- `mks2013`: Use a [Magic Kernel Sharp](https://johncostella.com/magic/mks.pdf) 2013 kernel, as adopted by Facebook.
|
||||||
|
- `mks2021`: Use a Magic Kernel Sharp 2021 kernel, with more accurate (reduced) sharpening than the 2013 version.
|
||||||
|
|
||||||
When upsampling, these kernels map to `nearest`, `linear` and `cubic` interpolators.
|
When upsampling, these kernels map to `nearest`, `linear` and `cubic` interpolators.
|
||||||
Downsampling kernels without a matching upsampling interpolator map to `cubic`.
|
Downsampling kernels without a matching upsampling interpolator map to `cubic`.
|
||||||
|
@ -10,6 +10,10 @@ Requires libvips v8.17.0
|
|||||||
|
|
||||||
* Upgrade to libvips v8.17.0 for upstream bug fixes.
|
* Upgrade to libvips v8.17.0 for upstream bug fixes.
|
||||||
|
|
||||||
|
* Add "Magic Kernel Sharp" (no relation) to resizing kernels.
|
||||||
|
|
||||||
|
* Expose `keepDuplicateFrames` GIF output parameter.
|
||||||
|
|
||||||
* Expose JPEG 2000 `oneshot` decoder option.
|
* Expose JPEG 2000 `oneshot` decoder option.
|
||||||
[#4262](https://github.com/lovell/sharp/pull/4262)
|
[#4262](https://github.com/lovell/sharp/pull/4262)
|
||||||
[@mbklein](https://github.com/mbklein)
|
[@mbklein](https://github.com/mbklein)
|
||||||
|
@ -338,6 +338,7 @@ const Sharp = function (input, options) {
|
|||||||
gifDither: 1,
|
gifDither: 1,
|
||||||
gifInterFrameMaxError: 0,
|
gifInterFrameMaxError: 0,
|
||||||
gifInterPaletteMaxError: 3,
|
gifInterPaletteMaxError: 3,
|
||||||
|
gifKeepDuplicateFrames: false,
|
||||||
gifReuse: true,
|
gifReuse: true,
|
||||||
gifProgressive: false,
|
gifProgressive: false,
|
||||||
tiffQuality: 80,
|
tiffQuality: 80,
|
||||||
|
8
lib/index.d.ts
vendored
8
lib/index.d.ts
vendored
@ -1392,9 +1392,11 @@ declare namespace sharp {
|
|||||||
/** Level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most) (optional, default 1.0) */
|
/** Level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most) (optional, default 1.0) */
|
||||||
dither?: number | undefined;
|
dither?: number | undefined;
|
||||||
/** Maximum inter-frame error for transparency, between 0 (lossless) and 32 (optional, default 0) */
|
/** Maximum inter-frame error for transparency, between 0 (lossless) and 32 (optional, default 0) */
|
||||||
interFrameMaxError?: number;
|
interFrameMaxError?: number | undefined;
|
||||||
/** Maximum inter-palette error for palette reuse, between 0 and 256 (optional, default 3) */
|
/** Maximum inter-palette error for palette reuse, between 0 and 256 (optional, default 3) */
|
||||||
interPaletteMaxError?: number;
|
interPaletteMaxError?: number | undefined;
|
||||||
|
/** Keep duplicate frames in the output instead of combining them (optional, default false) */
|
||||||
|
keepDuplicateFrames?: boolean | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TiffOptions extends OutputOptions {
|
interface TiffOptions extends OutputOptions {
|
||||||
@ -1729,6 +1731,8 @@ declare namespace sharp {
|
|||||||
mitchell: 'mitchell';
|
mitchell: 'mitchell';
|
||||||
lanczos2: 'lanczos2';
|
lanczos2: 'lanczos2';
|
||||||
lanczos3: 'lanczos3';
|
lanczos3: 'lanczos3';
|
||||||
|
mks2013: 'mks2013';
|
||||||
|
mks2021: 'mks2021';
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PresetEnum {
|
interface PresetEnum {
|
||||||
|
@ -729,6 +729,7 @@ function webp (options) {
|
|||||||
* @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.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.interPaletteMaxError=3] - maximum inter-palette error for palette reuse, between 0 and 256
|
||||||
|
* @param {boolean} [options.keepDuplicateFrames=false] - keep duplicate frames in the output instead of combining them
|
||||||
* @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
|
||||||
@ -779,6 +780,13 @@ function gif (options) {
|
|||||||
throw is.invalidParameterError('interPaletteMaxError', 'number between 0.0 and 256.0', options.interPaletteMaxError);
|
throw is.invalidParameterError('interPaletteMaxError', 'number between 0.0 and 256.0', options.interPaletteMaxError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (is.defined(options.keepDuplicateFrames)) {
|
||||||
|
if (is.bool(options.keepDuplicateFrames)) {
|
||||||
|
this._setBooleanOption('gifKeepDuplicateFrames', options.keepDuplicateFrames);
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('keepDuplicateFrames', 'boolean', options.keepDuplicateFrames);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
trySetAnimationOptions(options, this.options);
|
trySetAnimationOptions(options, this.options);
|
||||||
return this._updateFormatOut('gif', options);
|
return this._updateFormatOut('gif', options);
|
||||||
|
@ -150,6 +150,8 @@ function isResizeExpected (options) {
|
|||||||
* - `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf).
|
* - `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf).
|
||||||
* - `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
|
* - `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
|
||||||
* - `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
|
* - `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
|
||||||
|
* - `mks2013`: Use a [Magic Kernel Sharp](https://johncostella.com/magic/mks.pdf) 2013 kernel, as adopted by Facebook.
|
||||||
|
* - `mks2021`: Use a Magic Kernel Sharp 2021 kernel, with more accurate (reduced) sharpening than the 2013 version.
|
||||||
*
|
*
|
||||||
* When upsampling, these kernels map to `nearest`, `linear` and `cubic` interpolators.
|
* When upsampling, these kernels map to `nearest`, `linear` and `cubic` interpolators.
|
||||||
* Downsampling kernels without a matching upsampling interpolator map to `cubic`.
|
* Downsampling kernels without a matching upsampling interpolator map to `cubic`.
|
||||||
|
@ -1006,6 +1006,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
->set("interlace", baton->gifProgressive)
|
->set("interlace", baton->gifProgressive)
|
||||||
->set("interframe_maxerror", baton->gifInterFrameMaxError)
|
->set("interframe_maxerror", baton->gifInterFrameMaxError)
|
||||||
->set("interpalette_maxerror", baton->gifInterPaletteMaxError)
|
->set("interpalette_maxerror", baton->gifInterPaletteMaxError)
|
||||||
|
->set("keep_duplicate_frames", baton->gifKeepDuplicateFrames)
|
||||||
->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;
|
||||||
@ -1209,6 +1210,9 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
->set("effort", baton->gifEffort)
|
->set("effort", baton->gifEffort)
|
||||||
->set("reuse", baton->gifReuse)
|
->set("reuse", baton->gifReuse)
|
||||||
->set("interlace", baton->gifProgressive)
|
->set("interlace", baton->gifProgressive)
|
||||||
|
->set("interframe_maxerror", baton->gifInterFrameMaxError)
|
||||||
|
->set("interpalette_maxerror", baton->gifInterPaletteMaxError)
|
||||||
|
->set("keep_duplicate_frames", baton->gifKeepDuplicateFrames)
|
||||||
->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) ||
|
||||||
@ -1761,6 +1765,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
|
baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
|
||||||
baton->gifInterFrameMaxError = sharp::AttrAsDouble(options, "gifInterFrameMaxError");
|
baton->gifInterFrameMaxError = sharp::AttrAsDouble(options, "gifInterFrameMaxError");
|
||||||
baton->gifInterPaletteMaxError = sharp::AttrAsDouble(options, "gifInterPaletteMaxError");
|
baton->gifInterPaletteMaxError = sharp::AttrAsDouble(options, "gifInterPaletteMaxError");
|
||||||
|
baton->gifKeepDuplicateFrames = sharp::AttrAsBool(options, "gifKeepDuplicateFrames");
|
||||||
baton->gifReuse = sharp::AttrAsBool(options, "gifReuse");
|
baton->gifReuse = sharp::AttrAsBool(options, "gifReuse");
|
||||||
baton->gifProgressive = sharp::AttrAsBool(options, "gifProgressive");
|
baton->gifProgressive = sharp::AttrAsBool(options, "gifProgressive");
|
||||||
baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
|
baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
|
||||||
|
@ -169,6 +169,7 @@ struct PipelineBaton {
|
|||||||
double gifDither;
|
double gifDither;
|
||||||
double gifInterFrameMaxError;
|
double gifInterFrameMaxError;
|
||||||
double gifInterPaletteMaxError;
|
double gifInterPaletteMaxError;
|
||||||
|
bool gifKeepDuplicateFrames;
|
||||||
bool gifReuse;
|
bool gifReuse;
|
||||||
bool gifProgressive;
|
bool gifProgressive;
|
||||||
int tiffQuality;
|
int tiffQuality;
|
||||||
@ -342,6 +343,7 @@ struct PipelineBaton {
|
|||||||
gifDither(1.0),
|
gifDither(1.0),
|
||||||
gifInterFrameMaxError(0.0),
|
gifInterFrameMaxError(0.0),
|
||||||
gifInterPaletteMaxError(3.0),
|
gifInterPaletteMaxError(3.0),
|
||||||
|
gifKeepDuplicateFrames(false),
|
||||||
gifReuse(true),
|
gifReuse(true),
|
||||||
gifProgressive(false),
|
gifProgressive(false),
|
||||||
tiffQuality(80),
|
tiffQuality(80),
|
||||||
|
@ -188,6 +188,8 @@ sharp(input)
|
|||||||
// of the image data in inputBuffer
|
// of the image data in inputBuffer
|
||||||
});
|
});
|
||||||
|
|
||||||
|
sharp(input).resize({ kernel: 'mks2013' });
|
||||||
|
|
||||||
transformer = sharp()
|
transformer = sharp()
|
||||||
.resize(200, 200, {
|
.resize(200, 200, {
|
||||||
fit: 'cover',
|
fit: 'cover',
|
||||||
@ -373,6 +375,8 @@ sharp(input)
|
|||||||
.gif({ reuse: false })
|
.gif({ reuse: false })
|
||||||
.gif({ progressive: true })
|
.gif({ progressive: true })
|
||||||
.gif({ progressive: false })
|
.gif({ progressive: false })
|
||||||
|
.gif({ keepDuplicateFrames: true })
|
||||||
|
.gif({ keepDuplicateFrames: false })
|
||||||
.toBuffer({ resolveWithObject: true })
|
.toBuffer({ resolveWithObject: true })
|
||||||
.then(({ data, info }) => {
|
.then(({ data, info }) => {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
@ -187,6 +187,17 @@ describe('GIF input', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('invalid keepDuplicateFrames throws', () => {
|
||||||
|
assert.throws(
|
||||||
|
() => sharp().gif({ keepDuplicateFrames: -1 }),
|
||||||
|
/Expected boolean for keepDuplicateFrames but received -1 of type number/
|
||||||
|
);
|
||||||
|
assert.throws(
|
||||||
|
() => sharp().gif({ keepDuplicateFrames: 'fail' }),
|
||||||
|
/Expected boolean for keepDuplicateFrames 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 }))
|
||||||
@ -225,6 +236,20 @@ describe('GIF input', () => {
|
|||||||
assert.strict(before.length > after.length);
|
assert.strict(before.length > after.length);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should keep duplicate frames via keepDuplicateFrames', async () => {
|
||||||
|
const create = { width: 8, height: 8, channels: 4, background: 'blue' };
|
||||||
|
const input = sharp([{ create }, { create }], { join: { animated: true } });
|
||||||
|
|
||||||
|
const before = await input.gif({ keepDuplicateFrames: false }).toBuffer();
|
||||||
|
const after = await input.gif({ keepDuplicateFrames: true }).toBuffer();
|
||||||
|
assert.strict(before.length < after.length);
|
||||||
|
|
||||||
|
const beforeMeta = await sharp(before).metadata();
|
||||||
|
const afterMeta = await sharp(after).metadata();
|
||||||
|
assert.strictEqual(beforeMeta.pages, 1);
|
||||||
|
assert.strictEqual(afterMeta.pages, 2);
|
||||||
|
});
|
||||||
|
|
||||||
it('non-animated input defaults to no-loop', async () => {
|
it('non-animated input defaults to no-loop', async () => {
|
||||||
for (const input of [fixtures.inputGif, fixtures.inputPng]) {
|
for (const input of [fixtures.inputGif, fixtures.inputPng]) {
|
||||||
const data = await sharp(input)
|
const data = await sharp(input)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user