diff --git a/docs/src/content/docs/api-output.md b/docs/src/content/docs/api-output.md index b6729c63..25ad70c6 100644 --- a/docs/src/content/docs/api-output.md +++ b/docs/src/content/docs/api-output.md @@ -717,7 +717,7 @@ instead of providing `xres` and `yres` in pixels/mm. | [options.xres] | number | 1.0 | horizontal resolution in pixels/mm | | [options.yres] | number | 1.0 | vertical resolution in pixels/mm | | [options.resolutionUnit] | string | "'inch'" | resolution unit options: inch, cm | -| [options.bitdepth] | number | 8 | reduce bitdepth to 1, 2 or 4 bit | +| [options.bitdepth] | number | 0 | reduce bitdepth to 1, 2 or 4 bit | | [options.miniswhite] | boolean | false | write 1-bit images as miniswhite | **Example** diff --git a/docs/src/content/docs/changelog/v0.35.0.md b/docs/src/content/docs/changelog/v0.35.0.md index 9f97d0e3..69d954e8 100644 --- a/docs/src/content/docs/changelog/v0.35.0.md +++ b/docs/src/content/docs/changelog/v0.35.0.md @@ -20,6 +20,8 @@ slug: changelog/v0.35.0 * Upgrade to libvips v8.18.0 for upstream bug fixes. +* Improve thread-safety of error (and warning) messages. + * Deprecate Windows 32-bit (win32-ia32) prebuilt binaries. * Add AVIF/HEIF `tune` option for control over quality metrics. diff --git a/lib/constructor.js b/lib/constructor.js index 1a2e55be..149d0b34 100644 --- a/lib/constructor.js +++ b/lib/constructor.js @@ -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, diff --git a/lib/output.js b/lib/output.js index d0e1d3d5..b0213f5e 100644 --- a/lib/output.js +++ b/lib/output.js @@ -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 diff --git a/src/common.cc b/src/common.cc index bc608348..4b1f1c46 100644 --- a/src/common.cc +++ b/src/common.cc @@ -510,11 +510,11 @@ namespace sharp { 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 { @@ -585,10 +585,10 @@ namespace sharp { imageType = DetermineImageType(descriptor->file.data()); if (imageType == ImageType::MISSING) { if (descriptor->file.find("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 { @@ -600,11 +600,11 @@ namespace sharp { 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"); } } } @@ -612,7 +612,7 @@ namespace sharp { // Limit input images to a given number of pixels, where pixels = width * height if (descriptor->limitInputPixels > 0 && static_cast(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); } @@ -788,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"); } } } diff --git a/src/metadata.cc b/src/metadata.cc index 01cbd51f..65513474 100644 --- a/src/metadata.cc +++ b/src/metadata.cc @@ -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) { @@ -152,7 +152,12 @@ class MetadataWorker : public Napi::AsyncWorker { // PNG comments vips_image_map(image.get_image(), readPNGComment, &baton->comments); } - + // Handle warnings + std::string warning = sharp::VipsWarningPop(); + while (!warning.empty()) { + baton->warnings.push_back(warning); + warning = sharp::VipsWarningPop(); + } // Clean up vips_error_clear(); vips_thread_shutdown(); @@ -162,13 +167,9 @@ class MetadataWorker : public Napi::AsyncWorker { Napi::Env env = Env(); Napi::HandleScope scope(env); - // Handle warnings - std::string warning = sharp::VipsWarningPop(); - while (!warning.empty()) { + for (auto& warning : baton->warnings) { debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) }); - warning = sharp::VipsWarningPop(); } - if (baton->err.empty()) { Napi::Object info = Napi::Object::New(env); info.Set("format", baton->format); diff --git a/src/metadata.h b/src/metadata.h index 7d309bf1..3c0b127b 100644 --- a/src/metadata.h +++ b/src/metadata.h @@ -57,6 +57,7 @@ struct MetadataBaton { size_t gainMapLength; MetadataComments comments; std::string err; + std::vector warnings; MetadataBaton(): input(nullptr), diff --git a/src/operations.cc b/src/operations.cc index 50b4bed2..d4701729 100644 --- a/src/operations.cc +++ b/src/operations.cc @@ -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 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 const a, std::vector const b) { size_t const bands = static_cast(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)) { diff --git a/src/pipeline.cc b/src/pipeline.cc index 2de6d43e..53a7814b 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -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(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,19 +1273,28 @@ 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); } else { if (baton->input->failOn == VIPS_FAIL_ON_WARNING) { (baton->err).append("Warning treated as error due to failOn setting"); - baton->errUseWarning = true; } else { (baton->err).append("Unknown error"); } } } + // Handle warnings + std::string warning = sharp::VipsWarningPop(); + while (!warning.empty()) { + if (baton->input->failOn == VIPS_FAIL_ON_WARNING) { + (baton->err).append("\n").append(warning); + } else { + (baton->warnings).push_back(warning); + } + warning = sharp::VipsWarningPop(); + } // Clean up libvips' per-request data and threads vips_error_clear(); vips_thread_shutdown(); @@ -1296,17 +1304,9 @@ class PipelineWorker : public Napi::AsyncWorker { Napi::Env env = Env(); Napi::HandleScope scope(env); - // Handle warnings - std::string warning = sharp::VipsWarningPop(); - while (!warning.empty()) { - if (baton->errUseWarning) { - (baton->err).append("\n").append(warning); - } else { - debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) }); - } - warning = sharp::VipsWarningPop(); + for (auto &warning : baton->warnings) { + debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) }); } - if (baton->err.empty()) { int width = baton->width; int height = baton->height; @@ -1407,7 +1407,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"); } } diff --git a/src/pipeline.h b/src/pipeline.h index a007f7a8..6a2e2207 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -204,6 +204,7 @@ struct PipelineBaton { bool jxlLossless; VipsBandFormat rawDepth; std::string err; + std::vector warnings; bool errUseWarning; int keepMetadata; int withMetadataOrientation; @@ -365,7 +366,7 @@ struct PipelineBaton { tiffBigtiff(false), tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL), tiffPyramid(false), - tiffBitdepth(8), + tiffBitdepth(0), tiffMiniswhite(false), tiffTile(false), tiffTileHeight(256), diff --git a/src/stats.cc b/src/stats.cc index b1fd27a7..b4e57e2a 100644 --- a/src/stats.cc +++ b/src/stats.cc @@ -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,11 +92,16 @@ 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()); } } - + // Handle warnings + std::string warning = sharp::VipsWarningPop(); + while (!warning.empty()) { + baton->warnings.push_back(warning); + warning = sharp::VipsWarningPop(); + } // Clean up vips_error_clear(); vips_thread_shutdown(); @@ -106,13 +111,9 @@ class StatsWorker : public Napi::AsyncWorker { Napi::Env env = Env(); Napi::HandleScope scope(env); - // Handle warnings - std::string warning = sharp::VipsWarningPop(); - while (!warning.empty()) { + for (auto& warning : baton->warnings) { 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); diff --git a/src/stats.h b/src/stats.h index 88e13c60..fce82de9 100644 --- a/src/stats.h +++ b/src/stats.h @@ -45,6 +45,7 @@ struct StatsBaton { int dominantBlue; std::string err; + std::vector warnings; StatsBaton(): input(nullptr), diff --git a/src/utilities.cc b/src/utilities.cc index 03164b5c..9fcdb1cb 100644 --- a/src/utilities.cc +++ b/src/utilities.cc @@ -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()); } diff --git a/test/unit/metadata.js b/test/unit/metadata.js index 300380ed..3c3e25bb 100644 --- a/test/unit/metadata.js +++ b/test/unit/metadata.js @@ -642,7 +642,7 @@ describe('Image metadata', () => { }); it('keep existing ICC profile', async () => { - const data = await sharp(fixtures.inputJpgWithExif) + const data = await sharp(fixtures.inputJpgWithExif, { failOn: 'error' }) .keepIccProfile() .toBuffer(); @@ -675,7 +675,7 @@ describe('Image metadata', () => { }); it('keep existing ICC profile, avoid colour transform', async () => { - const [r, g, b] = await sharp(fixtures.inputPngWithProPhotoProfile) + const [r, g, b] = await sharp(fixtures.inputPngWithProPhotoProfile, { failOn: 'error' }) .keepIccProfile() .raw() .toBuffer(); @@ -721,7 +721,7 @@ describe('Image metadata', () => { }); it('transform to invalid ICC profile emits warning', async () => { - const img = sharp({ create }) + const img = sharp({ create, failOn: 'error' }) .png() .withIccProfile(fixtures.path('invalid-illuminant.icc')); diff --git a/test/unit/tiff.js b/test/unit/tiff.js index be5e5535..21c46ace 100644 --- a/test/unit/tiff.js +++ b/test/unit/tiff.js @@ -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', () =>