mirror of
https://github.com/lovell/sharp.git
synced 2026-02-04 05:36:18 +01:00
Compare commits
6 Commits
v0.35.0-rc
...
17d4a684df
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17d4a684df | ||
|
|
ed6b7384d0 | ||
|
|
ef77388a73 | ||
|
|
66764b359b | ||
|
|
8561f0da1d | ||
|
|
0468c1be9f |
@@ -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>"'inch'"</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>"'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.tune] | <code>string</code> | <code>"'iq'"</code> | tune output for a quality metric, one of 'iq' (default), 'ssim' or 'psnr' |
|
||||
| [options.tune] | <code>string</code> | <code>"'iq'"</code> | tune output for a quality metric, one of 'iq' (default), 'ssim' (default when lossless) or 'psnr' |
|
||||
|
||||
**Example**
|
||||
```js
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
4
lib/index.d.ts
vendored
@@ -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') */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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': [
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ namespace sharp {
|
||||
rawPremultiplied(false),
|
||||
rawPageHeight(0),
|
||||
pages(1),
|
||||
page(0),
|
||||
page(-1),
|
||||
createChannels(0),
|
||||
createWidth(0),
|
||||
createHeight(0),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
2
test/fixtures/index.js
vendored
2
test/fixtures/index.js
vendored
@@ -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
BIN
test/fixtures/pitm.avif
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
@@ -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 },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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', () =>
|
||||
|
||||
Reference in New Issue
Block a user