mirror of
https://github.com/lovell/sharp.git
synced 2026-02-08 07:36:16 +01:00
Add WebP 'exact' option for control over transparent pixels
This commit is contained in:
@@ -563,6 +563,7 @@ Use these WebP options for output image.
|
|||||||
| [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.minSize] | <code>boolean</code> | <code>false</code> | prevent use of animation key frames to minimise file size (slow) |
|
| [options.minSize] | <code>boolean</code> | <code>false</code> | prevent use of animation key frames to minimise file size (slow) |
|
||||||
| [options.mixed] | <code>boolean</code> | <code>false</code> | allow mixture of lossy and lossless animation frames (slow) |
|
| [options.mixed] | <code>boolean</code> | <code>false</code> | allow mixture of lossy and lossless animation frames (slow) |
|
||||||
|
| [options.exact] | <code>boolean</code> | <code>false</code> | preserve the colour data in transparent pixels |
|
||||||
| [options.force] | <code>boolean</code> | <code>true</code> | force WebP output, otherwise attempt to use input format |
|
| [options.force] | <code>boolean</code> | <code>true</code> | force WebP output, otherwise attempt to use input format |
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
|
|||||||
@@ -23,3 +23,5 @@ slug: changelog/v0.35.0
|
|||||||
|
|
||||||
* Add `toUint8Array` for output image as a `TypedArray` backed by a transferable `ArrayBuffer`.
|
* Add `toUint8Array` for output image as a `TypedArray` backed by a transferable `ArrayBuffer`.
|
||||||
[#4355](https://github.com/lovell/sharp/issues/4355)
|
[#4355](https://github.com/lovell/sharp/issues/4355)
|
||||||
|
|
||||||
|
* Add WebP `exact` option for control over transparent pixel colour values.
|
||||||
|
|||||||
@@ -350,6 +350,7 @@ const Sharp = function (input, options) {
|
|||||||
webpEffort: 4,
|
webpEffort: 4,
|
||||||
webpMinSize: false,
|
webpMinSize: false,
|
||||||
webpMixed: false,
|
webpMixed: false,
|
||||||
|
webpExact: false,
|
||||||
gifBitdepth: 8,
|
gifBitdepth: 8,
|
||||||
gifEffort: 7,
|
gifEffort: 7,
|
||||||
gifDither: 1,
|
gifDither: 1,
|
||||||
|
|||||||
6
lib/index.d.ts
vendored
6
lib/index.d.ts
vendored
@@ -1394,11 +1394,13 @@ declare namespace sharp {
|
|||||||
/** Level of CPU effort to reduce file size, integer 0-6 (optional, default 4) */
|
/** Level of CPU effort to reduce file size, integer 0-6 (optional, default 4) */
|
||||||
effort?: number | undefined;
|
effort?: number | undefined;
|
||||||
/** Prevent use of animation key frames to minimise file size (slow) (optional, default false) */
|
/** Prevent use of animation key frames to minimise file size (slow) (optional, default false) */
|
||||||
minSize?: boolean;
|
minSize?: boolean | undefined;
|
||||||
/** Allow mixture of lossy and lossless animation frames (slow) (optional, default false) */
|
/** Allow mixture of lossy and lossless animation frames (slow) (optional, default false) */
|
||||||
mixed?: boolean;
|
mixed?: boolean | undefined;
|
||||||
/** Preset options: one of default, photo, picture, drawing, icon, text (optional, default 'default') */
|
/** Preset options: one of default, photo, picture, drawing, icon, text (optional, default 'default') */
|
||||||
preset?: keyof PresetEnum | undefined;
|
preset?: keyof PresetEnum | undefined;
|
||||||
|
/** Preserve the colour data in transparent pixels (optional, default false) */
|
||||||
|
exact?: boolean | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AvifOptions extends OutputOptions {
|
interface AvifOptions extends OutputOptions {
|
||||||
|
|||||||
@@ -749,6 +749,7 @@ function png (options) {
|
|||||||
* @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.minSize=false] - prevent use of animation key frames to minimise file size (slow)
|
* @param {boolean} [options.minSize=false] - prevent use of animation key frames to minimise file size (slow)
|
||||||
* @param {boolean} [options.mixed=false] - allow mixture of lossy and lossless animation frames (slow)
|
* @param {boolean} [options.mixed=false] - allow mixture of lossy and lossless animation frames (slow)
|
||||||
|
* @param {boolean} [options.exact=false] - preserve the colour data in transparent pixels
|
||||||
* @param {boolean} [options.force=true] - force WebP output, otherwise attempt to use input format
|
* @param {boolean} [options.force=true] - force WebP output, otherwise attempt to use input format
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid options
|
* @throws {Error} Invalid options
|
||||||
@@ -801,6 +802,9 @@ function webp (options) {
|
|||||||
if (is.defined(options.mixed)) {
|
if (is.defined(options.mixed)) {
|
||||||
this._setBooleanOption('webpMixed', options.mixed);
|
this._setBooleanOption('webpMixed', options.mixed);
|
||||||
}
|
}
|
||||||
|
if (is.defined(options.exact)) {
|
||||||
|
this._setBooleanOption('webpExact', options.exact);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
trySetAnimationOptions(options, this.options);
|
trySetAnimationOptions(options, this.options);
|
||||||
return this._updateFormatOut('webp', options);
|
return this._updateFormatOut('webp', options);
|
||||||
|
|||||||
@@ -965,6 +965,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
->set("effort", baton->webpEffort)
|
->set("effort", baton->webpEffort)
|
||||||
->set("min_size", baton->webpMinSize)
|
->set("min_size", baton->webpMinSize)
|
||||||
->set("mixed", baton->webpMixed)
|
->set("mixed", baton->webpMixed)
|
||||||
|
->set("exact", baton->webpExact)
|
||||||
->set("alpha_q", baton->webpAlphaQuality)));
|
->set("alpha_q", baton->webpAlphaQuality)));
|
||||||
baton->bufferOut = static_cast<char*>(area->data);
|
baton->bufferOut = static_cast<char*>(area->data);
|
||||||
baton->bufferOutLength = area->length;
|
baton->bufferOutLength = area->length;
|
||||||
@@ -1176,6 +1177,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
->set("effort", baton->webpEffort)
|
->set("effort", baton->webpEffort)
|
||||||
->set("min_size", baton->webpMinSize)
|
->set("min_size", baton->webpMinSize)
|
||||||
->set("mixed", baton->webpMixed)
|
->set("mixed", baton->webpMixed)
|
||||||
|
->set("exact", baton->webpExact)
|
||||||
->set("alpha_q", baton->webpAlphaQuality));
|
->set("alpha_q", baton->webpAlphaQuality));
|
||||||
baton->formatOut = "webp";
|
baton->formatOut = "webp";
|
||||||
} else if (baton->formatOut == "gif" || (mightMatchInput && isGif) ||
|
} else if (baton->formatOut == "gif" || (mightMatchInput && isGif) ||
|
||||||
@@ -1486,6 +1488,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
{"preset", vips_enum_nick(VIPS_TYPE_FOREIGN_WEBP_PRESET, baton->webpPreset)},
|
{"preset", vips_enum_nick(VIPS_TYPE_FOREIGN_WEBP_PRESET, baton->webpPreset)},
|
||||||
{"min_size", baton->webpMinSize ? "true" : "false"},
|
{"min_size", baton->webpMinSize ? "true" : "false"},
|
||||||
{"mixed", baton->webpMixed ? "true" : "false"},
|
{"mixed", baton->webpMixed ? "true" : "false"},
|
||||||
|
{"exact", baton->webpExact ? "true" : "false"},
|
||||||
{"effort", std::to_string(baton->webpEffort)}
|
{"effort", std::to_string(baton->webpEffort)}
|
||||||
};
|
};
|
||||||
suffix = AssembleSuffixString(".webp", options);
|
suffix = AssembleSuffixString(".webp", options);
|
||||||
@@ -1760,6 +1763,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort");
|
baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort");
|
||||||
baton->webpMinSize = sharp::AttrAsBool(options, "webpMinSize");
|
baton->webpMinSize = sharp::AttrAsBool(options, "webpMinSize");
|
||||||
baton->webpMixed = sharp::AttrAsBool(options, "webpMixed");
|
baton->webpMixed = sharp::AttrAsBool(options, "webpMixed");
|
||||||
|
baton->webpExact = sharp::AttrAsBool(options, "webpExact");
|
||||||
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");
|
||||||
|
|||||||
@@ -168,6 +168,7 @@ struct PipelineBaton {
|
|||||||
int webpEffort;
|
int webpEffort;
|
||||||
bool webpMinSize;
|
bool webpMinSize;
|
||||||
bool webpMixed;
|
bool webpMixed;
|
||||||
|
bool webpExact;
|
||||||
int gifBitdepth;
|
int gifBitdepth;
|
||||||
int gifEffort;
|
int gifEffort;
|
||||||
double gifDither;
|
double gifDither;
|
||||||
@@ -347,6 +348,7 @@ struct PipelineBaton {
|
|||||||
webpEffort(4),
|
webpEffort(4),
|
||||||
webpMinSize(false),
|
webpMinSize(false),
|
||||||
webpMixed(false),
|
webpMixed(false),
|
||||||
|
webpExact(false),
|
||||||
gifBitdepth(8),
|
gifBitdepth(8),
|
||||||
gifEffort(7),
|
gifEffort(7),
|
||||||
gifDither(1.0),
|
gifDither(1.0),
|
||||||
|
|||||||
@@ -548,8 +548,8 @@ sharp('input.tiff').jxl({ decodingTier: 4 }).toFile('out.jxl');
|
|||||||
sharp('input.tiff').jxl({ lossless: true }).toFile('out.jxl');
|
sharp('input.tiff').jxl({ lossless: true }).toFile('out.jxl');
|
||||||
sharp('input.tiff').jxl({ effort: 7 }).toFile('out.jxl');
|
sharp('input.tiff').jxl({ effort: 7 }).toFile('out.jxl');
|
||||||
|
|
||||||
// Support `minSize` and `mixed` webp options
|
// Support webp options
|
||||||
sharp('input.tiff').webp({ minSize: true, mixed: true }).toFile('out.gif');
|
sharp('input.tiff').webp({ minSize: true, mixed: true, exact: true }).toFile('out.webp');
|
||||||
|
|
||||||
// 'failOn' input param
|
// 'failOn' input param
|
||||||
sharp('input.tiff', { failOn: 'none' });
|
sharp('input.tiff', { failOn: 'none' });
|
||||||
|
|||||||
@@ -213,6 +213,33 @@ describe('WebP', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('valid exact', () => {
|
||||||
|
assert.doesNotThrow(() => sharp().webp({ exact: true }));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid exact throws', () => {
|
||||||
|
assert.throws(
|
||||||
|
() => sharp().webp({ exact: 'fail' }),
|
||||||
|
/Expected boolean for webpExact but received fail of type string/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('saving exact pixel colour values produces larger file size', async () => {
|
||||||
|
const withExact = await
|
||||||
|
sharp(fixtures.inputPngAlphaPremultiplicationSmall)
|
||||||
|
.resize(8, 8)
|
||||||
|
.webp({ exact: true, effort: 0 })
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
const withoutExact = await
|
||||||
|
sharp(fixtures.inputPngAlphaPremultiplicationSmall)
|
||||||
|
.resize(8, 8)
|
||||||
|
.webp({ exact: false, effort: 0 })
|
||||||
|
.toBuffer()
|
||||||
|
|
||||||
|
assert.strictEqual(true, withExact.length > withoutExact.length);
|
||||||
|
});
|
||||||
|
|
||||||
it('invalid loop throws', () => {
|
it('invalid loop throws', () => {
|
||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
sharp().webp({ loop: -1 });
|
sharp().webp({ loop: -1 });
|
||||||
|
|||||||
Reference in New Issue
Block a user