Standardise HEIF effort option name, deprecate speed

This commit is contained in:
Lovell Fuller 2021-11-24 19:54:04 +00:00
parent 2b1f5cbe07
commit e1ba2a7fd8
8 changed files with 50 additions and 29 deletions

View File

@ -282,7 +282,7 @@ Use these WebP options for output image.
* `options.lossless` **[boolean][7]** use lossless compression mode (optional, default `false`) * `options.lossless` **[boolean][7]** use lossless compression mode (optional, default `false`)
* `options.nearLossless` **[boolean][7]** use near_lossless compression mode (optional, default `false`) * `options.nearLossless` **[boolean][7]** use near_lossless compression mode (optional, default `false`)
* `options.smartSubsample` **[boolean][7]** use high quality chroma subsampling (optional, default `false`) * `options.smartSubsample` **[boolean][7]** use high quality chroma subsampling (optional, default `false`)
* `options.effort` **[number][9]** level of CPU effort to reduce file size, integer 0-6 (optional, default `4`) * `options.effort` **[number][9]** CPU effort, between 0 (fastest) and 6 (slowest) (optional, default `4`)
* `options.pageHeight` **[number][9]?** page height for animated output * `options.pageHeight` **[number][9]?** page height for animated output
* `options.loop` **[number][9]** number of animation iterations, use 0 for infinite animation (optional, default `0`) * `options.loop` **[number][9]** number of animation iterations, use 0 for infinite animation (optional, default `0`)
* `options.delay` **[Array][10]<[number][9]>?** list of delays between animation frames (in milliseconds) * `options.delay` **[Array][10]<[number][9]>?** list of delays between animation frames (in milliseconds)
@ -460,7 +460,7 @@ AVIF image sequences are not supported.
* `options.quality` **[number][9]** quality, integer 1-100 (optional, default `50`) * `options.quality` **[number][9]** quality, integer 1-100 (optional, default `50`)
* `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`) * `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
* `options.speed` **[number][9]** CPU effort vs file size, 0 (slowest/smallest) to 9 (fastest/largest) (optional, default `5`) * `options.effort` **[number][9]** CPU effort, between 0 (fastest) and 9 (slowest) (optional, default `4`)
* `options.chromaSubsampling` **[string][2]** set to '4:2:0' to use chroma subsampling (optional, default `'4:4:4'`) * `options.chromaSubsampling` **[string][2]** set to '4:2:0' to use chroma subsampling (optional, default `'4:4:4'`)
<!----> <!---->
@ -487,7 +487,7 @@ globally-installed libvips compiled with support for libheif, libde265 and x265.
* `options.quality` **[number][9]** quality, integer 1-100 (optional, default `50`) * `options.quality` **[number][9]** quality, integer 1-100 (optional, default `50`)
* `options.compression` **[string][2]** compression format: av1, hevc (optional, default `'av1'`) * `options.compression` **[string][2]** compression format: av1, hevc (optional, default `'av1'`)
* `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`) * `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
* `options.speed` **[number][9]** CPU effort vs file size, 0 (slowest/smallest) to 9 (fastest/largest) (optional, default `5`) * `options.effort` **[number][9]** CPU effort, between 0 (fastest) and 9 (slowest) (optional, default `4`)
* `options.chromaSubsampling` **[string][2]** set to '4:2:0' to use chroma subsampling (optional, default `'4:4:4'`) * `options.chromaSubsampling` **[string][2]** set to '4:2:0' to use chroma subsampling (optional, default `'4:4:4'`)
<!----> <!---->

View File

@ -12,6 +12,8 @@ Requires libvips v8.12.0
* Standardise WebP `effort` option name, deprecate `reductionEffort`. * Standardise WebP `effort` option name, deprecate `reductionEffort`.
* Standardise HEIF `effort` option name, deprecate `speed`.
* Expose control over CPU effort for palette-based PNG output. * Expose control over CPU effort for palette-based PNG output.
[#2541](https://github.com/lovell/sharp/issues/2541) [#2541](https://github.com/lovell/sharp/issues/2541)

View File

@ -264,7 +264,7 @@ const Sharp = function (input, options) {
heifQuality: 50, heifQuality: 50,
heifLossless: false, heifLossless: false,
heifCompression: 'av1', heifCompression: 'av1',
heifSpeed: 5, heifEffort: 4,
heifChromaSubsampling: '4:4:4', heifChromaSubsampling: '4:4:4',
rawDepth: 'uchar', rawDepth: 'uchar',
tileSize: 256, tileSize: 256,

View File

@ -457,7 +457,7 @@ function png (options) {
* @param {boolean} [options.lossless=false] - use lossless compression mode * @param {boolean} [options.lossless=false] - use lossless compression mode
* @param {boolean} [options.nearLossless=false] - use near_lossless compression mode * @param {boolean} [options.nearLossless=false] - use near_lossless compression mode
* @param {boolean} [options.smartSubsample=false] - use high quality chroma subsampling * @param {boolean} [options.smartSubsample=false] - use high quality chroma subsampling
* @param {number} [options.effort=4] - level of CPU effort to reduce file size, integer 0-6 * @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 6 (slowest)
* @param {number} [options.pageHeight] - page height for animated output * @param {number} [options.pageHeight] - page height for animated output
* @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[]} [options.delay] - list of delays between animation frames (in milliseconds) * @param {number[]} [options.delay] - list of delays between animation frames (in milliseconds)
@ -806,7 +806,7 @@ function tiff (options) {
* @param {Object} [options] - output options * @param {Object} [options] - output options
* @param {number} [options.quality=50] - quality, integer 1-100 * @param {number} [options.quality=50] - quality, integer 1-100
* @param {boolean} [options.lossless=false] - use lossless compression * @param {boolean} [options.lossless=false] - use lossless compression
* @param {number} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 9 (fastest/largest) * @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
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
@ -827,7 +827,7 @@ function avif (options) {
* @param {number} [options.quality=50] - quality, integer 1-100 * @param {number} [options.quality=50] - quality, integer 1-100
* @param {string} [options.compression='av1'] - compression format: av1, hevc * @param {string} [options.compression='av1'] - compression format: av1, hevc
* @param {boolean} [options.lossless=false] - use lossless compression * @param {boolean} [options.lossless=false] - use lossless compression
* @param {number} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 9 (fastest/largest) * @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
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
@ -855,9 +855,15 @@ function heif (options) {
throw is.invalidParameterError('compression', 'one of: av1, hevc', options.compression); throw is.invalidParameterError('compression', 'one of: av1, hevc', options.compression);
} }
} }
if (is.defined(options.speed)) { if (is.defined(options.effort)) {
if (is.integer(options.effort) && is.inRange(options.effort, 0, 9)) {
this.options.heifEffort = options.effort;
} else {
throw is.invalidParameterError('effort', 'integer between 0 and 9', options.effort);
}
} else if (is.defined(options.speed)) {
if (is.integer(options.speed) && is.inRange(options.speed, 0, 9)) { if (is.integer(options.speed) && is.inRange(options.speed, 0, 9)) {
this.options.heifSpeed = options.speed; this.options.heifEffort = 9 - options.speed;
} else { } else {
throw is.invalidParameterError('speed', 'integer between 0 and 9', options.speed); throw is.invalidParameterError('speed', 'integer between 0 and 9', options.speed);
} }

View File

@ -899,7 +899,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->heifQuality) ->set("Q", baton->heifQuality)
->set("compression", baton->heifCompression) ->set("compression", baton->heifCompression)
->set("speed", baton->heifSpeed) ->set("effort", baton->heifEffort)
->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)));
@ -1053,7 +1053,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->heifQuality) ->set("Q", baton->heifQuality)
->set("compression", baton->heifCompression) ->set("compression", baton->heifCompression)
->set("speed", baton->heifSpeed) ->set("effort", baton->heifEffort)
->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));
@ -1512,7 +1512,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->heifCompression = static_cast<VipsForeignHeifCompression>( baton->heifCompression = static_cast<VipsForeignHeifCompression>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION, vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
sharp::AttrAsStr(options, "heifCompression").data())); sharp::AttrAsStr(options, "heifCompression").data()));
baton->heifSpeed = sharp::AttrAsUint32(options, "heifSpeed"); baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort");
baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling"); baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
// Raw output // Raw output

View File

@ -176,7 +176,7 @@ struct PipelineBaton {
double tiffYres; double tiffYres;
int heifQuality; int heifQuality;
VipsForeignHeifCompression heifCompression; VipsForeignHeifCompression heifCompression;
int heifSpeed; int heifEffort;
std::string heifChromaSubsampling; std::string heifChromaSubsampling;
bool heifLossless; bool heifLossless;
VipsBandFormat rawDepth; VipsBandFormat rawDepth;
@ -314,7 +314,7 @@ struct PipelineBaton {
tiffYres(1.0), tiffYres(1.0),
heifQuality(50), heifQuality(50),
heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_AV1), heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_AV1),
heifSpeed(5), heifEffort(4),
heifChromaSubsampling("4:4:4"), heifChromaSubsampling("4:4:4"),
heifLossless(false), heifLossless(false),
rawDepth(VIPS_FORMAT_UCHAR), rawDepth(VIPS_FORMAT_UCHAR),

View File

@ -17,10 +17,9 @@ describe('AVIF', () => {
.resize(32) .resize(32)
.jpeg() .jpeg()
.toBuffer(); .toBuffer();
const metadata = await sharp(data) const { size, ...metadata } = await sharp(data)
.metadata(); .metadata();
const { compression, size, ...metadataWithoutSize } = metadata; assert.deepStrictEqual(metadata, {
assert.deepStrictEqual(metadataWithoutSize, {
channels: 3, channels: 3,
chromaSubsampling: '4:2:0', chromaSubsampling: '4:2:0',
density: 72, density: 72,
@ -38,13 +37,13 @@ describe('AVIF', () => {
it('can convert JPEG to AVIF', async () => { it('can convert JPEG to AVIF', async () => {
const data = await sharp(inputJpg) const data = await sharp(inputJpg)
.resize(32) .resize(32)
.avif() .avif({ effort: 0 })
.toBuffer(); .toBuffer();
const metadata = await sharp(data) const { size, ...metadata } = await sharp(data)
.metadata(); .metadata();
const { compression, size, ...metadataWithoutSize } = metadata; assert.deepStrictEqual(metadata, {
assert.deepStrictEqual(metadataWithoutSize, {
channels: 3, channels: 3,
compression: 'av1',
depth: 'uchar', depth: 'uchar',
format: 'heif', format: 'heif',
hasAlpha: false, hasAlpha: false,
@ -63,11 +62,11 @@ describe('AVIF', () => {
const data = await sharp(inputAvif) const data = await sharp(inputAvif)
.resize(32) .resize(32)
.toBuffer(); .toBuffer();
const metadata = await sharp(data) const { size, ...metadata } = await sharp(data)
.metadata(); .metadata();
const { compression, size, ...metadataWithoutSize } = metadata; assert.deepStrictEqual(metadata, {
assert.deepStrictEqual(metadataWithoutSize, {
channels: 3, channels: 3,
compression: 'av1',
depth: 'uchar', depth: 'uchar',
format: 'heif', format: 'heif',
hasAlpha: false, hasAlpha: false,
@ -85,12 +84,11 @@ describe('AVIF', () => {
it('can convert animated GIF to non-animated AVIF', async () => { it('can convert animated GIF to non-animated AVIF', async () => {
const data = await sharp(inputGifAnimated, { animated: true }) const data = await sharp(inputGifAnimated, { animated: true })
.resize(10) .resize(10)
.avif({ speed: 8 }) .avif({ effort: 0 })
.toBuffer(); .toBuffer();
const metadata = await sharp(data) const { size, ...metadata } = await sharp(data)
.metadata(); .metadata();
const { size, ...metadataWithoutSize } = metadata; assert.deepStrictEqual(metadata, {
assert.deepStrictEqual(metadataWithoutSize, {
channels: 4, channels: 4,
compression: 'av1', compression: 'av1',
depth: 'uchar', depth: 'uchar',

View File

@ -50,6 +50,21 @@ describe('HEIF', () => {
sharp().heif({ compression: 1 }); sharp().heif({ compression: 1 });
}); });
}); });
it('valid effort does not throw an error', () => {
assert.doesNotThrow(() => {
sharp().heif({ speed: 6 });
});
});
it('out of range effort should throw an error', () => {
assert.throws(() => {
sharp().heif({ effort: 10 });
});
});
it('invalid effort should throw an error', () => {
assert.throws(() => {
sharp().heif({ effort: 'fail' });
});
});
it('valid speed does not throw an error', () => { it('valid speed does not throw an error', () => {
assert.doesNotThrow(() => { assert.doesNotThrow(() => {
sharp().heif({ speed: 6 }); sharp().heif({ speed: 6 });
@ -62,7 +77,7 @@ describe('HEIF', () => {
}); });
it('invalid speed should throw an error', () => { it('invalid speed should throw an error', () => {
assert.throws(() => { assert.throws(() => {
sharp().heif({ compression: 'fail' }); sharp().heif({ speed: 'fail' });
}); });
}); });
it('invalid chromaSubsampling should throw an error', () => { it('invalid chromaSubsampling should throw an error', () => {