Compare commits

...

6 Commits

Author SHA1 Message Date
Lovell Fuller
17d4a684df WIP: Switch from custom VError to standard runtime_error 2026-01-23 22:26:15 +00:00
Lovell Fuller
ed6b7384d0 Ensure TIFF output bitdepth option is limited to 1, 2 or 4 2026-01-23 21:29:40 +00:00
Lovell Fuller
ef77388a73 Force MSVC to use exception handling
As of 8.18.0, libvips C++ wrapper retrieves error messages at
exception construction time rather than lazily when accessed.

On Windows this led to error messages being referenced rather
than copied, leading to access beyond their lifetime and possible
corruption.
2026-01-22 12:52:48 +00:00
Lovell Fuller
66764b359b Remove unused option parameter added in 8561f0d 2026-01-20 21:10:56 +00:00
Lovell Fuller
8561f0da1d Ensure HEIF primary item is used as default page #4487 2026-01-18 20:24:34 +00:00
Lovell Fuller
0468c1be9f Encoding lossless AVIF is mutually exclusive with iq tuning 2026-01-08 12:43:53 +00:00
18 changed files with 188 additions and 81 deletions

View File

@@ -717,7 +717,7 @@ instead of providing `xres` and `yres` in pixels/mm.
| [options.xres] | <code>number</code> | <code>1.0</code> | horizontal resolution in pixels/mm |
| [options.yres] | <code>number</code> | <code>1.0</code> | vertical resolution in pixels/mm |
| [options.resolutionUnit] | <code>string</code> | <code>&quot;&#x27;inch&#x27;&quot;</code> | resolution unit options: inch, cm |
| [options.bitdepth] | <code>number</code> | <code>8</code> | reduce bitdepth to 1, 2 or 4 bit |
| [options.bitdepth] | <code>number</code> | <code>0</code> | reduce bitdepth to 1, 2 or 4 bit |
| [options.miniswhite] | <code>boolean</code> | <code>false</code> | write 1-bit images as miniswhite |
**Example**
@@ -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.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.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**
```js

View File

@@ -20,6 +20,8 @@ slug: changelog/v0.35.0
* Upgrade to libvips v8.18.0 for upstream bug fixes.
* Ensure TIFF output `bitdepth` option is limited to 1, 2 or 4.
* Deprecate Windows 32-bit (win32-ia32) prebuilt binaries.
* Add AVIF/HEIF `tune` option for control over quality metrics.
@@ -38,4 +40,7 @@ slug: changelog/v0.35.0
[#4480](https://github.com/lovell/sharp/issues/4480)
[@eddienubes](https://github.com/eddienubes)
* Ensure HEIF primary item is used as default page/frame.
[#4487](https://github.com/lovell/sharp/issues/4487)
* Add WebP `exact` option for control over transparent pixel colour values.

View File

@@ -366,7 +366,7 @@ const Sharp = function (input, options) {
tiffPredictor: 'horizontal',
tiffPyramid: false,
tiffMiniswhite: false,
tiffBitdepth: 8,
tiffBitdepth: 0,
tiffTile: false,
tiffTileHeight: 256,
tiffTileWidth: 256,

4
lib/index.d.ts vendored
View File

@@ -1479,8 +1479,8 @@ declare namespace sharp {
xres?: number | undefined;
/** Vertical resolution in pixels/mm (optional, default 1.0) */
yres?: number | undefined;
/** Reduce bitdepth to 1, 2 or 4 bit (optional, default 8) */
bitdepth?: 1 | 2 | 4 | 8 | undefined;
/** Reduce bitdepth to 1, 2 or 4 bit (optional) */
bitdepth?: 1 | 2 | 4 | undefined;
/** Write 1-bit images as miniswhite (optional, default false) */
miniswhite?: boolean | undefined;
/** Resolution unit options: inch, cm (optional, default 'inch') */

View File

@@ -1055,7 +1055,7 @@ function trySetAnimationOptions (source, target) {
* @param {number} [options.xres=1.0] - horizontal resolution in pixels/mm
* @param {number} [options.yres=1.0] - vertical resolution in pixels/mm
* @param {string} [options.resolutionUnit='inch'] - resolution unit options: inch, cm
* @param {number} [options.bitdepth=8] - reduce bitdepth to 1, 2 or 4 bit
* @param {number} [options.bitdepth=0] - reduce bitdepth to 1, 2 or 4 bit
* @param {boolean} [options.miniswhite=false] - write 1-bit images as miniswhite
* @returns {Sharp}
* @throws {Error} Invalid options
@@ -1070,10 +1070,10 @@ function tiff (options) {
}
}
if (is.defined(options.bitdepth)) {
if (is.integer(options.bitdepth) && is.inArray(options.bitdepth, [1, 2, 4, 8])) {
if (is.integer(options.bitdepth) && is.inArray(options.bitdepth, [1, 2, 4])) {
this.options.tiffBitdepth = options.bitdepth;
} else {
throw is.invalidParameterError('bitdepth', '1, 2, 4 or 8', options.bitdepth);
throw is.invalidParameterError('bitdepth', '1, 2 or 4', options.bitdepth);
}
}
// tiling
@@ -1175,7 +1175,7 @@ function tiff (options) {
* @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 {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}
* @throws {Error} Invalid options
*/
@@ -1255,7 +1255,11 @@ function heif (options) {
}
if (is.defined(options.tune)) {
if (is.string(options.tune) && is.inArray(options.tune, ['iq', 'ssim', 'psnr'])) {
this.options.heifTune = options.tune;
if (this.options.heifLossless && options.tune === 'iq') {
this.options.heifTune = 'ssim';
} else {
this.options.heifTune = options.tune;
}
} else {
throw is.invalidParameterError('tune', 'one of: psnr, ssim, iq', options.tune);
}

View File

@@ -21,6 +21,7 @@
'defines': [
'_VIPS_PUBLIC=__declspec(dllexport)',
'_ALLOW_KEYWORD_MACROS',
'_HAS_EXCEPTIONS=1',
'G_DISABLE_ASSERT',
'G_DISABLE_CAST_CHECKS',
'G_DISABLE_CHECKS'
@@ -148,7 +149,8 @@
['OS == "win"', {
'defines': [
'_ALLOW_KEYWORD_MACROS',
'_FILE_OFFSET_BITS=64'
'_FILE_OFFSET_BITS=64',
'_HAS_EXCEPTIONS=1'
],
'link_settings': {
'libraries': [

View File

@@ -426,7 +426,7 @@ namespace sharp {
}
if (ImageTypeSupportsPage(imageType)) {
option->set("n", descriptor->pages);
option->set("page", descriptor->page);
option->set("page", std::max(0, descriptor->page));
}
switch (imageType) {
case ImageType::SVG:
@@ -456,6 +456,22 @@ namespace sharp {
return option;
}
/*
Should HEIF image be re-opened using the primary item?
*/
static bool HeifPrimaryPageReopen(VImage image, InputDescriptor *descriptor) {
if (image.get_typeof(VIPS_META_N_PAGES) == G_TYPE_INT && image.get_typeof("heif-primary") == G_TYPE_INT) {
if (image.get_int(VIPS_META_N_PAGES) > 1 && descriptor->pages == 1 && descriptor->page == -1) {
int const pagePrimary = image.get_int("heif-primary");
if (pagePrimary != 0) {
descriptor->page = pagePrimary;
return true;
}
}
}
return false;
}
/*
Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
*/
@@ -490,12 +506,15 @@ namespace sharp {
image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
image = SetDensity(image, descriptor->density);
} else if (imageType == ImageType::HEIF && HeifPrimaryPageReopen(image, descriptor)) {
option = GetOptionsForImageType(imageType, descriptor);
image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
}
} catch (vips::VError const &err) {
throw vips::VError(std::string("Input buffer has corrupt header: ") + err.what());
} catch (std::runtime_error const &err) {
throw std::runtime_error(std::string("Input buffer has corrupt header: ") + err.what());
}
} else {
throw vips::VError("Input buffer contains unsupported image format");
throw std::runtime_error("Input buffer contains unsupported image format");
}
}
} else {
@@ -566,10 +585,10 @@ namespace sharp {
imageType = DetermineImageType(descriptor->file.data());
if (imageType == ImageType::MISSING) {
if (descriptor->file.find("<svg") != std::string::npos) {
throw vips::VError("Input file is missing, did you mean "
throw std::runtime_error("Input file is missing, did you mean "
"sharp(Buffer.from('" + descriptor->file.substr(0, 8) + "...')?");
}
throw vips::VError("Input file is missing: " + descriptor->file);
throw std::runtime_error("Input file is missing: " + descriptor->file);
}
if (imageType != ImageType::UNKNOWN) {
try {
@@ -577,12 +596,15 @@ namespace sharp {
image = VImage::new_from_file(descriptor->file.data(), option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
image = SetDensity(image, descriptor->density);
} else if (imageType == ImageType::HEIF && HeifPrimaryPageReopen(image, descriptor)) {
option = GetOptionsForImageType(imageType, descriptor);
image = VImage::new_from_file(descriptor->file.data(), option);
}
} catch (vips::VError const &err) {
throw vips::VError(std::string("Input file has corrupt header: ") + err.what());
} catch (std::runtime_error const &err) {
throw std::runtime_error(std::string("Input file has corrupt header: ") + err.what());
}
} else {
throw vips::VError("Input file contains unsupported image format");
throw std::runtime_error("Input file contains unsupported image format");
}
}
}
@@ -590,7 +612,7 @@ namespace sharp {
// Limit input images to a given number of pixels, where pixels = width * height
if (descriptor->limitInputPixels > 0 &&
static_cast<uint64_t>(image.width()) * image.height() > descriptor->limitInputPixels) {
throw vips::VError("Input image exceeds pixel limit");
throw std::runtime_error("Input image exceeds pixel limit");
}
return std::make_tuple(image, imageType);
}
@@ -766,19 +788,19 @@ namespace sharp {
: image.height();
if (imageType == ImageType::JPEG) {
if (image.width() > 65535 || height > 65535) {
throw vips::VError("Processed image is too large for the JPEG format");
throw std::runtime_error("Processed image is too large for the JPEG format");
}
} else if (imageType == ImageType::WEBP) {
if (image.width() > 16383 || height > 16383) {
throw vips::VError("Processed image is too large for the WebP format");
throw std::runtime_error("Processed image is too large for the WebP format");
}
} else if (imageType == ImageType::GIF) {
if (image.width() > 65535 || height > 65535) {
throw vips::VError("Processed image is too large for the GIF format");
throw std::runtime_error("Processed image is too large for the GIF format");
}
} else if (imageType == ImageType::HEIF) {
if (image.width() > 16384 || height > 16384) {
throw vips::VError("Processed image is too large for the HEIF format");
throw std::runtime_error("Processed image is too large for the HEIF format");
}
}
}

View File

@@ -105,7 +105,7 @@ namespace sharp {
rawPremultiplied(false),
rawPageHeight(0),
pages(1),
page(0),
page(-1),
createChannels(0),
createWidth(0),
createHeight(0),

View File

@@ -31,7 +31,7 @@ class MetadataWorker : public Napi::AsyncWorker {
sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
try {
std::tie(image, imageType) = OpenInput(baton->input);
} catch (vips::VError const &err) {
} catch (std::runtime_error const &err) {
(baton->err).append(err.what());
}
if (imageType != sharp::ImageType::UNKNOWN) {

View File

@@ -14,7 +14,6 @@
#include "./operations.h"
using vips::VImage;
using vips::VError;
namespace sharp {
/*
@@ -287,7 +286,7 @@ namespace sharp {
*/
VImage Trim(VImage image, std::vector<double> background, double threshold, bool const lineArt, int const margin) {
if (image.width() < 3 && image.height() < 3) {
throw VError("Image to trim must be at least 3x3 pixels");
throw std::runtime_error("Image to trim must be at least 3x3 pixels");
}
if (background.size() == 0) {
// Top-left pixel provides the default background colour if none is given
@@ -361,7 +360,7 @@ namespace sharp {
VImage Linear(VImage image, std::vector<double> const a, std::vector<double> const b) {
size_t const bands = static_cast<size_t>(image.bands());
if (a.size() > bands) {
throw VError("Band expansion using linear is unsupported");
throw std::runtime_error("Band expansion using linear is unsupported");
}
bool const uchar = !Is16Bit(image.interpretation());
if (image.has_alpha() && a.size() != bands && (a.size() == 1 || a.size() == bands - 1 || bands - 1 == 1)) {

View File

@@ -84,7 +84,7 @@ class PipelineWorker : public Napi::AsyncWorker {
if (nPages == -1) {
// Resolve the number of pages if we need to render until the end of the document
nPages = image.get_typeof(VIPS_META_N_PAGES) != 0
? image.get_int(VIPS_META_N_PAGES) - baton->input->page
? image.get_int(VIPS_META_N_PAGES) - std::max(0, baton->input->page)
: 1;
}
@@ -274,7 +274,7 @@ class PipelineWorker : public Napi::AsyncWorker {
}
sharp::SetDensity(image, baton->input->density);
if (image.width() > 32767 || image.height() > 32767) {
throw vips::VError("Input SVG image will exceed 32767x32767 pixel limit when scaled");
throw std::runtime_error("Input SVG image will exceed 32767x32767 pixel limit when scaled");
}
} else if (inputImageType == sharp::ImageType::PDF) {
if (baton->input->buffer != nullptr) {
@@ -290,7 +290,7 @@ class PipelineWorker : public Napi::AsyncWorker {
}
} else {
if (inputImageType == sharp::ImageType::SVG && (image.width() > 32767 || image.height() > 32767)) {
throw vips::VError("Input SVG image exceeds 32767x32767 pixel limit");
throw std::runtime_error("Input SVG image exceeds 32767x32767 pixel limit");
}
}
if (baton->input->autoOrient) {
@@ -675,7 +675,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// Verify within current dimensions
if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) {
throw vips::VError("Image to composite must have same dimensions or smaller");
throw std::runtime_error("Image to composite must have same dimensions or smaller");
}
// Check if overlay is tiled
if (composite->tile) {
@@ -1086,20 +1086,19 @@ class PipelineWorker : public Napi::AsyncWorker {
// Get raw image data
baton->bufferOut = static_cast<char*>(image.write_to_memory(&baton->bufferOutLength));
if (baton->bufferOut == nullptr) {
(baton->err).append("Could not allocate enough memory for raw output");
return Error();
throw std::runtime_error("Could not allocate enough memory for raw output");
}
baton->formatOut = "raw";
} else {
// Unsupported output format
(baton->err).append("Unsupported output format ");
auto unsupported = std::string("Unsupported output format ");
if (baton->formatOut == "input") {
(baton->err).append("when trying to match input format of ");
(baton->err).append(ImageTypeId(inputImageType));
unsupported.append("when trying to match input format of ");
unsupported.append(ImageTypeId(inputImageType));
} else {
(baton->err).append(baton->formatOut);
unsupported.append(baton->formatOut);
}
return Error();
throw std::runtime_error(unsupported);
}
} else {
// File output
@@ -1274,7 +1273,7 @@ class PipelineWorker : public Napi::AsyncWorker {
return Error();
}
}
} catch (vips::VError const &err) {
} catch (std::runtime_error const &err) {
char const *what = err.what();
if (what && what[0]) {
(baton->err).append(what);
@@ -1306,7 +1305,6 @@ class PipelineWorker : public Napi::AsyncWorker {
}
warning = sharp::VipsWarningPop();
}
if (baton->err.empty()) {
int width = baton->width;
int height = baton->height;
@@ -1407,7 +1405,7 @@ class PipelineWorker : public Napi::AsyncWorker {
void MultiPageUnsupported(int const pages, std::string op) {
if (pages > 1) {
throw vips::VError(op + " is not supported for multi-page images");
throw std::runtime_error(op + " is not supported for multi-page images");
}
}

View File

@@ -365,7 +365,7 @@ struct PipelineBaton {
tiffBigtiff(false),
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL),
tiffPyramid(false),
tiffBitdepth(8),
tiffBitdepth(0),
tiffMiniswhite(false),
tiffTile(false),
tiffTileHeight(256),

View File

@@ -39,7 +39,7 @@ class StatsWorker : public Napi::AsyncWorker {
sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
try {
std::tie(image, imageType) = OpenInput(baton->input);
} catch (vips::VError const &err) {
} catch (std::runtime_error const &err) {
(baton->err).append(err.what());
}
if (imageType != sharp::ImageType::UNKNOWN) {
@@ -92,7 +92,7 @@ class StatsWorker : public Napi::AsyncWorker {
baton->dominantRed = dx * 16 + 8;
baton->dominantGreen = dy * 16 + 8;
baton->dominantBlue = dz * 16 + 8;
} catch (vips::VError const &err) {
} catch (std::runtime_error const &err) {
(baton->err).append(err.what());
}
}
@@ -112,7 +112,6 @@ class StatsWorker : public Napi::AsyncWorker {
debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) });
warning = sharp::VipsWarningPop();
}
if (baton->err.empty()) {
// Stats Object
Napi::Object info = Napi::Object::New(env);

View File

@@ -244,7 +244,7 @@ Napi::Value _maxColourDistance(const Napi::CallbackInfo& info) {
}
// Calculate colour distance
maxColourDistance = image1.dE00(image2).max();
} catch (vips::VError const &err) {
} catch (std::runtime_error const &err) {
throw Napi::Error::New(env, err.what());
}

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 } = 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,20 +73,47 @@ describe('AVIF', () => {
pagePrimary: 0,
pages: 1,
space: 'srgb',
width: 32
width: 32,
});
});
it('can passthrough AVIF', async () => {
const data = await sharp(inputAvif)
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 () => {
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',
@@ -98,7 +128,7 @@ describe('AVIF', () => {
pagePrimary: 0,
pages: 1,
space: 'srgb',
width: 32
width: 32,
});
});
@@ -112,7 +142,7 @@ describe('AVIF', () => {
assert.deepStrictEqual(metadata, {
autoOrient: {
height: 300,
width: 10
width: 10,
},
channels: 4,
compression: 'av1',
@@ -127,7 +157,7 @@ describe('AVIF', () => {
pagePrimary: 0,
pages: 1,
space: 'srgb',
width: 10
width: 10,
});
});
@@ -142,7 +172,7 @@ describe('AVIF', () => {
assert.deepStrictEqual(metadata, {
autoOrient: {
height: 26,
width: 32
width: 32,
},
channels: 3,
compression: 'av1',
@@ -157,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)
@@ -192,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 },
});
});
});

View File

@@ -122,7 +122,6 @@ describe('TIFF', () => {
sharp(fixtures.inputTiff8BitDepth)
.toColourspace('b-w') // can only squash 1 band uchar images
.tiff({
bitdepth: 8,
compression: 'none',
predictor: 'none'
})
@@ -154,7 +153,7 @@ describe('TIFF', () => {
it('Invalid TIFF bitdepth value throws error', () => {
assert.throws(() => {
sharp().tiff({ bitdepth: 3 });
}, /Error: Expected 1, 2, 4 or 8 for bitdepth but received 3 of type number/);
}, /Error: Expected 1, 2 or 4 for bitdepth but received 3 of type number/);
});
it('TIFF setting xres and yres on file', () =>