Encoding lossless AVIF is mutually exclusive with iq tuning

This commit is contained in:
Lovell Fuller
2026-01-07 20:39:53 +00:00
parent 4b1680c312
commit 0468c1be9f
3 changed files with 37 additions and 4 deletions

View File

@@ -758,7 +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>&quot;&#x27;4:4:4&#x27;&quot;</code> | set to '4:2:0' to use chroma subsampling | | [options.chromaSubsampling] | <code>string</code> | <code>&quot;&#x27;4:4:4&#x27;&quot;</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>&quot;&#x27;iq&#x27;&quot;</code> | tune output for a quality metric, one of 'iq' (default), 'ssim' or 'psnr' | | [options.tune] | <code>string</code> | <code>&quot;&#x27;iq&#x27;&quot;</code> | tune output for a quality metric, one of 'iq' (default), 'ssim' (default when lossless) or 'psnr' |
**Example** **Example**
```js ```js

View File

@@ -1175,7 +1175,7 @@ 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' * @param {string} [options.tune='iq'] - tune output for a quality metric, one of 'iq' (default), 'ssim' (default when lossless) or 'psnr'
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
@@ -1255,7 +1255,11 @@ function heif (options) {
} }
if (is.defined(options.tune)) { if (is.defined(options.tune)) {
if (is.string(options.tune) && is.inArray(options.tune, ['iq', 'ssim', 'psnr'])) { if (is.string(options.tune) && is.inArray(options.tune, ['iq', 'ssim', 'psnr'])) {
if (this.options.heifLossless && options.tune === 'iq') {
this.options.heifTune = 'ssim';
} else {
this.options.heifTune = options.tune; this.options.heifTune = options.tune;
}
} else { } else {
throw is.invalidParameterError('tune', 'one of: psnr, ssim, iq', options.tune); throw is.invalidParameterError('tune', 'one of: psnr, ssim, iq', options.tune);
} }

View File

@@ -7,7 +7,7 @@ const { describe, it } = require('node:test');
const assert = require('node:assert'); const assert = require('node:assert');
const sharp = require('../../'); const sharp = require('../../');
const { inputAvif, inputJpg, inputGifAnimated } = require('../fixtures'); const { inputAvif, inputJpg, inputGifAnimated, inputPng } = require('../fixtures');
describe('AVIF', () => { describe('AVIF', () => {
it('called without options does not throw an error', () => { it('called without options does not throw an error', () => {
@@ -74,6 +74,35 @@ describe('AVIF', () => {
}); });
}); });
it('can convert PNG to lossless AVIF', async () => {
const data = await sharp(inputPng)
.resize(32)
.avif({ lossless: true, effort: 0 })
.toBuffer();
const { size, ...metadata } = await sharp(data).metadata();
void size;
assert.deepStrictEqual(metadata, {
autoOrient: {
height: 24,
width: 32
},
channels: 3,
compression: 'av1',
depth: 'uchar',
format: 'heif',
hasAlpha: false,
hasProfile: false,
height: 24,
isProgressive: false,
isPalette: false,
bitsPerSample: 8,
pagePrimary: 0,
pages: 1,
space: 'srgb',
width: 32
});
});
it('can passthrough AVIF', async () => { it('can passthrough AVIF', async () => {
const data = await sharp(inputAvif) const data = await sharp(inputAvif)
.resize(32) .resize(32)