Ensure HEIF primary item is used as default page #4487

This commit is contained in:
Lovell Fuller
2026-01-18 20:24:34 +00:00
parent 0468c1be9f
commit 8561f0da1d
7 changed files with 111 additions and 36 deletions

View File

@@ -127,7 +127,7 @@ module.exports = {
inputSvgSmallViewBox: getPath('circle.svg'),
inputSvgWithEmbeddedImages: getPath('struct-image-04-t.svg'), // https://dev.w3.org/SVG/profiles/1.2T/test/svg/struct-image-04-t.svg
inputAvif: getPath('sdr_cosmos12920_cicp1-13-6_yuv444_full_qp10.avif'), // CC by-nc-nd https://github.com/AOMediaCodec/av1-avif/tree/master/testFiles/Netflix
inputAvifWithPitmBox: getPath('pitm.avif'), // https://github.com/lovell/sharp/issues/4487
inputJPGBig: getPath('flowers.jpeg'),
inputPngDotAndLines: getPath('dot-and-lines.png'),

BIN
test/fixtures/pitm.avif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

View File

@@ -7,7 +7,13 @@ const { describe, it } = require('node:test');
const assert = require('node:assert');
const sharp = require('../../');
const { inputAvif, inputJpg, inputGifAnimated, inputPng } = require('../fixtures');
const {
inputAvif,
inputAvifWithPitmBox,
inputJpg,
inputGifAnimated,
inputPng,
} = require('../fixtures');
describe('AVIF', () => {
it('called without options does not throw an error', () => {
@@ -17,16 +23,13 @@ describe('AVIF', () => {
});
it('can convert AVIF to JPEG', async () => {
const data = await sharp(inputAvif)
.resize(32)
.jpeg()
.toBuffer();
const data = await sharp(inputAvif).resize(32).jpeg().toBuffer();
const { size, ...metadata } = await sharp(data).metadata();
void size;
assert.deepStrictEqual(metadata, {
autoOrient: {
height: 13,
width: 32
width: 32,
},
channels: 3,
chromaSubsampling: '4:2:0',
@@ -41,7 +44,7 @@ describe('AVIF', () => {
isProgressive: false,
isPalette: false,
space: 'srgb',
width: 32
width: 32,
});
});
@@ -55,7 +58,7 @@ describe('AVIF', () => {
assert.deepStrictEqual(metadata, {
autoOrient: {
height: 26,
width: 32
width: 32,
},
channels: 3,
compression: 'av1',
@@ -70,7 +73,7 @@ describe('AVIF', () => {
pagePrimary: 0,
pages: 1,
space: 'srgb',
width: 32
width: 32,
});
});
@@ -84,7 +87,7 @@ describe('AVIF', () => {
assert.deepStrictEqual(metadata, {
autoOrient: {
height: 24,
width: 32
width: 32,
},
channels: 3,
compression: 'av1',
@@ -99,20 +102,18 @@ describe('AVIF', () => {
pagePrimary: 0,
pages: 1,
space: 'srgb',
width: 32
width: 32,
});
});
it('can passthrough AVIF', async () => {
const data = await sharp(inputAvif)
.resize(32)
.toBuffer();
const data = await sharp(inputAvif).resize(32).toBuffer();
const { size, ...metadata } = await sharp(data).metadata();
void size;
assert.deepStrictEqual(metadata, {
autoOrient: {
height: 13,
width: 32
width: 32,
},
channels: 3,
compression: 'av1',
@@ -127,7 +128,7 @@ describe('AVIF', () => {
pagePrimary: 0,
pages: 1,
space: 'srgb',
width: 32
width: 32,
});
});
@@ -141,7 +142,7 @@ describe('AVIF', () => {
assert.deepStrictEqual(metadata, {
autoOrient: {
height: 300,
width: 10
width: 10,
},
channels: 4,
compression: 'av1',
@@ -156,7 +157,7 @@ describe('AVIF', () => {
pagePrimary: 0,
pages: 1,
space: 'srgb',
width: 10
width: 10,
});
});
@@ -171,7 +172,7 @@ describe('AVIF', () => {
assert.deepStrictEqual(metadata, {
autoOrient: {
height: 26,
width: 32
width: 32,
},
channels: 3,
compression: 'av1',
@@ -186,30 +187,37 @@ describe('AVIF', () => {
pagePrimary: 0,
pages: 1,
space: 'srgb',
width: 32
width: 32,
});
});
it('Invalid width - too large', async () =>
assert.rejects(
() => sharp({ create: { width: 16385, height: 16, channels: 3, background: 'red' } }).avif().toBuffer(),
/Processed image is too large for the HEIF format/
)
);
() =>
sharp({
create: { width: 16385, height: 16, channels: 3, background: 'red' },
})
.avif()
.toBuffer(),
/Processed image is too large for the HEIF format/,
));
it('Invalid height - too large', async () =>
assert.rejects(
() => sharp({ create: { width: 16, height: 16385, channels: 3, background: 'red' } }).avif().toBuffer(),
/Processed image is too large for the HEIF format/
)
);
() =>
sharp({
create: { width: 16, height: 16385, channels: 3, background: 'red' },
})
.avif()
.toBuffer(),
/Processed image is too large for the HEIF format/,
));
it('Invalid bitdepth value throws error', () =>
assert.throws(
() => sharp().avif({ bitdepth: 11 }),
/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)
@@ -221,5 +229,47 @@ describe('AVIF', () => {
.avif({ tune: 'iq', effort: 0 })
.toBuffer();
assert(ssim.length < iq.length);
})
});
it('AVIF with non-zero primary item uses it as default page', async () => {
const { exif, ...metadata } = await sharp(inputAvifWithPitmBox).metadata();
void exif;
assert.deepStrictEqual(metadata, {
format: 'heif',
width: 4096,
height: 800,
space: 'srgb',
channels: 3,
depth: 'uchar',
isProgressive: false,
isPalette: false,
bitsPerSample: 8,
pages: 5,
pagePrimary: 4,
compression: 'av1',
resolutionUnit: 'cm',
hasProfile: false,
hasAlpha: false,
autoOrient: { width: 4096, height: 800 },
});
const data = await sharp(inputAvifWithPitmBox)
.png({ compressionLevel: 0 })
.toBuffer();
const { size, ...pngMetadata } = await sharp(data).metadata();
assert.deepStrictEqual(pngMetadata, {
format: 'png',
width: 4096,
height: 800,
space: 'srgb',
channels: 3,
depth: 'uchar',
isProgressive: false,
isPalette: false,
bitsPerSample: 8,
hasProfile: false,
hasAlpha: false,
autoOrient: { width: 4096, height: 800 },
});
});
});