mirror of
https://github.com/lovell/sharp.git
synced 2026-02-04 05:36:18 +01:00
Add AVIF/HEIF 'tune' option to control quality metrics #4227
This commit is contained in:
@@ -758,6 +758,7 @@ When using Windows ARM64, this feature requires a CPU with ARM64v8.4 or later.
|
|||||||
| [options.effort] | <code>number</code> | <code>4</code> | CPU effort, between 0 (fastest) and 9 (slowest) |
|
| [options.effort] | <code>number</code> | <code>4</code> | CPU effort, between 0 (fastest) and 9 (slowest) |
|
||||||
| [options.chromaSubsampling] | <code>string</code> | <code>"'4:4:4'"</code> | set to '4:2:0' to use chroma subsampling |
|
| [options.chromaSubsampling] | <code>string</code> | <code>"'4:4:4'"</code> | set to '4:2:0' to use chroma subsampling |
|
||||||
| [options.bitdepth] | <code>number</code> | <code>8</code> | set bitdepth to 8, 10 or 12 bit |
|
| [options.bitdepth] | <code>number</code> | <code>8</code> | set bitdepth to 8, 10 or 12 bit |
|
||||||
|
| [options.tune] | <code>string</code> | <code>"'iq'"</code> | tune output for a quality metric, one of 'iq' (default), 'ssim' or 'psnr' |
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
```js
|
```js
|
||||||
@@ -797,6 +798,7 @@ globally-installed libvips compiled with support for libheif, libde265 and x265.
|
|||||||
| [options.effort] | <code>number</code> | <code>4</code> | CPU effort, between 0 (fastest) and 9 (slowest) |
|
| [options.effort] | <code>number</code> | <code>4</code> | CPU effort, between 0 (fastest) and 9 (slowest) |
|
||||||
| [options.chromaSubsampling] | <code>string</code> | <code>"'4:4:4'"</code> | set to '4:2:0' to use chroma subsampling |
|
| [options.chromaSubsampling] | <code>string</code> | <code>"'4:4:4'"</code> | set to '4:2:0' to use chroma subsampling |
|
||||||
| [options.bitdepth] | <code>number</code> | <code>8</code> | set bitdepth to 8, 10 or 12 bit |
|
| [options.bitdepth] | <code>number</code> | <code>8</code> | set bitdepth to 8, 10 or 12 bit |
|
||||||
|
| [options.tune] | <code>string</code> | <code>"'ssim'"</code> | tune output for a quality metric, one of 'ssim' (default), 'psnr' or 'iq' |
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
```js
|
```js
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ slug: changelog/v0.35.0
|
|||||||
* Breaking: Remove `install` script from `package.json` file.
|
* Breaking: Remove `install` script from `package.json` file.
|
||||||
Compiling from source is now opt-in via the `build` script.
|
Compiling from source is now opt-in via the `build` script.
|
||||||
|
|
||||||
|
* Breaking: AVIF output is now tuned using SSIMULACRA2-based `iq` quality metrics rather than `ssim`.
|
||||||
|
|
||||||
* Breaking: Remove deprecated `failOnError` constructor property.
|
* Breaking: Remove deprecated `failOnError` constructor property.
|
||||||
|
|
||||||
* Breaking: Remove deprecated `paletteBitDepth` from `metadata` response.
|
* Breaking: Remove deprecated `paletteBitDepth` from `metadata` response.
|
||||||
@@ -18,6 +20,9 @@ slug: changelog/v0.35.0
|
|||||||
|
|
||||||
* Deprecate Windows 32-bit (win32-ia32) prebuilt binaries.
|
* Deprecate Windows 32-bit (win32-ia32) prebuilt binaries.
|
||||||
|
|
||||||
|
* Add AVIF/HEIF `tune` option for control over quality metrics.
|
||||||
|
[#4227](https://github.com/lovell/sharp/issues/4227)
|
||||||
|
|
||||||
* Add `withGainMap` to process HDR JPEG images with embedded gain maps.
|
* Add `withGainMap` to process HDR JPEG images with embedded gain maps.
|
||||||
[#4314](https://github.com/lovell/sharp/issues/4314)
|
[#4314](https://github.com/lovell/sharp/issues/4314)
|
||||||
|
|
||||||
|
|||||||
@@ -378,6 +378,7 @@ const Sharp = function (input, options) {
|
|||||||
heifEffort: 4,
|
heifEffort: 4,
|
||||||
heifChromaSubsampling: '4:4:4',
|
heifChromaSubsampling: '4:4:4',
|
||||||
heifBitdepth: 8,
|
heifBitdepth: 8,
|
||||||
|
heifTune: 'ssim',
|
||||||
jxlDistance: 1,
|
jxlDistance: 1,
|
||||||
jxlDecodingTier: 0,
|
jxlDecodingTier: 0,
|
||||||
jxlEffort: 7,
|
jxlEffort: 7,
|
||||||
|
|||||||
6
lib/index.d.ts
vendored
6
lib/index.d.ts
vendored
@@ -1167,6 +1167,8 @@ declare namespace sharp {
|
|||||||
|
|
||||||
type HeifCompression = 'av1' | 'hevc';
|
type HeifCompression = 'av1' | 'hevc';
|
||||||
|
|
||||||
|
type HeifTune = 'iq' | 'ssim' | 'psnr';
|
||||||
|
|
||||||
type Unit = 'inch' | 'cm';
|
type Unit = 'inch' | 'cm';
|
||||||
|
|
||||||
interface WriteableMetadata {
|
interface WriteableMetadata {
|
||||||
@@ -1414,6 +1416,8 @@ declare namespace sharp {
|
|||||||
chromaSubsampling?: string | undefined;
|
chromaSubsampling?: string | undefined;
|
||||||
/** Set bitdepth to 8, 10 or 12 bit (optional, default 8) */
|
/** Set bitdepth to 8, 10 or 12 bit (optional, default 8) */
|
||||||
bitdepth?: 8 | 10 | 12 | undefined;
|
bitdepth?: 8 | 10 | 12 | undefined;
|
||||||
|
/** Tune output for a quality metric, one of 'iq', 'ssim' or 'psnr' (optional, default 'iq') */
|
||||||
|
tune?: HeifTune | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HeifOptions extends OutputOptions {
|
interface HeifOptions extends OutputOptions {
|
||||||
@@ -1429,6 +1433,8 @@ declare namespace sharp {
|
|||||||
chromaSubsampling?: string | undefined;
|
chromaSubsampling?: string | undefined;
|
||||||
/** Set bitdepth to 8, 10 or 12 bit (optional, default 8) */
|
/** Set bitdepth to 8, 10 or 12 bit (optional, default 8) */
|
||||||
bitdepth?: 8 | 10 | 12 | undefined;
|
bitdepth?: 8 | 10 | 12 | undefined;
|
||||||
|
/** Tune output for a quality metric, one of 'ssim', 'psnr' or 'iq' (optional, default 'ssim') */
|
||||||
|
tune?: HeifTune | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GifOptions extends OutputOptions, AnimationOptions {
|
interface GifOptions extends OutputOptions, AnimationOptions {
|
||||||
|
|||||||
@@ -1175,11 +1175,13 @@ function tiff (options) {
|
|||||||
* @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 9 (slowest)
|
* @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 9 (slowest)
|
||||||
* @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
|
* @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
|
||||||
* @param {number} [options.bitdepth=8] - set bitdepth to 8, 10 or 12 bit
|
* @param {number} [options.bitdepth=8] - set bitdepth to 8, 10 or 12 bit
|
||||||
|
* @param {string} [options.tune='iq'] - tune output for a quality metric, one of 'iq' (default), 'ssim' or 'psnr'
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid options
|
* @throws {Error} Invalid options
|
||||||
*/
|
*/
|
||||||
function avif (options) {
|
function avif (options) {
|
||||||
return this.heif({ ...options, compression: 'av1' });
|
const tune = is.object(options) && is.defined(options.tune) ? options.tune : 'iq';
|
||||||
|
return this.heif({ ...options, compression: 'av1', tune });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1202,6 +1204,7 @@ function avif (options) {
|
|||||||
* @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 9 (slowest)
|
* @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 9 (slowest)
|
||||||
* @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
|
* @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
|
||||||
* @param {number} [options.bitdepth=8] - set bitdepth to 8, 10 or 12 bit
|
* @param {number} [options.bitdepth=8] - set bitdepth to 8, 10 or 12 bit
|
||||||
|
* @param {string} [options.tune='ssim'] - tune output for a quality metric, one of 'ssim' (default), 'psnr' or 'iq'
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid options
|
* @throws {Error} Invalid options
|
||||||
*/
|
*/
|
||||||
@@ -1250,6 +1253,13 @@ function heif (options) {
|
|||||||
throw is.invalidParameterError('bitdepth', '8, 10 or 12', options.bitdepth);
|
throw is.invalidParameterError('bitdepth', '8, 10 or 12', options.bitdepth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (is.defined(options.tune)) {
|
||||||
|
if (is.string(options.tune) && is.inArray(options.tune, ['iq', 'ssim', 'psnr'])) {
|
||||||
|
this.options.heifTune = options.tune;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('tune', 'one of: psnr, ssim, iq', options.tune);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw is.invalidParameterError('options', 'Object', options);
|
throw is.invalidParameterError('options', 'Object', options);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1033,6 +1033,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
->set("compression", baton->heifCompression)
|
->set("compression", baton->heifCompression)
|
||||||
->set("effort", baton->heifEffort)
|
->set("effort", baton->heifEffort)
|
||||||
->set("bitdepth", baton->heifBitdepth)
|
->set("bitdepth", baton->heifBitdepth)
|
||||||
|
->set("tune", baton->heifTune.c_str())
|
||||||
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
||||||
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
||||||
->set("lossless", baton->heifLossless)));
|
->set("lossless", baton->heifLossless)));
|
||||||
@@ -1233,6 +1234,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
->set("compression", baton->heifCompression)
|
->set("compression", baton->heifCompression)
|
||||||
->set("effort", baton->heifEffort)
|
->set("effort", baton->heifEffort)
|
||||||
->set("bitdepth", baton->heifBitdepth)
|
->set("bitdepth", baton->heifBitdepth)
|
||||||
|
->set("tune", baton->heifTune.c_str())
|
||||||
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
||||||
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
||||||
->set("lossless", baton->heifLossless));
|
->set("lossless", baton->heifLossless));
|
||||||
@@ -1798,6 +1800,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort");
|
baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort");
|
||||||
baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
|
baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
|
||||||
baton->heifBitdepth = sharp::AttrAsUint32(options, "heifBitdepth");
|
baton->heifBitdepth = sharp::AttrAsUint32(options, "heifBitdepth");
|
||||||
|
baton->heifTune = sharp::AttrAsStr(options, "heifTune");
|
||||||
baton->jxlDistance = sharp::AttrAsDouble(options, "jxlDistance");
|
baton->jxlDistance = sharp::AttrAsDouble(options, "jxlDistance");
|
||||||
baton->jxlDecodingTier = sharp::AttrAsUint32(options, "jxlDecodingTier");
|
baton->jxlDecodingTier = sharp::AttrAsUint32(options, "jxlDecodingTier");
|
||||||
baton->jxlEffort = sharp::AttrAsUint32(options, "jxlEffort");
|
baton->jxlEffort = sharp::AttrAsUint32(options, "jxlEffort");
|
||||||
|
|||||||
@@ -196,6 +196,7 @@ struct PipelineBaton {
|
|||||||
std::string heifChromaSubsampling;
|
std::string heifChromaSubsampling;
|
||||||
bool heifLossless;
|
bool heifLossless;
|
||||||
int heifBitdepth;
|
int heifBitdepth;
|
||||||
|
std::string heifTune;
|
||||||
double jxlDistance;
|
double jxlDistance;
|
||||||
int jxlDecodingTier;
|
int jxlDecodingTier;
|
||||||
int jxlEffort;
|
int jxlEffort;
|
||||||
@@ -376,6 +377,7 @@ struct PipelineBaton {
|
|||||||
heifChromaSubsampling("4:4:4"),
|
heifChromaSubsampling("4:4:4"),
|
||||||
heifLossless(false),
|
heifLossless(false),
|
||||||
heifBitdepth(8),
|
heifBitdepth(8),
|
||||||
|
heifTune("ssim"),
|
||||||
jxlDistance(1.0),
|
jxlDistance(1.0),
|
||||||
jxlDecodingTier(0),
|
jxlDecodingTier(0),
|
||||||
jxlEffort(7),
|
jxlEffort(7),
|
||||||
|
|||||||
@@ -365,7 +365,7 @@ sharp(input)
|
|||||||
.avif({ quality: 50, lossless: false, effort: 5, chromaSubsampling: '4:2:0' })
|
.avif({ quality: 50, lossless: false, effort: 5, chromaSubsampling: '4:2:0' })
|
||||||
.heif()
|
.heif()
|
||||||
.heif({})
|
.heif({})
|
||||||
.heif({ quality: 50, compression: 'hevc', lossless: false, effort: 5, chromaSubsampling: '4:2:0' })
|
.heif({ quality: 50, compression: 'hevc', lossless: false, effort: 5, chromaSubsampling: '4:2:0', tune: 'psnr' })
|
||||||
.toBuffer({ resolveWithObject: true })
|
.toBuffer({ resolveWithObject: true })
|
||||||
.then(({ data, info }) => {
|
.then(({ data, info }) => {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
|||||||
@@ -181,4 +181,16 @@ describe('AVIF', () => {
|
|||||||
/Expected 8, 10 or 12 for bitdepth but received 11 of type number/
|
/Expected 8, 10 or 12 for bitdepth but received 11 of type number/
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it('Different tune options result in different file sizes', async () => {
|
||||||
|
const ssim = await sharp(inputJpg)
|
||||||
|
.resize(32)
|
||||||
|
.avif({ tune: 'ssim', effort: 0 })
|
||||||
|
.toBuffer();
|
||||||
|
const iq = await sharp(inputJpg)
|
||||||
|
.resize(32)
|
||||||
|
.avif({ tune: 'iq', effort: 0 })
|
||||||
|
.toBuffer();
|
||||||
|
assert(ssim.length < iq.length);
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -96,4 +96,14 @@ describe('HEIF', () => {
|
|||||||
sharp().heif({ compression: 'av1', bitdepth: 11 });
|
sharp().heif({ compression: 'av1', bitdepth: 11 });
|
||||||
}, /Error: Expected 8, 10 or 12 for bitdepth but received 11 of type number/);
|
}, /Error: Expected 8, 10 or 12 for bitdepth but received 11 of type number/);
|
||||||
});
|
});
|
||||||
|
it('valid tune does not throw an error', () => {
|
||||||
|
assert.doesNotThrow(() => {
|
||||||
|
sharp().heif({ compression: 'hevc', tune: 'psnr' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('invalid tune should throw an error', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().heif({ compression: 'hevc', tune: 'fail' });
|
||||||
|
}, /Error: Expected one of: psnr, ssim, iq for tune but received fail of type string/);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user