/
opt
/
canhelp
/
node_modules
/
sharp
/
src
/
/opt/canhelp/node_modules/sharp/src
mkdir
upload
Name
Size
Mode
Actions
binding.gyp
10597
0644
edit
dl
rm
common.cc
39181
0644
edit
dl
rm
common.h
10639
0644
edit
dl
rm
metadata.cc
13221
0644
edit
dl
rm
metadata.h
1768
0644
edit
dl
rm
operations.cc
16584
0644
edit
dl
rm
operations.h
3607
0644
edit
dl
rm
pipeline.cc
84430
0644
edit
dl
rm
pipeline.h
9828
0644
edit
dl
rm
sharp.cc
1529
0644
edit
dl
rm
stats.cc
6683
0644
edit
dl
rm
stats.h
1267
0644
edit
dl
rm
utilities.cc
9454
0644
edit
dl
rm
utilities.h
741
0644
edit
dl
rm
Edit:
/opt/canhelp/node_modules/sharp/src/pipeline.cc
(84430B)
/*! Copyright 2013 Lovell Fuller and others. SPDX-License-Identifier: Apache-2.0 */ #include <algorithm> #include <cmath> #include <filesystem> // NOLINT(build/c++17) #include <map> #include <memory> #include <numeric> #include <string> #include <tuple> #include <utility> #include <vector> #include <sys/types.h> #include <sys/stat.h> #include <vips/vips8> #include <napi.h> #include "./common.h" #include "./operations.h" #include "./pipeline.h" class PipelineWorker : public Napi::AsyncWorker { public: PipelineWorker(Napi::Function callback, PipelineBaton *baton, Napi::Function debuglog, Napi::Function queueListener) : Napi::AsyncWorker(callback), baton(baton), debuglog(Napi::Persistent(debuglog)), queueListener(Napi::Persistent(queueListener)) {} ~PipelineWorker() {} // libuv worker void Execute() { // Decrement queued task counter sharp::counterQueue--; // Increment processing task counter sharp::counterProcess++; try { // Open input vips::VImage image; sharp::ImageType inputImageType; if (baton->join.empty()) { std::tie(image, inputImageType) = sharp::OpenInput(baton->input); } else { std::vector<VImage> images; bool hasAlpha = false; for (auto &join : baton->join) { std::tie(image, inputImageType) = sharp::OpenInput(join); image = sharp::EnsureColourspace(image, baton->colourspacePipeline); images.push_back(image); hasAlpha |= image.has_alpha(); } if (hasAlpha) { for (auto &image : images) { if (!image.has_alpha()) { image = sharp::EnsureAlpha(image, 1); } } } else { baton->input->joinBackground.pop_back(); } inputImageType = sharp::ImageType::PNG; image = VImage::arrayjoin(images, VImage::option() ->set("across", baton->input->joinAcross) ->set("shim", baton->input->joinShim) ->set("background", baton->input->joinBackground) ->set("halign", baton->input->joinHalign) ->set("valign", baton->input->joinValign)); if (baton->input->joinAnimated) { image = image.copy(); image.set(VIPS_META_N_PAGES, static_cast<int>(images.size())); image.set(VIPS_META_PAGE_HEIGHT, static_cast<int>(image.height() / images.size())); } } VipsAccess access = baton->input->access; image = sharp::EnsureColourspace(image, baton->colourspacePipeline); int nPages = baton->input->pages; 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 : 1; } // Get pre-resize page height int pageHeight = sharp::GetPageHeight(image); // Calculate angle of rotation VipsAngle rotation = VIPS_ANGLE_D0; VipsAngle autoRotation = VIPS_ANGLE_D0; bool autoFlop = false; if (baton->input->autoOrient) { // Rotate and flip image according to Exif orientation std::tie(autoRotation, autoFlop) = CalculateExifRotationAndFlop(sharp::ExifOrientation(image)); } rotation = CalculateAngleRotation(baton->angle); bool const shouldRotateBefore = baton->rotateBefore && (rotation != VIPS_ANGLE_D0 || baton->flip || baton->flop || baton->rotationAngle != 0.0); bool const shouldOrientBefore = (shouldRotateBefore || baton->orientBefore) && (autoRotation != VIPS_ANGLE_D0 || autoFlop); if (shouldOrientBefore) { image = sharp::StaySequential(image, autoRotation != VIPS_ANGLE_D0); if (autoRotation != VIPS_ANGLE_D0) { if (autoRotation != VIPS_ANGLE_D180) { MultiPageUnsupported(nPages, "Rotate"); } image = image.rot(autoRotation); autoRotation = VIPS_ANGLE_D0; } if (autoFlop) { image = image.flip(VIPS_DIRECTION_HORIZONTAL); autoFlop = false; } } if (shouldRotateBefore) { image = sharp::StaySequential(image, rotation != VIPS_ANGLE_D0 || baton->flip || baton->rotationAngle != 0.0); if (baton->flip) { image = image.flip(VIPS_DIRECTION_VERTICAL); baton->flip = false; } if (baton->flop) { image = image.flip(VIPS_DIRECTION_HORIZONTAL); baton->flop = false; } if (rotation != VIPS_ANGLE_D0) { if (rotation != VIPS_ANGLE_D180) { MultiPageUnsupported(nPages, "Rotate"); } image = image.rot(rotation); rotation = VIPS_ANGLE_D0; } if (baton->rotationAngle != 0.0) { MultiPageUnsupported(nPages, "Rotate"); std::vector<double> background; std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, false); image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background)).copy_memory(); baton->rotationAngle = 0.0; } } // Trim if (baton->trimThreshold >= 0.0) { MultiPageUnsupported(nPages, "Trim"); image = sharp::StaySequential(image); image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold, baton->trimLineArt); baton->trimOffsetLeft = image.xoffset(); baton->trimOffsetTop = image.yoffset(); } // Pre extraction if (baton->topOffsetPre != -1) { image = nPages > 1 ? sharp::CropMultiPage(image, baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre, nPages, &pageHeight) : image.extract_area(baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre); } // Get pre-resize image width and height int inputWidth = image.width(); int inputHeight = image.height(); // Is there just one page? Shrink to inputHeight instead if (nPages == 1) { pageHeight = inputHeight; } // Scaling calculations double hshrink; double vshrink; int targetResizeWidth = baton->width; int targetResizeHeight = baton->height; // When auto-rotating by 90 or 270 degrees, swap the target width and // height to ensure the behavior aligns with how it would have been if // the rotation had taken place *before* resizing. if (autoRotation == VIPS_ANGLE_D90 || autoRotation == VIPS_ANGLE_D270) { std::swap(targetResizeWidth, targetResizeHeight); } // Shrink to pageHeight, so we work for multi-page images std::tie(hshrink, vshrink) = sharp::ResolveShrink( inputWidth, pageHeight, targetResizeWidth, targetResizeHeight, baton->canvas, baton->withoutEnlargement, baton->withoutReduction); // The jpeg preload shrink. int jpegShrinkOnLoad = 1; // WebP, PDF, SVG scale double scale = 1.0; // Try to reload input using shrink-on-load for JPEG, WebP, SVG and PDF, when: // - the width or height parameters are specified; // - gamma correction doesn't need to be applied; // - trimming or pre-resize extract isn't required; // - input colourspace is not specified; bool const shouldPreShrink = (targetResizeWidth > 0 || targetResizeHeight > 0) && baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold < 0.0 && baton->colourspacePipeline == VIPS_INTERPRETATION_LAST && !(shouldOrientBefore || shouldRotateBefore); if (shouldPreShrink) { // The common part of the shrink: the bit by which both axes must be shrunk double shrink = std::min(hshrink, vshrink); if (inputImageType == sharp::ImageType::JPEG) { // Leave at least a factor of two for the final resize step, when fastShrinkOnLoad: false // for more consistent results and to avoid extra sharpness to the image int factor = baton->fastShrinkOnLoad ? 1 : 2; if (shrink >= 8 * factor) { jpegShrinkOnLoad = 8; } else if (shrink >= 4 * factor) { jpegShrinkOnLoad = 4; } else if (shrink >= 2 * factor) { jpegShrinkOnLoad = 2; } // Lower shrink-on-load for known libjpeg rounding errors if (jpegShrinkOnLoad > 1 && static_cast<int>(shrink) == jpegShrinkOnLoad) { jpegShrinkOnLoad /= 2; } } else if (inputImageType == sharp::ImageType::WEBP && baton->fastShrinkOnLoad && shrink > 1.0) { // Avoid upscaling via webp scale = 1.0 / shrink; } else if (inputImageType == sharp::ImageType::SVG || inputImageType == sharp::ImageType::PDF) { scale = 1.0 / shrink; } } // Reload input using shrink-on-load, it'll be an integer shrink // factor for jpegload*, a double scale factor for webpload*, // pdfload* and svgload* if (jpegShrinkOnLoad > 1) { vips::VOption *option = GetOptionsForImageType(inputImageType, baton->input)->set("shrink", jpegShrinkOnLoad); if (baton->input->buffer != nullptr) { // Reload JPEG buffer VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength); image = VImage::jpegload_buffer(blob, option); vips_area_unref(reinterpret_cast<VipsArea*>(blob)); } else { // Reload JPEG file image = VImage::jpegload(const_cast<char*>(baton->input->file.data()), option); } } else if (scale != 1.0) { vips::VOption *option = GetOptionsForImageType(inputImageType, baton->input)->set("scale", scale); if (inputImageType == sharp::ImageType::WEBP) { if (baton->input->buffer != nullptr) { // Reload WebP buffer VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength); image = VImage::webpload_buffer(blob, option); vips_area_unref(reinterpret_cast<VipsArea*>(blob)); } else { // Reload WebP file image = VImage::webpload(const_cast<char*>(baton->input->file.data()), option); } } else if (inputImageType == sharp::ImageType::SVG) { if (baton->input->buffer != nullptr) { // Reload SVG buffer VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength); image = VImage::svgload_buffer(blob, option); vips_area_unref(reinterpret_cast<VipsArea*>(blob)); } else { // Reload SVG file image = VImage::svgload(const_cast<char*>(baton->input->file.data()), option); } 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"); } } else if (inputImageType == sharp::ImageType::PDF) { if (baton->input->buffer != nullptr) { // Reload PDF buffer VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength); image = VImage::pdfload_buffer(blob, option); vips_area_unref(reinterpret_cast<VipsArea*>(blob)); } else { // Reload PDF file image = VImage::pdfload(const_cast<char*>(baton->input->file.data()), option); } sharp::SetDensity(image, baton->input->density); } } else { if (inputImageType == sharp::ImageType::SVG && (image.width() > 32767 || image.height() > 32767)) { throw vips::VError("Input SVG image exceeds 32767x32767 pixel limit"); } } if (baton->input->autoOrient) { image = sharp::RemoveExifOrientation(image); } // Any pre-shrinking may already have been done inputWidth = image.width(); inputHeight = image.height(); // After pre-shrink, but before the main shrink stage // Reuse the initial pageHeight if we didn't pre-shrink if (shouldPreShrink) { pageHeight = sharp::GetPageHeight(image); } // Shrink to pageHeight, so we work for multi-page images std::tie(hshrink, vshrink) = sharp::ResolveShrink( inputWidth, pageHeight, targetResizeWidth, targetResizeHeight, baton->canvas, baton->withoutEnlargement, baton->withoutReduction); int targetHeight = static_cast<int>(std::rint(static_cast<double>(pageHeight) / vshrink)); int targetPageHeight = targetHeight; // In toilet-roll mode, we must adjust vshrink so that we exactly hit // pageHeight or we'll have pixels straddling pixel boundaries if (inputHeight > pageHeight) { targetHeight *= nPages; vshrink = static_cast<double>(inputHeight) / targetHeight; } // Ensure we're using a device-independent colour space std::pair<char*, size_t> inputProfile(nullptr, 0); if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) && baton->withIccProfile.empty()) { // Cache input profile for use with output inputProfile = sharp::GetProfile(image); baton->input->ignoreIcc = true; } char const *processingProfile = image.interpretation() == VIPS_INTERPRETATION_RGB16 ? "p3" : "srgb"; if ( sharp::HasProfile(image) && image.interpretation() != VIPS_INTERPRETATION_LABS && image.interpretation() != VIPS_INTERPRETATION_GREY16 && baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK && !baton->input->ignoreIcc ) { // Convert to sRGB/P3 using embedded profile try { image = image.icc_transform(processingProfile, VImage::option() ->set("embedded", true) ->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8) ->set("intent", VIPS_INTENT_PERCEPTUAL)); } catch(...) { sharp::VipsWarningCallback(nullptr, G_LOG_LEVEL_WARNING, "Invalid embedded profile", nullptr); } } else if ( image.interpretation() == VIPS_INTERPRETATION_CMYK && baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK ) { image = image.icc_transform(processingProfile, VImage::option() ->set("input_profile", "cmyk") ->set("intent", VIPS_INTENT_PERCEPTUAL)); } // Flatten image to remove alpha channel if (baton->flatten && image.has_alpha()) { image = sharp::Flatten(image, baton->flattenBackground); } // Gamma encoding (darken) if (baton->gamma >= 1 && baton->gamma <= 3) { image = sharp::Gamma(image, 1.0 / baton->gamma); } // Convert to greyscale (linear, therefore after gamma encoding, if any) if (baton->greyscale) { image = image.colourspace(VIPS_INTERPRETATION_B_W); } bool const shouldResize = hshrink != 1.0 || vshrink != 1.0; bool const shouldBlur = baton->blurSigma != 0.0; bool const shouldConv = baton->convKernelWidth * baton->convKernelHeight > 0; bool const shouldSharpen = baton->sharpenSigma != 0.0; bool const shouldComposite = !baton->composite.empty(); if (shouldComposite && !image.has_alpha()) { image = sharp::EnsureAlpha(image, 1); } VipsBandFormat premultiplyFormat = image.format(); bool const shouldPremultiplyAlpha = image.has_alpha() && (shouldResize || shouldBlur || shouldConv || shouldSharpen); if (shouldPremultiplyAlpha) { image = image.premultiply().cast(premultiplyFormat); } // Resize if (shouldResize) { image = image.resize(1.0 / hshrink, VImage::option() ->set("vscale", 1.0 / vshrink) ->set("kernel", baton->kernel)); } image = sharp::StaySequential(image, autoRotation != VIPS_ANGLE_D0 || baton->flip || rotation != VIPS_ANGLE_D0); // Auto-rotate post-extract if (autoRotation != VIPS_ANGLE_D0) { if (autoRotation != VIPS_ANGLE_D180) { MultiPageUnsupported(nPages, "Rotate"); } image = image.rot(autoRotation); } // Mirror vertically (up-down) about the x-axis if (baton->flip) { image = image.flip(VIPS_DIRECTION_VERTICAL); } // Mirror horizontally (left-right) about the y-axis if (baton->flop != autoFlop) { image = image.flip(VIPS_DIRECTION_HORIZONTAL); } // Rotate post-extract 90-angle if (rotation != VIPS_ANGLE_D0) { if (rotation != VIPS_ANGLE_D180) { MultiPageUnsupported(nPages, "Rotate"); } image = image.rot(rotation); } // Join additional color channels to the image if (!baton->joinChannelIn.empty()) { VImage joinImage; sharp::ImageType joinImageType = sharp::ImageType::UNKNOWN; for (unsigned int i = 0; i < baton->joinChannelIn.size(); i++) { baton->joinChannelIn[i]->access = access; std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i]); joinImage = sharp::EnsureColourspace(joinImage, baton->colourspacePipeline); image = image.bandjoin(joinImage); } image = image.copy(VImage::option()->set("interpretation", baton->colourspace)); image = sharp::RemoveGifPalette(image); } inputWidth = image.width(); inputHeight = nPages > 1 ? targetPageHeight : image.height(); // Resolve dimensions if (baton->width <= 0) { baton->width = inputWidth; } if (baton->height <= 0) { baton->height = inputHeight; } // Crop/embed if (inputWidth != baton->width || inputHeight != baton->height) { if (baton->canvas == sharp::Canvas::EMBED) { std::vector<double> background; std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground, shouldPremultiplyAlpha); // Embed const auto& [left, top] = sharp::CalculateEmbedPosition( inputWidth, inputHeight, baton->width, baton->height, baton->position); const int width = std::max(inputWidth, baton->width); const int height = std::max(inputHeight, baton->height); image = nPages > 1 ? sharp::EmbedMultiPage(image, left, top, width, height, VIPS_EXTEND_BACKGROUND, background, nPages, &targetPageHeight) : image.embed(left, top, width, height, VImage::option() ->set("extend", VIPS_EXTEND_BACKGROUND) ->set("background", background)); } else if (baton->canvas == sharp::Canvas::CROP) { if (baton->width > inputWidth) { baton->width = inputWidth; } if (baton->height > inputHeight) { baton->height = inputHeight; } // Crop if (baton->position < 9) { // Gravity-based crop const auto& [left, top] = sharp::CalculateCrop( inputWidth, inputHeight, baton->width, baton->height, baton->position); const int width = std::min(inputWidth, baton->width); const int height = std::min(inputHeight, baton->height); image = nPages > 1 ? sharp::CropMultiPage(image, left, top, width, height, nPages, &targetPageHeight) : image.extract_area(left, top, width, height); } else { int attention_x; int attention_y; // Attention-based or Entropy-based crop MultiPageUnsupported(nPages, "Resize strategy"); image = sharp::StaySequential(image); image = image.smartcrop(baton->width, baton->height, VImage::option() ->set("interesting", baton->position == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION) ->set("premultiplied", shouldPremultiplyAlpha) ->set("attention_x", &attention_x) ->set("attention_y", &attention_y)); baton->hasCropOffset = true; baton->cropOffsetLeft = static_cast<int>(image.xoffset()); baton->cropOffsetTop = static_cast<int>(image.yoffset()); baton->hasAttentionCenter = true; baton->attentionX = static_cast<int>(attention_x * jpegShrinkOnLoad / scale); baton->attentionY = static_cast<int>(attention_y * jpegShrinkOnLoad / scale); } } } // Rotate post-extract non-90 angle if (!baton->rotateBefore && baton->rotationAngle != 0.0) { MultiPageUnsupported(nPages, "Rotate"); image = sharp::StaySequential(image); std::vector<double> background; std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, shouldPremultiplyAlpha); image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background)); } // Post extraction if (baton->topOffsetPost != -1) { if (nPages > 1) { image = sharp::CropMultiPage(image, baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost, nPages, &targetPageHeight); // heightPost is used in the info object, so update to reflect the number of pages baton->heightPost *= nPages; } else { image = image.extract_area( baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost); } } // Affine transform if (!baton->affineMatrix.empty()) { MultiPageUnsupported(nPages, "Affine"); image = sharp::StaySequential(image); std::vector<double> background; std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha); vips::VInterpolate interp = vips::VInterpolate::new_from_name( const_cast<char*>(baton->affineInterpolator.data())); image = image.affine(baton->affineMatrix, VImage::option()->set("background", background) ->set("idx", baton->affineIdx) ->set("idy", baton->affineIdy) ->set("odx", baton->affineOdx) ->set("ody", baton->affineOdy) ->set("interpolate", interp)); } // Extend edges if (baton->extendTop > 0 || baton->extendBottom > 0 || baton->extendLeft > 0 || baton->extendRight > 0) { // Embed baton->width = image.width() + baton->extendLeft + baton->extendRight; baton->height = (nPages > 1 ? targetPageHeight : image.height()) + baton->extendTop + baton->extendBottom; if (baton->extendWith == VIPS_EXTEND_BACKGROUND) { std::vector<double> background; std::tie(image, background) = sharp::ApplyAlpha(image, baton->extendBackground, shouldPremultiplyAlpha); image = sharp::StaySequential(image, nPages > 1); image = nPages > 1 ? sharp::EmbedMultiPage(image, baton->extendLeft, baton->extendTop, baton->width, baton->height, baton->extendWith, background, nPages, &targetPageHeight) : image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height, VImage::option()->set("extend", baton->extendWith)->set("background", background)); } else { std::vector<double> ignoredBackground(1); image = sharp::StaySequential(image); image = nPages > 1 ? sharp::EmbedMultiPage(image, baton->extendLeft, baton->extendTop, baton->width, baton->height, baton->extendWith, ignoredBackground, nPages, &targetPageHeight) : image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height, VImage::option()->set("extend", baton->extendWith)); } } // Median - must happen before blurring, due to the utility of blurring after thresholding if (baton->medianSize > 0) { image = image.median(baton->medianSize); } // Threshold - must happen before blurring, due to the utility of blurring after thresholding // Threshold - must happen before unflatten to enable non-white unflattening if (baton->threshold != 0) { image = sharp::Threshold(image, baton->threshold, baton->thresholdGrayscale); } // Dilate - must happen before blurring, due to the utility of dilating after thresholding if (baton->dilateWidth != 0) { image = sharp::Dilate(image, baton->dilateWidth); } // Erode - must happen before blurring, due to the utility of eroding after thresholding if (baton->erodeWidth != 0) { image = sharp::Erode(image, baton->erodeWidth); } // Blur if (shouldBlur) { image = sharp::Blur(image, baton->blurSigma, baton->precision, baton->minAmpl); } // Unflatten the image if (baton->unflatten) { image = sharp::Unflatten(image); } // Convolve if (shouldConv) { image = sharp::Convolve(image, baton->convKernelWidth, baton->convKernelHeight, baton->convKernelScale, baton->convKernelOffset, baton->convKernel); } // Recomb if (!baton->recombMatrix.empty()) { image = sharp::Recomb(image, baton->recombMatrix); } // Modulate if (baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0 || baton->lightness != 0.0) { image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue, baton->lightness); } // Sharpen if (shouldSharpen) { image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenM1, baton->sharpenM2, baton->sharpenX1, baton->sharpenY2, baton->sharpenY3); } // Reverse premultiplication after all transformations if (shouldPremultiplyAlpha) { image = image.unpremultiply().cast(premultiplyFormat); } baton->premultiplied = shouldPremultiplyAlpha; // Composite if (shouldComposite) { std::vector<VImage> images = { image }; std::vector<int> modes, xs, ys; for (Composite *composite : baton->composite) { VImage compositeImage; sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN; composite->input->access = access; std::tie(compositeImage, compositeImageType) = sharp::OpenInput(composite->input); if (composite->input->autoOrient) { // Respect EXIF Orientation VipsAngle compositeAutoRotation = VIPS_ANGLE_D0; bool compositeAutoFlop = false; std::tie(compositeAutoRotation, compositeAutoFlop) = CalculateExifRotationAndFlop(sharp::ExifOrientation(compositeImage)); compositeImage = sharp::RemoveExifOrientation(compositeImage); compositeImage = sharp::StaySequential(compositeImage, compositeAutoRotation != VIPS_ANGLE_D0); if (compositeAutoRotation != VIPS_ANGLE_D0) { compositeImage = compositeImage.rot(compositeAutoRotation); } if (compositeAutoFlop) { compositeImage = compositeImage.flip(VIPS_DIRECTION_HORIZONTAL); } } // 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"); } // Check if overlay is tiled if (composite->tile) { int across = 0; int down = 0; // Use gravity in overlay if (compositeImage.width() <= image.width()) { across = static_cast<int>(ceil(static_cast<double>(image.width()) / compositeImage.width())); // Ensure odd number of tiles across when gravity is centre, north or south if (composite->gravity == 0 || composite->gravity == 1 || composite->gravity == 3) { across |= 1; } } if (compositeImage.height() <= image.height()) { down = static_cast<int>(ceil(static_cast<double>(image.height()) / compositeImage.height())); // Ensure odd number of tiles down when gravity is centre, east or west if (composite->gravity == 0 || composite->gravity == 2 || composite->gravity == 4) { down |= 1; } } if (across != 0 || down != 0) { int left; int top; compositeImage = sharp::StaySequential(compositeImage).replicate(across, down); if (composite->hasOffset) { std::tie(left, top) = sharp::CalculateCrop( compositeImage.width(), compositeImage.height(), image.width(), image.height(), composite->left, composite->top); } else { std::tie(left, top) = sharp::CalculateCrop( compositeImage.width(), compositeImage.height(), image.width(), image.height(), composite->gravity); } compositeImage = compositeImage.extract_area(left, top, image.width(), image.height()); } // gravity was used for extract_area, set it back to its default value of 0 composite->gravity = 0; } // Ensure image to composite is with unpremultiplied alpha compositeImage = sharp::EnsureAlpha(compositeImage, 1); if (composite->premultiplied) compositeImage = compositeImage.unpremultiply(); // Calculate position int left; int top; if (composite->hasOffset) { // Composite image at given offsets if (composite->tile) { std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(), compositeImage.width(), compositeImage.height(), composite->left, composite->top); } else { left = composite->left; top = composite->top; } } else { // Composite image with given gravity std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(), compositeImage.width(), compositeImage.height(), composite->gravity); } images.push_back(compositeImage); modes.push_back(composite->mode); xs.push_back(left); ys.push_back(top); } image = VImage::composite(images, modes, VImage::option() ->set("compositing_space", baton->colourspacePipeline == VIPS_INTERPRETATION_LAST ? VIPS_INTERPRETATION_sRGB : baton->colourspacePipeline) ->set("x", xs) ->set("y", ys)); image = sharp::RemoveGifPalette(image); } // Gamma decoding (brighten) if (baton->gammaOut >= 1 && baton->gammaOut <= 3) { image = sharp::Gamma(image, baton->gammaOut); } // Linear adjustment (a * in + b) if (!baton->linearA.empty()) { image = sharp::Linear(image, baton->linearA, baton->linearB); } // Apply normalisation - stretch luminance to cover full dynamic range if (baton->normalise) { image = sharp::StaySequential(image); image = sharp::Normalise(image, baton->normaliseLower, baton->normaliseUpper); } // Apply contrast limiting adaptive histogram equalization (CLAHE) if (baton->claheWidth != 0 && baton->claheHeight != 0) { image = sharp::StaySequential(image); image = sharp::Clahe(image, baton->claheWidth, baton->claheHeight, baton->claheMaxSlope); } // Apply bitwise boolean operation between images if (baton->boolean != nullptr) { VImage booleanImage; sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN; baton->boolean->access = access; std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean); booleanImage = sharp::EnsureColourspace(booleanImage, baton->colourspacePipeline); image = sharp::Boolean(image, booleanImage, baton->booleanOp); image = sharp::RemoveGifPalette(image); } // Apply per-channel Bandbool bitwise operations after all other operations if (baton->bandBoolOp >= VIPS_OPERATION_BOOLEAN_AND && baton->bandBoolOp < VIPS_OPERATION_BOOLEAN_LAST) { image = sharp::Bandbool(image, baton->bandBoolOp); } // Tint the image if (baton->tint[0] >= 0.0) { image = sharp::Tint(image, baton->tint); } // Remove alpha channel, if any if (baton->removeAlpha) { image = sharp::RemoveAlpha(image); } // Ensure alpha channel, if missing if (baton->ensureAlpha != -1) { image = sharp::EnsureAlpha(image, baton->ensureAlpha); } // Ensure output colour space if (sharp::Is16Bit(image.interpretation())) { image = image.cast(VIPS_FORMAT_USHORT); } if (image.interpretation() != baton->colourspace) { image = image.colourspace(baton->colourspace, VImage::option()->set("source_space", image.interpretation())); if (inputProfile.first != nullptr && baton->withIccProfile.empty()) { image = sharp::SetProfile(image, inputProfile); } } // Extract channel if (baton->extractChannel > -1) { if (baton->extractChannel >= image.bands()) { if (baton->extractChannel == 3 && image.has_alpha()) { baton->extractChannel = image.bands() - 1; } else { (baton->err) .append("Cannot extract channel ").append(std::to_string(baton->extractChannel)) .append(" from image with channels 0-").append(std::to_string(image.bands() - 1)); return Error(); } } VipsInterpretation colourspace = sharp::Is16Bit(image.interpretation()) ? VIPS_INTERPRETATION_GREY16 : VIPS_INTERPRETATION_B_W; image = image .extract_band(baton->extractChannel) .copy(VImage::option()->set("interpretation", colourspace)); } // Apply output ICC profile if (!baton->withIccProfile.empty()) { try { image = image.icc_transform(const_cast<char*>(baton->withIccProfile.data()), VImage::option() ->set("input_profile", processingProfile) ->set("embedded", true) ->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8) ->set("intent", VIPS_INTENT_PERCEPTUAL)); } catch(...) { sharp::VipsWarningCallback(nullptr, G_LOG_LEVEL_WARNING, "Invalid profile", nullptr); } } // Negate the colours in the image if (baton->negate) { image = sharp::Negate(image, baton->negateAlpha); } // Override EXIF Orientation tag if (baton->withMetadataOrientation != -1) { image = sharp::SetExifOrientation(image, baton->withMetadataOrientation); } // Override pixel density if (baton->withMetadataDensity > 0) { image = sharp::SetDensity(image, baton->withMetadataDensity); } // EXIF key/value pairs if (baton->keepMetadata & VIPS_FOREIGN_KEEP_EXIF) { image = image.copy(); if (!baton->withExifMerge) { image = sharp::RemoveExif(image); } for (const auto& [key, value] : baton->withExif) { image.set(key.c_str(), value.c_str()); } } // XMP buffer if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_XMP) && !baton->withXmp.empty()) { image = image.copy(); image.set(VIPS_META_XMP_NAME, nullptr, const_cast<void*>(static_cast<void const*>(baton->withXmp.c_str())), baton->withXmp.size()); } // Number of channels used in output image baton->channels = image.bands(); baton->width = image.width(); baton->height = image.height(); image = sharp::SetAnimationProperties( image, nPages, targetPageHeight, baton->delay, baton->loop); if (image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT) { baton->pageHeightOut = image.get_int(VIPS_META_PAGE_HEIGHT); baton->pagesOut = image.get_int(VIPS_META_N_PAGES); } // Output sharp::SetTimeout(image, baton->timeoutSeconds); if (baton->fileOut.empty()) { // Buffer output if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) { // Write JPEG to buffer sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG); VipsArea *area = reinterpret_cast<VipsArea*>(image.jpegsave_buffer(VImage::option() ->set("keep", baton->keepMetadata) ->set("Q", baton->jpegQuality) ->set("interlace", baton->jpegProgressive) ->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4" ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON) ->set("trellis_quant", baton->jpegTrellisQuantisation) ->set("quant_table", baton->jpegQuantisationTable) ->set("overshoot_deringing", baton->jpegOvershootDeringing) ->set("optimize_scans", baton->jpegOptimiseScans) ->set("optimize_coding", baton->jpegOptimiseCoding))); baton->bufferOut = static_cast<char*>(area->data); baton->bufferOutLength = area->length; area->free_fn = nullptr; vips_area_unref(area); baton->formatOut = "jpeg"; if (baton->colourspace == VIPS_INTERPRETATION_CMYK) { baton->channels = std::min(baton->channels, 4); } else { baton->channels = std::min(baton->channels, 3); } } else if (baton->formatOut == "jp2" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JP2)) { // Write JP2 to Buffer sharp::AssertImageTypeDimensions(image, sharp::ImageType::JP2); VipsArea *area = reinterpret_cast<VipsArea*>(image.jp2ksave_buffer(VImage::option() ->set("Q", baton->jp2Quality) ->set("lossless", baton->jp2Lossless) ->set("subsample_mode", baton->jp2ChromaSubsampling == "4:4:4" ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON) ->set("tile_height", baton->jp2TileHeight) ->set("tile_width", baton->jp2TileWidth))); baton->bufferOut = static_cast<char*>(area->data); baton->bufferOutLength = area->length; area->free_fn = nullptr; vips_area_unref(area); baton->formatOut = "jp2"; } else if (baton->formatOut == "png" || (baton->formatOut == "input" && (inputImageType == sharp::ImageType::PNG || inputImageType == sharp::ImageType::SVG))) { // Write PNG to buffer sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG); VipsArea *area = reinterpret_cast<VipsArea*>(image.pngsave_buffer(VImage::option() ->set("keep", baton->keepMetadata) ->set("interlace", baton->pngProgressive) ->set("compression", baton->pngCompressionLevel) ->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE) ->set("palette", baton->pngPalette) ->set("Q", baton->pngQuality) ->set("effort", baton->pngEffort) ->set("bitdepth", sharp::Is16Bit(image.interpretation()) ? 16 : baton->pngBitdepth) ->set("dither", baton->pngDither))); baton->bufferOut = static_cast<char*>(area->data); baton->bufferOutLength = area->length; area->free_fn = nullptr; vips_area_unref(area); baton->formatOut = "png"; } else if (baton->formatOut == "webp" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::WEBP)) { // Write WEBP to buffer sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP); VipsArea *area = reinterpret_cast<VipsArea*>(image.webpsave_buffer(VImage::option() ->set("keep", baton->keepMetadata) ->set("Q", baton->webpQuality) ->set("lossless", baton->webpLossless) ->set("near_lossless", baton->webpNearLossless) ->set("smart_subsample", baton->webpSmartSubsample) ->set("smart_deblock", baton->webpSmartDeblock) ->set("preset", baton->webpPreset) ->set("effort", baton->webpEffort) ->set("min_size", baton->webpMinSize) ->set("mixed", baton->webpMixed) ->set("alpha_q", baton->webpAlphaQuality))); baton->bufferOut = static_cast<char*>(area->data); baton->bufferOutLength = area->length; area->free_fn = nullptr; vips_area_unref(area); baton->formatOut = "webp"; } else if (baton->formatOut == "gif" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::GIF)) { // Write GIF to buffer sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF); VipsArea *area = reinterpret_cast<VipsArea*>(image.gifsave_buffer(VImage::option() ->set("keep", baton->keepMetadata) ->set("bitdepth", baton->gifBitdepth) ->set("effort", baton->gifEffort) ->set("reuse", baton->gifReuse) ->set("interlace", baton->gifProgressive) ->set("interframe_maxerror", baton->gifInterFrameMaxError) ->set("interpalette_maxerror", baton->gifInterPaletteMaxError) ->set("keep_duplicate_frames", baton->gifKeepDuplicateFrames) ->set("dither", baton->gifDither))); baton->bufferOut = static_cast<char*>(area->data); baton->bufferOutLength = area->length; area->free_fn = nullptr; vips_area_unref(area); baton->formatOut = "gif"; } else if (baton->formatOut == "tiff" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::TIFF)) { // Write TIFF to buffer if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) { sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG); baton->channels = std::min(baton->channels, 3); } // Cast pixel values to float, if required if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) { image = image.cast(VIPS_FORMAT_FLOAT); } VipsArea *area = reinterpret_cast<VipsArea*>(image.tiffsave_buffer(VImage::option() ->set("keep", baton->keepMetadata) ->set("Q", baton->tiffQuality) ->set("bitdepth", baton->tiffBitdepth) ->set("compression", baton->tiffCompression) ->set("bigtiff", baton->tiffBigtiff) ->set("miniswhite", baton->tiffMiniswhite) ->set("predictor", baton->tiffPredictor) ->set("pyramid", baton->tiffPyramid) ->set("tile", baton->tiffTile) ->set("tile_height", baton->tiffTileHeight) ->set("tile_width", baton->tiffTileWidth) ->set("xres", baton->tiffXres) ->set("yres", baton->tiffYres) ->set("resunit", baton->tiffResolutionUnit))); baton->bufferOut = static_cast<char*>(area->data); baton->bufferOutLength = area->length; area->free_fn = nullptr; vips_area_unref(area); baton->formatOut = "tiff"; } else if (baton->formatOut == "heif" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) { // Write HEIF to buffer sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF); image = sharp::RemoveAnimationProperties(image); VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option() ->set("keep", baton->keepMetadata) ->set("Q", baton->heifQuality) ->set("compression", baton->heifCompression) ->set("effort", baton->heifEffort) ->set("bitdepth", baton->heifBitdepth) ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4" ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON) ->set("lossless", baton->heifLossless))); baton->bufferOut = static_cast<char*>(area->data); baton->bufferOutLength = area->length; area->free_fn = nullptr; vips_area_unref(area); baton->formatOut = "heif"; } else if (baton->formatOut == "dz") { // Write DZ to buffer baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP; if (!image.has_alpha()) { baton->tileBackground.pop_back(); } image = sharp::StaySequential(image, baton->tileAngle != 0); vips::VOption *options = BuildOptionsDZ(baton); VipsArea *area = reinterpret_cast<VipsArea*>(image.dzsave_buffer(options)); baton->bufferOut = static_cast<char*>(area->data); baton->bufferOutLength = area->length; area->free_fn = nullptr; vips_area_unref(area); baton->formatOut = "dz"; } else if (baton->formatOut == "jxl" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JXL)) { // Write JXL to buffer image = sharp::RemoveAnimationProperties(image); VipsArea *area = reinterpret_cast<VipsArea*>(image.jxlsave_buffer(VImage::option() ->set("keep", baton->keepMetadata) ->set("distance", baton->jxlDistance) ->set("tier", baton->jxlDecodingTier) ->set("effort", baton->jxlEffort) ->set("lossless", baton->jxlLossless))); baton->bufferOut = static_cast<char*>(area->data); baton->bufferOutLength = area->length; area->free_fn = nullptr; vips_area_unref(area); baton->formatOut = "jxl"; } else if (baton->formatOut == "raw" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::RAW)) { // Write raw, uncompressed image data to buffer if (baton->greyscale || image.interpretation() == VIPS_INTERPRETATION_B_W) { // Extract first band for greyscale image image = image[0]; baton->channels = 1; } if (image.format() != baton->rawDepth) { // Cast pixels to requested format image = image.cast(baton->rawDepth); } // 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(); } baton->formatOut = "raw"; } else { // Unsupported output format (baton->err).append("Unsupported output format "); if (baton->formatOut == "input") { (baton->err).append("when trying to match input format of "); (baton->err).append(ImageTypeId(inputImageType)); } else { (baton->err).append(baton->formatOut); } return Error(); } } else { // File output bool const isJpeg = sharp::IsJpeg(baton->fileOut); bool const isPng = sharp::IsPng(baton->fileOut); bool const isWebp = sharp::IsWebp(baton->fileOut); bool const isGif = sharp::IsGif(baton->fileOut); bool const isTiff = sharp::IsTiff(baton->fileOut); bool const isJp2 = sharp::IsJp2(baton->fileOut); bool const isHeif = sharp::IsHeif(baton->fileOut); bool const isJxl = sharp::IsJxl(baton->fileOut); bool const isDz = sharp::IsDz(baton->fileOut); bool const isDzZip = sharp::IsDzZip(baton->fileOut); bool const isV = sharp::IsV(baton->fileOut); bool const mightMatchInput = baton->formatOut == "input"; bool const willMatchInput = mightMatchInput && !(isJpeg || isPng || isWebp || isGif || isTiff || isJp2 || isHeif || isDz || isDzZip || isV); if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) || (willMatchInput && inputImageType == sharp::ImageType::JPEG)) { // Write JPEG to file sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG); image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option() ->set("keep", baton->keepMetadata) ->set("Q", baton->jpegQuality) ->set("interlace", baton->jpegProgressive) ->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4" ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON) ->set("trellis_quant", baton->jpegTrellisQuantisation) ->set("quant_table", baton->jpegQuantisationTable) ->set("overshoot_deringing", baton->jpegOvershootDeringing) ->set("optimize_scans", baton->jpegOptimiseScans) ->set("optimize_coding", baton->jpegOptimiseCoding)); baton->formatOut = "jpeg"; baton->channels = std::min(baton->channels, 3); } else if (baton->formatOut == "jp2" || (mightMatchInput && isJp2) || (willMatchInput && (inputImageType == sharp::ImageType::JP2))) { // Write JP2 to file sharp::AssertImageTypeDimensions(image, sharp::ImageType::JP2); image.jp2ksave(const_cast<char*>(baton->fileOut.data()), VImage::option() ->set("Q", baton->jp2Quality) ->set("lossless", baton->jp2Lossless) ->set("subsample_mode", baton->jp2ChromaSubsampling == "4:4:4" ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON) ->set("tile_height", baton->jp2TileHeight) ->set("tile_width", baton->jp2TileWidth)); baton->formatOut = "jp2"; } else if (baton->formatOut == "png" || (mightMatchInput && isPng) || (willMatchInput && (inputImageType == sharp::ImageType::PNG || inputImageType == sharp::ImageType::SVG))) { // Write PNG to file sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG); image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option() ->set("keep", baton->keepMetadata) ->set("interlace", baton->pngProgressive) ->set("compression", baton->pngCompressionLevel) ->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE) ->set("palette", baton->pngPalette) ->set("Q", baton->pngQuality) ->set("bitdepth", sharp::Is16Bit(image.interpretation()) ? 16 : baton->pngBitdepth) ->set("effort", baton->pngEffort) ->set("dither", baton->pngDither)); baton->formatOut = "png"; } else if (baton->formatOut == "webp" || (mightMatchInput && isWebp) || (willMatchInput && inputImageType == sharp::ImageType::WEBP)) { // Write WEBP to file sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP); image.webpsave(const_cast<char*>(baton->fileOut.data()), VImage::option() ->set("keep", baton->keepMetadata) ->set("Q", baton->webpQuality) ->set("lossless", baton->webpLossless) ->set("near_lossless", baton->webpNearLossless) ->set("smart_subsample", baton->webpSmartSubsample) ->set("smart_deblock", baton->webpSmartDeblock) ->set("preset", baton->webpPreset) ->set("effort", baton->webpEffort) ->set("min_size", baton->webpMinSize) ->set("mixed", baton->webpMixed) ->set("alpha_q", baton->webpAlphaQuality)); baton->formatOut = "webp"; } else if (baton->formatOut == "gif" || (mightMatchInput && isGif) || (willMatchInput && inputImageType == sharp::ImageType::GIF)) { // Write GIF to file sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF); image.gifsave(const_cast<char*>(baton->fileOut.data()), VImage::option() ->set("keep", baton->keepMetadata) ->set("bitdepth", baton->gifBitdepth) ->set("effort", baton->gifEffort) ->set("reuse", baton->gifReuse) ->set("interlace", baton->gifProgressive) ->set("interframe_maxerror", baton->gifInterFrameMaxError) ->set("interpalette_maxerror", baton->gifInterPaletteMaxError) ->set("keep_duplicate_frames", baton->gifKeepDuplicateFrames) ->set("dither", baton->gifDither)); baton->formatOut = "gif"; } else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) || (willMatchInput && inputImageType == sharp::ImageType::TIFF)) { // Write TIFF to file if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) { sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG); baton->channels = std::min(baton->channels, 3); } // Cast pixel values to float, if required if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) { image = image.cast(VIPS_FORMAT_FLOAT); } image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option() ->set("keep", baton->keepMetadata) ->set("Q", baton->tiffQuality) ->set("bitdepth", baton->tiffBitdepth) ->set("compression", baton->tiffCompression) ->set("bigtiff", baton->tiffBigtiff) ->set("miniswhite", baton->tiffMiniswhite) ->set("predictor", baton->tiffPredictor) ->set("pyramid", baton->tiffPyramid) ->set("tile", baton->tiffTile) ->set("tile_height", baton->tiffTileHeight) ->set("tile_width", baton->tiffTileWidth) ->set("xres", baton->tiffXres) ->set("yres", baton->tiffYres) ->set("resunit", baton->tiffResolutionUnit)); baton->formatOut = "tiff"; } else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) || (willMatchInput && inputImageType == sharp::ImageType::HEIF)) { // Write HEIF to file sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF); image = sharp::RemoveAnimationProperties(image); image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option() ->set("keep", baton->keepMetadata) ->set("Q", baton->heifQuality) ->set("compression", baton->heifCompression) ->set("effort", baton->heifEffort) ->set("bitdepth", baton->heifBitdepth) ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4" ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON) ->set("lossless", baton->heifLossless)); baton->formatOut = "heif"; } else if (baton->formatOut == "jxl" || (mightMatchInput && isJxl) || (willMatchInput && inputImageType == sharp::ImageType::JXL)) { // Write JXL to file image = sharp::RemoveAnimationProperties(image); image.jxlsave(const_cast<char*>(baton->fileOut.data()), VImage::option() ->set("keep", baton->keepMetadata) ->set("distance", baton->jxlDistance) ->set("tier", baton->jxlDecodingTier) ->set("effort", baton->jxlEffort) ->set("lossless", baton->jxlLossless)); baton->formatOut = "jxl"; } else if (baton->formatOut == "dz" || isDz || isDzZip) { // Write DZ to file if (isDzZip) { baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP; } if (!image.has_alpha()) { baton->tileBackground.pop_back(); } image = sharp::StaySequential(image, baton->tileAngle != 0); vips::VOption *options = BuildOptionsDZ(baton); image.dzsave(const_cast<char*>(baton->fileOut.data()), options); baton->formatOut = "dz"; } else if (baton->formatOut == "v" || (mightMatchInput && isV) || (willMatchInput && inputImageType == sharp::ImageType::VIPS)) { // Write V to file image.vipssave(const_cast<char*>(baton->fileOut.data()), VImage::option() ->set("keep", baton->keepMetadata)); baton->formatOut = "v"; } else { // Unsupported output format (baton->err).append("Unsupported output format " + baton->fileOut); return Error(); } } } catch (vips::VError 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"); } } } // Clean up libvips' per-request data and threads vips_error_clear(); vips_thread_shutdown(); } void OnOK() { 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(); } if (baton->err.empty()) { int width = baton->width; int height = baton->height; if (baton->topOffsetPre != -1 && (baton->width == -1 || baton->height == -1)) { width = baton->widthPre; height = baton->heightPre; } if (baton->topOffsetPost != -1) { width = baton->widthPost; height = baton->heightPost; } // Info Object Napi::Object info = Napi::Object::New(env); info.Set("format", baton->formatOut); info.Set("width", static_cast<uint32_t>(width)); info.Set("height", static_cast<uint32_t>(height)); info.Set("channels", static_cast<uint32_t>(baton->channels)); if (baton->formatOut == "raw") { info.Set("depth", vips_enum_nick(VIPS_TYPE_BAND_FORMAT, baton->rawDepth)); } info.Set("premultiplied", baton->premultiplied); if (baton->hasCropOffset) { info.Set("cropOffsetLeft", static_cast<int32_t>(baton->cropOffsetLeft)); info.Set("cropOffsetTop", static_cast<int32_t>(baton->cropOffsetTop)); } if (baton->hasAttentionCenter) { info.Set("attentionX", static_cast<int32_t>(baton->attentionX)); info.Set("attentionY", static_cast<int32_t>(baton->attentionY)); } if (baton->trimThreshold >= 0.0) { info.Set("trimOffsetLeft", static_cast<int32_t>(baton->trimOffsetLeft)); info.Set("trimOffsetTop", static_cast<int32_t>(baton->trimOffsetTop)); } if (baton->input->textAutofitDpi) { info.Set("textAutofitDpi", static_cast<uint32_t>(baton->input->textAutofitDpi)); } if (baton->pageHeightOut) { info.Set("pageHeight", static_cast<int32_t>(baton->pageHeightOut)); info.Set("pages", static_cast<int32_t>(baton->pagesOut)); } if (baton->bufferOutLength > 0) { // Add buffer size to info info.Set("size", static_cast<uint32_t>(baton->bufferOutLength)); // Pass ownership of output data to Buffer instance Napi::Buffer<char> data = Napi::Buffer<char>::NewOrCopy(env, static_cast<char*>(baton->bufferOut), baton->bufferOutLength, sharp::FreeCallback); Callback().Call(Receiver().Value(), { env.Null(), data, info }); } else { // Add file size to info if (baton->formatOut != "dz" || sharp::IsDzZip(baton->fileOut)) { try { uint32_t const size = static_cast<uint32_t>( std::filesystem::file_size(std::filesystem::u8path(baton->fileOut))); info.Set("size", size); } catch (...) {} } Callback().Call(Receiver().Value(), { env.Null(), info }); } } else { Callback().Call(Receiver().Value(), { Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() }); } // Delete baton delete baton->input; delete baton->boolean; for (Composite *composite : baton->composite) { delete composite->input; delete composite; } for (sharp::InputDescriptor *input : baton->joinChannelIn) { delete input; } for (sharp::InputDescriptor *input : baton->join) { delete input; } delete baton; // Decrement processing task counter sharp::counterProcess--; Napi::Number queueLength = Napi::Number::New(env, static_cast<int>(sharp::counterQueue)); queueListener.Call(Receiver().Value(), { queueLength }); } private: PipelineBaton *baton; Napi::FunctionReference debuglog; Napi::FunctionReference queueListener; void MultiPageUnsupported(int const pages, std::string op) { if (pages > 1) { throw vips::VError(op + " is not supported for multi-page images"); } } /* Calculate the angle of rotation and need-to-flip for the given Exif orientation By default, returns zero, i.e. no rotation. */ std::tuple<VipsAngle, bool> CalculateExifRotationAndFlop(int const exifOrientation) { VipsAngle rotate = VIPS_ANGLE_D0; bool flop = false; switch (exifOrientation) { case 6: rotate = VIPS_ANGLE_D90; break; case 3: rotate = VIPS_ANGLE_D180; break; case 8: rotate = VIPS_ANGLE_D270; break; case 2: flop = true; break; case 7: flop = true; rotate = VIPS_ANGLE_D270; break; case 4: flop = true; rotate = VIPS_ANGLE_D180; break; case 5: flop = true; rotate = VIPS_ANGLE_D90; break; } return std::make_tuple(rotate, flop); } /* Calculate the rotation for the given angle. Supports any positive or negative angle that is a multiple of 90. */ VipsAngle CalculateAngleRotation(int angle) { angle = angle % 360; if (angle < 0) angle = 360 + angle; switch (angle) { case 90: return VIPS_ANGLE_D90; case 180: return VIPS_ANGLE_D180; case 270: return VIPS_ANGLE_D270; } return VIPS_ANGLE_D0; } /* Assemble the suffix argument to dzsave, which is the format (by extname) alongside comma-separated arguments to the corresponding `formatsave` vips action. */ std::string AssembleSuffixString(std::string extname, std::vector<std::pair<std::string, std::string>> options) { std::string argument; for (const auto& [key, value] : options) { if (!argument.empty()) { argument += ","; } argument += key + "=" + value; } return extname + "[" + argument + "]"; } /* Build VOption for dzsave */ vips::VOption* BuildOptionsDZ(PipelineBaton *baton) { // Forward format options through suffix std::string suffix; if (baton->tileFormat == "png") { std::vector<std::pair<std::string, std::string>> options { {"interlace", baton->pngProgressive ? "true" : "false"}, {"compression", std::to_string(baton->pngCompressionLevel)}, {"filter", baton->pngAdaptiveFiltering ? "all" : "none"} }; suffix = AssembleSuffixString(".png", options); } else if (baton->tileFormat == "webp") { std::vector<std::pair<std::string, std::string>> options { {"Q", std::to_string(baton->webpQuality)}, {"alpha_q", std::to_string(baton->webpAlphaQuality)}, {"lossless", baton->webpLossless ? "true" : "false"}, {"near_lossless", baton->webpNearLossless ? "true" : "false"}, {"smart_subsample", baton->webpSmartSubsample ? "true" : "false"}, {"smart_deblock", baton->webpSmartDeblock ? "true" : "false"}, {"preset", vips_enum_nick(VIPS_TYPE_FOREIGN_WEBP_PRESET, baton->webpPreset)}, {"min_size", baton->webpMinSize ? "true" : "false"}, {"mixed", baton->webpMixed ? "true" : "false"}, {"effort", std::to_string(baton->webpEffort)} }; suffix = AssembleSuffixString(".webp", options); } else { std::vector<std::pair<std::string, std::string>> options { {"Q", std::to_string(baton->jpegQuality)}, {"interlace", baton->jpegProgressive ? "true" : "false"}, {"subsample_mode", baton->jpegChromaSubsampling == "4:4:4" ? "off" : "on"}, {"trellis_quant", baton->jpegTrellisQuantisation ? "true" : "false"}, {"quant_table", std::to_string(baton->jpegQuantisationTable)}, {"overshoot_deringing", baton->jpegOvershootDeringing ? "true": "false"}, {"optimize_scans", baton->jpegOptimiseScans ? "true": "false"}, {"optimize_coding", baton->jpegOptimiseCoding ? "true": "false"} }; std::string extname = baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_DZ ? ".jpeg" : ".jpg"; suffix = AssembleSuffixString(extname, options); } vips::VOption *options = VImage::option() ->set("keep", baton->keepMetadata) ->set("tile_size", baton->tileSize) ->set("overlap", baton->tileOverlap) ->set("container", baton->tileContainer) ->set("layout", baton->tileLayout) ->set("suffix", const_cast<char*>(suffix.data())) ->set("angle", CalculateAngleRotation(baton->tileAngle)) ->set("background", baton->tileBackground) ->set("centre", baton->tileCentre) ->set("id", const_cast<char*>(baton->tileId.data())) ->set("skip_blanks", baton->tileSkipBlanks); if (baton->tileDepth < VIPS_FOREIGN_DZ_DEPTH_LAST) { options->set("depth", baton->tileDepth); } if (!baton->tileBasename.empty()) { options->set("basename", const_cast<char*>(baton->tileBasename.data())); } return options; } /* Clear all thread-local data. */ void Error() { // Clean up libvips' per-request data and threads vips_error_clear(); vips_thread_shutdown(); } }; /* pipeline(options, output, callback) */ Napi::Value pipeline(const Napi::CallbackInfo& info) { // V8 objects are converted to non-V8 types held in the baton struct PipelineBaton *baton = new PipelineBaton; Napi::Object options = info[size_t(0)].As<Napi::Object>(); // Input baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>()); // Join images together if (sharp::HasAttr(options, "join")) { Napi::Array join = options.Get("join").As<Napi::Array>(); for (unsigned int i = 0; i < join.Length(); i++) { baton->join.push_back( sharp::CreateInputDescriptor(join.Get(i).As<Napi::Object>())); } } // Extract image options baton->topOffsetPre = sharp::AttrAsInt32(options, "topOffsetPre"); baton->leftOffsetPre = sharp::AttrAsInt32(options, "leftOffsetPre"); baton->widthPre = sharp::AttrAsInt32(options, "widthPre"); baton->heightPre = sharp::AttrAsInt32(options, "heightPre"); baton->topOffsetPost = sharp::AttrAsInt32(options, "topOffsetPost"); baton->leftOffsetPost = sharp::AttrAsInt32(options, "leftOffsetPost"); baton->widthPost = sharp::AttrAsInt32(options, "widthPost"); baton->heightPost = sharp::AttrAsInt32(options, "heightPost"); // Output image dimensions baton->width = sharp::AttrAsInt32(options, "width"); baton->height = sharp::AttrAsInt32(options, "height"); // Canvas option std::string canvas = sharp::AttrAsStr(options, "canvas"); if (canvas == "crop") { baton->canvas = sharp::Canvas::CROP; } else if (canvas == "embed") { baton->canvas = sharp::Canvas::EMBED; } else if (canvas == "max") { baton->canvas = sharp::Canvas::MAX; } else if (canvas == "min") { baton->canvas = sharp::Canvas::MIN; } else if (canvas == "ignore_aspect") { baton->canvas = sharp::Canvas::IGNORE_ASPECT; } // Composite Napi::Array compositeArray = options.Get("composite").As<Napi::Array>(); for (unsigned int i = 0; i < compositeArray.Length(); i++) { Napi::Object compositeObject = compositeArray.Get(i).As<Napi::Object>(); Composite *composite = new Composite; composite->input = sharp::CreateInputDescriptor(compositeObject.Get("input").As<Napi::Object>()); composite->mode = sharp::AttrAsEnum<VipsBlendMode>(compositeObject, "blend", VIPS_TYPE_BLEND_MODE); composite->gravity = sharp::AttrAsUint32(compositeObject, "gravity"); composite->left = sharp::AttrAsInt32(compositeObject, "left"); composite->top = sharp::AttrAsInt32(compositeObject, "top"); composite->hasOffset = sharp::AttrAsBool(compositeObject, "hasOffset"); composite->tile = sharp::AttrAsBool(compositeObject, "tile"); composite->premultiplied = sharp::AttrAsBool(compositeObject, "premultiplied"); baton->composite.push_back(composite); } // Resize options baton->withoutEnlargement = sharp::AttrAsBool(options, "withoutEnlargement"); baton->withoutReduction = sharp::AttrAsBool(options, "withoutReduction"); baton->position = sharp::AttrAsInt32(options, "position"); baton->resizeBackground = sharp::AttrAsVectorOfDouble(options, "resizeBackground"); baton->kernel = sharp::AttrAsEnum<VipsKernel>(options, "kernel", VIPS_TYPE_KERNEL); baton->fastShrinkOnLoad = sharp::AttrAsBool(options, "fastShrinkOnLoad"); // Join Channel Options if (options.Has("joinChannelIn")) { Napi::Array joinChannelArray = options.Get("joinChannelIn").As<Napi::Array>(); for (unsigned int i = 0; i < joinChannelArray.Length(); i++) { baton->joinChannelIn.push_back( sharp::CreateInputDescriptor(joinChannelArray.Get(i).As<Napi::Object>())); } } // Operators baton->flatten = sharp::AttrAsBool(options, "flatten"); baton->flattenBackground = sharp::AttrAsVectorOfDouble(options, "flattenBackground"); baton->unflatten = sharp::AttrAsBool(options, "unflatten"); baton->negate = sharp::AttrAsBool(options, "negate"); baton->negateAlpha = sharp::AttrAsBool(options, "negateAlpha"); baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma"); baton->precision = sharp::AttrAsEnum<VipsPrecision>(options, "precision", VIPS_TYPE_PRECISION); baton->minAmpl = sharp::AttrAsDouble(options, "minAmpl"); baton->brightness = sharp::AttrAsDouble(options, "brightness"); baton->saturation = sharp::AttrAsDouble(options, "saturation"); baton->hue = sharp::AttrAsInt32(options, "hue"); baton->lightness = sharp::AttrAsDouble(options, "lightness"); baton->medianSize = sharp::AttrAsUint32(options, "medianSize"); baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma"); baton->sharpenM1 = sharp::AttrAsDouble(options, "sharpenM1"); baton->sharpenM2 = sharp::AttrAsDouble(options, "sharpenM2"); baton->sharpenX1 = sharp::AttrAsDouble(options, "sharpenX1"); baton->sharpenY2 = sharp::AttrAsDouble(options, "sharpenY2"); baton->sharpenY3 = sharp::AttrAsDouble(options, "sharpenY3"); baton->threshold = sharp::AttrAsInt32(options, "threshold"); baton->thresholdGrayscale = sharp::AttrAsBool(options, "thresholdGrayscale"); baton->trimBackground = sharp::AttrAsVectorOfDouble(options, "trimBackground"); baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold"); baton->trimLineArt = sharp::AttrAsBool(options, "trimLineArt"); baton->gamma = sharp::AttrAsDouble(options, "gamma"); baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut"); baton->linearA = sharp::AttrAsVectorOfDouble(options, "linearA"); baton->linearB = sharp::AttrAsVectorOfDouble(options, "linearB"); baton->dilateWidth = sharp::AttrAsUint32(options, "dilateWidth"); baton->erodeWidth = sharp::AttrAsUint32(options, "erodeWidth"); baton->greyscale = sharp::AttrAsBool(options, "greyscale"); baton->normalise = sharp::AttrAsBool(options, "normalise"); baton->normaliseLower = sharp::AttrAsUint32(options, "normaliseLower"); baton->normaliseUpper = sharp::AttrAsUint32(options, "normaliseUpper"); baton->tint = sharp::AttrAsVectorOfDouble(options, "tint"); baton->claheWidth = sharp::AttrAsUint32(options, "claheWidth"); baton->claheHeight = sharp::AttrAsUint32(options, "claheHeight"); baton->claheMaxSlope = sharp::AttrAsUint32(options, "claheMaxSlope"); baton->angle = sharp::AttrAsInt32(options, "angle"); baton->rotationAngle = sharp::AttrAsDouble(options, "rotationAngle"); baton->rotationBackground = sharp::AttrAsVectorOfDouble(options, "rotationBackground"); baton->rotateBefore = sharp::AttrAsBool(options, "rotateBefore"); baton->orientBefore = sharp::AttrAsBool(options, "orientBefore"); baton->flip = sharp::AttrAsBool(options, "flip"); baton->flop = sharp::AttrAsBool(options, "flop"); baton->extendTop = sharp::AttrAsInt32(options, "extendTop"); baton->extendBottom = sharp::AttrAsInt32(options, "extendBottom"); baton->extendLeft = sharp::AttrAsInt32(options, "extendLeft"); baton->extendRight = sharp::AttrAsInt32(options, "extendRight"); baton->extendBackground = sharp::AttrAsVectorOfDouble(options, "extendBackground"); baton->extendWith = sharp::AttrAsEnum<VipsExtend>(options, "extendWith", VIPS_TYPE_EXTEND); baton->extractChannel = sharp::AttrAsInt32(options, "extractChannel"); baton->affineMatrix = sharp::AttrAsVectorOfDouble(options, "affineMatrix"); baton->affineBackground = sharp::AttrAsVectorOfDouble(options, "affineBackground"); baton->affineIdx = sharp::AttrAsDouble(options, "affineIdx"); baton->affineIdy = sharp::AttrAsDouble(options, "affineIdy"); baton->affineOdx = sharp::AttrAsDouble(options, "affineOdx"); baton->affineOdy = sharp::AttrAsDouble(options, "affineOdy"); baton->affineInterpolator = sharp::AttrAsStr(options, "affineInterpolator"); baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha"); baton->ensureAlpha = sharp::AttrAsDouble(options, "ensureAlpha"); if (options.Has("boolean")) { baton->boolean = sharp::CreateInputDescriptor(options.Get("boolean").As<Napi::Object>()); baton->booleanOp = sharp::AttrAsEnum<VipsOperationBoolean>(options, "booleanOp", VIPS_TYPE_OPERATION_BOOLEAN); } if (options.Has("bandBoolOp")) { baton->bandBoolOp = sharp::AttrAsEnum<VipsOperationBoolean>(options, "bandBoolOp", VIPS_TYPE_OPERATION_BOOLEAN); } if (options.Has("convKernel")) { Napi::Object kernel = options.Get("convKernel").As<Napi::Object>(); baton->convKernelWidth = sharp::AttrAsUint32(kernel, "width"); baton->convKernelHeight = sharp::AttrAsUint32(kernel, "height"); baton->convKernelScale = sharp::AttrAsDouble(kernel, "scale"); baton->convKernelOffset = sharp::AttrAsDouble(kernel, "offset"); size_t const kernelSize = static_cast<size_t>(baton->convKernelWidth * baton->convKernelHeight); baton->convKernel.resize(kernelSize); Napi::Array kdata = kernel.Get("kernel").As<Napi::Array>(); for (unsigned int i = 0; i < kernelSize; i++) { baton->convKernel[i] = sharp::AttrAsDouble(kdata, i); } } if (options.Has("recombMatrix")) { Napi::Array recombMatrix = options.Get("recombMatrix").As<Napi::Array>(); unsigned int matrixElements = recombMatrix.Length(); baton->recombMatrix.resize(matrixElements); for (unsigned int i = 0; i < matrixElements; i++) { baton->recombMatrix[i] = sharp::AttrAsDouble(recombMatrix, i); } } baton->colourspacePipeline = sharp::AttrAsEnum<VipsInterpretation>( options, "colourspacePipeline", VIPS_TYPE_INTERPRETATION); if (baton->colourspacePipeline == VIPS_INTERPRETATION_ERROR) { baton->colourspacePipeline = VIPS_INTERPRETATION_LAST; } baton->colourspace = sharp::AttrAsEnum<VipsInterpretation>(options, "colourspace", VIPS_TYPE_INTERPRETATION); if (baton->colourspace == VIPS_INTERPRETATION_ERROR) { baton->colourspace = VIPS_INTERPRETATION_sRGB; } // Output baton->formatOut = sharp::AttrAsStr(options, "formatOut"); baton->fileOut = sharp::AttrAsStr(options, "fileOut"); baton->keepMetadata = sharp::AttrAsUint32(options, "keepMetadata"); baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation"); baton->withMetadataDensity = sharp::AttrAsDouble(options, "withMetadataDensity"); baton->withIccProfile = sharp::AttrAsStr(options, "withIccProfile"); Napi::Object withExif = options.Get("withExif").As<Napi::Object>(); Napi::Array withExifKeys = withExif.GetPropertyNames(); for (unsigned int i = 0; i < withExifKeys.Length(); i++) { std::string k = sharp::AttrAsStr(withExifKeys, i); if (withExif.HasOwnProperty(k)) { baton->withExif.insert(std::make_pair(k, sharp::AttrAsStr(withExif, k))); } } baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge"); baton->withXmp = sharp::AttrAsStr(options, "withXmp"); baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds"); baton->loop = sharp::AttrAsUint32(options, "loop"); baton->delay = sharp::AttrAsInt32Vector(options, "delay"); // Format-specific baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality"); baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive"); baton->jpegChromaSubsampling = sharp::AttrAsStr(options, "jpegChromaSubsampling"); baton->jpegTrellisQuantisation = sharp::AttrAsBool(options, "jpegTrellisQuantisation"); baton->jpegQuantisationTable = sharp::AttrAsUint32(options, "jpegQuantisationTable"); baton->jpegOvershootDeringing = sharp::AttrAsBool(options, "jpegOvershootDeringing"); baton->jpegOptimiseScans = sharp::AttrAsBool(options, "jpegOptimiseScans"); baton->jpegOptimiseCoding = sharp::AttrAsBool(options, "jpegOptimiseCoding"); baton->pngProgressive = sharp::AttrAsBool(options, "pngProgressive"); baton->pngCompressionLevel = sharp::AttrAsUint32(options, "pngCompressionLevel"); baton->pngAdaptiveFiltering = sharp::AttrAsBool(options, "pngAdaptiveFiltering"); baton->pngPalette = sharp::AttrAsBool(options, "pngPalette"); baton->pngQuality = sharp::AttrAsUint32(options, "pngQuality"); baton->pngEffort = sharp::AttrAsUint32(options, "pngEffort"); baton->pngBitdepth = sharp::AttrAsUint32(options, "pngBitdepth"); baton->pngDither = sharp::AttrAsDouble(options, "pngDither"); baton->jp2Quality = sharp::AttrAsUint32(options, "jp2Quality"); baton->jp2Lossless = sharp::AttrAsBool(options, "jp2Lossless"); baton->jp2TileHeight = sharp::AttrAsUint32(options, "jp2TileHeight"); baton->jp2TileWidth = sharp::AttrAsUint32(options, "jp2TileWidth"); baton->jp2ChromaSubsampling = sharp::AttrAsStr(options, "jp2ChromaSubsampling"); baton->webpQuality = sharp::AttrAsUint32(options, "webpQuality"); baton->webpAlphaQuality = sharp::AttrAsUint32(options, "webpAlphaQuality"); baton->webpLossless = sharp::AttrAsBool(options, "webpLossless"); baton->webpNearLossless = sharp::AttrAsBool(options, "webpNearLossless"); baton->webpSmartSubsample = sharp::AttrAsBool(options, "webpSmartSubsample"); baton->webpSmartDeblock = sharp::AttrAsBool(options, "webpSmartDeblock"); baton->webpPreset = sharp::AttrAsEnum<VipsForeignWebpPreset>(options, "webpPreset", VIPS_TYPE_FOREIGN_WEBP_PRESET); baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort"); baton->webpMinSize = sharp::AttrAsBool(options, "webpMinSize"); baton->webpMixed = sharp::AttrAsBool(options, "webpMixed"); baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth"); baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort"); baton->gifDither = sharp::AttrAsDouble(options, "gifDither"); baton->gifInterFrameMaxError = sharp::AttrAsDouble(options, "gifInterFrameMaxError"); baton->gifInterPaletteMaxError = sharp::AttrAsDouble(options, "gifInterPaletteMaxError"); baton->gifKeepDuplicateFrames = sharp::AttrAsBool(options, "gifKeepDuplicateFrames"); baton->gifReuse = sharp::AttrAsBool(options, "gifReuse"); baton->gifProgressive = sharp::AttrAsBool(options, "gifProgressive"); baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality"); baton->tiffBigtiff = sharp::AttrAsBool(options, "tiffBigtiff"); baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid"); baton->tiffMiniswhite = sharp::AttrAsBool(options, "tiffMiniswhite"); baton->tiffBitdepth = sharp::AttrAsUint32(options, "tiffBitdepth"); baton->tiffTile = sharp::AttrAsBool(options, "tiffTile"); baton->tiffTileWidth = sharp::AttrAsUint32(options, "tiffTileWidth"); baton->tiffTileHeight = sharp::AttrAsUint32(options, "tiffTileHeight"); baton->tiffXres = sharp::AttrAsDouble(options, "tiffXres"); baton->tiffYres = sharp::AttrAsDouble(options, "tiffYres"); if (baton->tiffXres == 1.0 && baton->tiffYres == 1.0 && baton->withMetadataDensity > 0) { baton->tiffXres = baton->tiffYres = baton->withMetadataDensity / 25.4; } baton->tiffCompression = sharp::AttrAsEnum<VipsForeignTiffCompression>( options, "tiffCompression", VIPS_TYPE_FOREIGN_TIFF_COMPRESSION); baton->tiffPredictor = sharp::AttrAsEnum<VipsForeignTiffPredictor>( options, "tiffPredictor", VIPS_TYPE_FOREIGN_TIFF_PREDICTOR); baton->tiffResolutionUnit = sharp::AttrAsEnum<VipsForeignTiffResunit>( options, "tiffResolutionUnit", VIPS_TYPE_FOREIGN_TIFF_RESUNIT); baton->heifQuality = sharp::AttrAsUint32(options, "heifQuality"); baton->heifLossless = sharp::AttrAsBool(options, "heifLossless"); baton->heifCompression = sharp::AttrAsEnum<VipsForeignHeifCompression>( options, "heifCompression", VIPS_TYPE_FOREIGN_HEIF_COMPRESSION); baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort"); baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling"); baton->heifBitdepth = sharp::AttrAsUint32(options, "heifBitdepth"); baton->jxlDistance = sharp::AttrAsDouble(options, "jxlDistance"); baton->jxlDecodingTier = sharp::AttrAsUint32(options, "jxlDecodingTier"); baton->jxlEffort = sharp::AttrAsUint32(options, "jxlEffort"); baton->jxlLossless = sharp::AttrAsBool(options, "jxlLossless"); baton->rawDepth = sharp::AttrAsEnum<VipsBandFormat>(options, "rawDepth", VIPS_TYPE_BAND_FORMAT); baton->tileSize = sharp::AttrAsUint32(options, "tileSize"); baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap"); baton->tileAngle = sharp::AttrAsInt32(options, "tileAngle"); baton->tileBackground = sharp::AttrAsVectorOfDouble(options, "tileBackground"); baton->tileSkipBlanks = sharp::AttrAsInt32(options, "tileSkipBlanks"); baton->tileContainer = sharp::AttrAsEnum<VipsForeignDzContainer>( options, "tileContainer", VIPS_TYPE_FOREIGN_DZ_CONTAINER); baton->tileLayout = sharp::AttrAsEnum<VipsForeignDzLayout>(options, "tileLayout", VIPS_TYPE_FOREIGN_DZ_LAYOUT); baton->tileFormat = sharp::AttrAsStr(options, "tileFormat"); baton->tileDepth = sharp::AttrAsEnum<VipsForeignDzDepth>(options, "tileDepth", VIPS_TYPE_FOREIGN_DZ_DEPTH); baton->tileCentre = sharp::AttrAsBool(options, "tileCentre"); baton->tileId = sharp::AttrAsStr(options, "tileId"); baton->tileBasename = sharp::AttrAsStr(options, "tileBasename"); // Function to notify of libvips warnings Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>(); // Function to notify of queue length changes Napi::Function queueListener = options.Get("queueListener").As<Napi::Function>(); // Join queue for worker thread Napi::Function callback = info[size_t(1)].As<Napi::Function>(); PipelineWorker *worker = new PipelineWorker(callback, baton, debuglog, queueListener); worker->Receiver().Set("options", options); worker->Queue(); // Increment queued task counter Napi::Number queueLength = Napi::Number::New(info.Env(), static_cast<int>(++sharp::counterQueue)); queueListener.Call(info.This(), { queueLength }); return info.Env().Undefined(); }
Save
/*! Copyright 2013 Lovell Fuller and others. SPDX-License-Identifier: Apache-2.0 */ #include
#include
#include
// NOLINT(build/c++17) #include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "./common.h" #include "./operations.h" #include "./pipeline.h" class PipelineWorker : public Napi::AsyncWorker { public: PipelineWorker(Napi::Function callback, PipelineBaton *baton, Napi::Function debuglog, Napi::Function queueListener) : Napi::AsyncWorker(callback), baton(baton), debuglog(Napi::Persistent(debuglog)), queueListener(Napi::Persistent(queueListener)) {} ~PipelineWorker() {} // libuv worker void Execute() { // Decrement queued task counter sharp::counterQueue--; // Increment processing task counter sharp::counterProcess++; try { // Open input vips::VImage image; sharp::ImageType inputImageType; if (baton->join.empty()) { std::tie(image, inputImageType) = sharp::OpenInput(baton->input); } else { std::vector
images; bool hasAlpha = false; for (auto &join : baton->join) { std::tie(image, inputImageType) = sharp::OpenInput(join); image = sharp::EnsureColourspace(image, baton->colourspacePipeline); images.push_back(image); hasAlpha |= image.has_alpha(); } if (hasAlpha) { for (auto &image : images) { if (!image.has_alpha()) { image = sharp::EnsureAlpha(image, 1); } } } else { baton->input->joinBackground.pop_back(); } inputImageType = sharp::ImageType::PNG; image = VImage::arrayjoin(images, VImage::option() ->set("across", baton->input->joinAcross) ->set("shim", baton->input->joinShim) ->set("background", baton->input->joinBackground) ->set("halign", baton->input->joinHalign) ->set("valign", baton->input->joinValign)); if (baton->input->joinAnimated) { image = image.copy(); image.set(VIPS_META_N_PAGES, static_cast
(images.size())); image.set(VIPS_META_PAGE_HEIGHT, static_cast
(image.height() / images.size())); } } VipsAccess access = baton->input->access; image = sharp::EnsureColourspace(image, baton->colourspacePipeline); int nPages = baton->input->pages; 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 : 1; } // Get pre-resize page height int pageHeight = sharp::GetPageHeight(image); // Calculate angle of rotation VipsAngle rotation = VIPS_ANGLE_D0; VipsAngle autoRotation = VIPS_ANGLE_D0; bool autoFlop = false; if (baton->input->autoOrient) { // Rotate and flip image according to Exif orientation std::tie(autoRotation, autoFlop) = CalculateExifRotationAndFlop(sharp::ExifOrientation(image)); } rotation = CalculateAngleRotation(baton->angle); bool const shouldRotateBefore = baton->rotateBefore && (rotation != VIPS_ANGLE_D0 || baton->flip || baton->flop || baton->rotationAngle != 0.0); bool const shouldOrientBefore = (shouldRotateBefore || baton->orientBefore) && (autoRotation != VIPS_ANGLE_D0 || autoFlop); if (shouldOrientBefore) { image = sharp::StaySequential(image, autoRotation != VIPS_ANGLE_D0); if (autoRotation != VIPS_ANGLE_D0) { if (autoRotation != VIPS_ANGLE_D180) { MultiPageUnsupported(nPages, "Rotate"); } image = image.rot(autoRotation); autoRotation = VIPS_ANGLE_D0; } if (autoFlop) { image = image.flip(VIPS_DIRECTION_HORIZONTAL); autoFlop = false; } } if (shouldRotateBefore) { image = sharp::StaySequential(image, rotation != VIPS_ANGLE_D0 || baton->flip || baton->rotationAngle != 0.0); if (baton->flip) { image = image.flip(VIPS_DIRECTION_VERTICAL); baton->flip = false; } if (baton->flop) { image = image.flip(VIPS_DIRECTION_HORIZONTAL); baton->flop = false; } if (rotation != VIPS_ANGLE_D0) { if (rotation != VIPS_ANGLE_D180) { MultiPageUnsupported(nPages, "Rotate"); } image = image.rot(rotation); rotation = VIPS_ANGLE_D0; } if (baton->rotationAngle != 0.0) { MultiPageUnsupported(nPages, "Rotate"); std::vector
background; std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, false); image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background)).copy_memory(); baton->rotationAngle = 0.0; } } // Trim if (baton->trimThreshold >= 0.0) { MultiPageUnsupported(nPages, "Trim"); image = sharp::StaySequential(image); image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold, baton->trimLineArt); baton->trimOffsetLeft = image.xoffset(); baton->trimOffsetTop = image.yoffset(); } // Pre extraction if (baton->topOffsetPre != -1) { image = nPages > 1 ? sharp::CropMultiPage(image, baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre, nPages, &pageHeight) : image.extract_area(baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre); } // Get pre-resize image width and height int inputWidth = image.width(); int inputHeight = image.height(); // Is there just one page? Shrink to inputHeight instead if (nPages == 1) { pageHeight = inputHeight; } // Scaling calculations double hshrink; double vshrink; int targetResizeWidth = baton->width; int targetResizeHeight = baton->height; // When auto-rotating by 90 or 270 degrees, swap the target width and // height to ensure the behavior aligns with how it would have been if // the rotation had taken place *before* resizing. if (autoRotation == VIPS_ANGLE_D90 || autoRotation == VIPS_ANGLE_D270) { std::swap(targetResizeWidth, targetResizeHeight); } // Shrink to pageHeight, so we work for multi-page images std::tie(hshrink, vshrink) = sharp::ResolveShrink( inputWidth, pageHeight, targetResizeWidth, targetResizeHeight, baton->canvas, baton->withoutEnlargement, baton->withoutReduction); // The jpeg preload shrink. int jpegShrinkOnLoad = 1; // WebP, PDF, SVG scale double scale = 1.0; // Try to reload input using shrink-on-load for JPEG, WebP, SVG and PDF, when: // - the width or height parameters are specified; // - gamma correction doesn't need to be applied; // - trimming or pre-resize extract isn't required; // - input colourspace is not specified; bool const shouldPreShrink = (targetResizeWidth > 0 || targetResizeHeight > 0) && baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold < 0.0 && baton->colourspacePipeline == VIPS_INTERPRETATION_LAST && !(shouldOrientBefore || shouldRotateBefore); if (shouldPreShrink) { // The common part of the shrink: the bit by which both axes must be shrunk double shrink = std::min(hshrink, vshrink); if (inputImageType == sharp::ImageType::JPEG) { // Leave at least a factor of two for the final resize step, when fastShrinkOnLoad: false // for more consistent results and to avoid extra sharpness to the image int factor = baton->fastShrinkOnLoad ? 1 : 2; if (shrink >= 8 * factor) { jpegShrinkOnLoad = 8; } else if (shrink >= 4 * factor) { jpegShrinkOnLoad = 4; } else if (shrink >= 2 * factor) { jpegShrinkOnLoad = 2; } // Lower shrink-on-load for known libjpeg rounding errors if (jpegShrinkOnLoad > 1 && static_cast
(shrink) == jpegShrinkOnLoad) { jpegShrinkOnLoad /= 2; } } else if (inputImageType == sharp::ImageType::WEBP && baton->fastShrinkOnLoad && shrink > 1.0) { // Avoid upscaling via webp scale = 1.0 / shrink; } else if (inputImageType == sharp::ImageType::SVG || inputImageType == sharp::ImageType::PDF) { scale = 1.0 / shrink; } } // Reload input using shrink-on-load, it'll be an integer shrink // factor for jpegload*, a double scale factor for webpload*, // pdfload* and svgload* if (jpegShrinkOnLoad > 1) { vips::VOption *option = GetOptionsForImageType(inputImageType, baton->input)->set("shrink", jpegShrinkOnLoad); if (baton->input->buffer != nullptr) { // Reload JPEG buffer VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength); image = VImage::jpegload_buffer(blob, option); vips_area_unref(reinterpret_cast
(blob)); } else { // Reload JPEG file image = VImage::jpegload(const_cast
(baton->input->file.data()), option); } } else if (scale != 1.0) { vips::VOption *option = GetOptionsForImageType(inputImageType, baton->input)->set("scale", scale); if (inputImageType == sharp::ImageType::WEBP) { if (baton->input->buffer != nullptr) { // Reload WebP buffer VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength); image = VImage::webpload_buffer(blob, option); vips_area_unref(reinterpret_cast
(blob)); } else { // Reload WebP file image = VImage::webpload(const_cast
(baton->input->file.data()), option); } } else if (inputImageType == sharp::ImageType::SVG) { if (baton->input->buffer != nullptr) { // Reload SVG buffer VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength); image = VImage::svgload_buffer(blob, option); vips_area_unref(reinterpret_cast
(blob)); } else { // Reload SVG file image = VImage::svgload(const_cast
(baton->input->file.data()), option); } 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"); } } else if (inputImageType == sharp::ImageType::PDF) { if (baton->input->buffer != nullptr) { // Reload PDF buffer VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength); image = VImage::pdfload_buffer(blob, option); vips_area_unref(reinterpret_cast
(blob)); } else { // Reload PDF file image = VImage::pdfload(const_cast
(baton->input->file.data()), option); } sharp::SetDensity(image, baton->input->density); } } else { if (inputImageType == sharp::ImageType::SVG && (image.width() > 32767 || image.height() > 32767)) { throw vips::VError("Input SVG image exceeds 32767x32767 pixel limit"); } } if (baton->input->autoOrient) { image = sharp::RemoveExifOrientation(image); } // Any pre-shrinking may already have been done inputWidth = image.width(); inputHeight = image.height(); // After pre-shrink, but before the main shrink stage // Reuse the initial pageHeight if we didn't pre-shrink if (shouldPreShrink) { pageHeight = sharp::GetPageHeight(image); } // Shrink to pageHeight, so we work for multi-page images std::tie(hshrink, vshrink) = sharp::ResolveShrink( inputWidth, pageHeight, targetResizeWidth, targetResizeHeight, baton->canvas, baton->withoutEnlargement, baton->withoutReduction); int targetHeight = static_cast
(std::rint(static_cast
(pageHeight) / vshrink)); int targetPageHeight = targetHeight; // In toilet-roll mode, we must adjust vshrink so that we exactly hit // pageHeight or we'll have pixels straddling pixel boundaries if (inputHeight > pageHeight) { targetHeight *= nPages; vshrink = static_cast
(inputHeight) / targetHeight; } // Ensure we're using a device-independent colour space std::pair
inputProfile(nullptr, 0); if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) && baton->withIccProfile.empty()) { // Cache input profile for use with output inputProfile = sharp::GetProfile(image); baton->input->ignoreIcc = true; } char const *processingProfile = image.interpretation() == VIPS_INTERPRETATION_RGB16 ? "p3" : "srgb"; if ( sharp::HasProfile(image) && image.interpretation() != VIPS_INTERPRETATION_LABS && image.interpretation() != VIPS_INTERPRETATION_GREY16 && baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK && !baton->input->ignoreIcc ) { // Convert to sRGB/P3 using embedded profile try { image = image.icc_transform(processingProfile, VImage::option() ->set("embedded", true) ->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8) ->set("intent", VIPS_INTENT_PERCEPTUAL)); } catch(...) { sharp::VipsWarningCallback(nullptr, G_LOG_LEVEL_WARNING, "Invalid embedded profile", nullptr); } } else if ( image.interpretation() == VIPS_INTERPRETATION_CMYK && baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK ) { image = image.icc_transform(processingProfile, VImage::option() ->set("input_profile", "cmyk") ->set("intent", VIPS_INTENT_PERCEPTUAL)); } // Flatten image to remove alpha channel if (baton->flatten && image.has_alpha()) { image = sharp::Flatten(image, baton->flattenBackground); } // Gamma encoding (darken) if (baton->gamma >= 1 && baton->gamma <= 3) { image = sharp::Gamma(image, 1.0 / baton->gamma); } // Convert to greyscale (linear, therefore after gamma encoding, if any) if (baton->greyscale) { image = image.colourspace(VIPS_INTERPRETATION_B_W); } bool const shouldResize = hshrink != 1.0 || vshrink != 1.0; bool const shouldBlur = baton->blurSigma != 0.0; bool const shouldConv = baton->convKernelWidth * baton->convKernelHeight > 0; bool const shouldSharpen = baton->sharpenSigma != 0.0; bool const shouldComposite = !baton->composite.empty(); if (shouldComposite && !image.has_alpha()) { image = sharp::EnsureAlpha(image, 1); } VipsBandFormat premultiplyFormat = image.format(); bool const shouldPremultiplyAlpha = image.has_alpha() && (shouldResize || shouldBlur || shouldConv || shouldSharpen); if (shouldPremultiplyAlpha) { image = image.premultiply().cast(premultiplyFormat); } // Resize if (shouldResize) { image = image.resize(1.0 / hshrink, VImage::option() ->set("vscale", 1.0 / vshrink) ->set("kernel", baton->kernel)); } image = sharp::StaySequential(image, autoRotation != VIPS_ANGLE_D0 || baton->flip || rotation != VIPS_ANGLE_D0); // Auto-rotate post-extract if (autoRotation != VIPS_ANGLE_D0) { if (autoRotation != VIPS_ANGLE_D180) { MultiPageUnsupported(nPages, "Rotate"); } image = image.rot(autoRotation); } // Mirror vertically (up-down) about the x-axis if (baton->flip) { image = image.flip(VIPS_DIRECTION_VERTICAL); } // Mirror horizontally (left-right) about the y-axis if (baton->flop != autoFlop) { image = image.flip(VIPS_DIRECTION_HORIZONTAL); } // Rotate post-extract 90-angle if (rotation != VIPS_ANGLE_D0) { if (rotation != VIPS_ANGLE_D180) { MultiPageUnsupported(nPages, "Rotate"); } image = image.rot(rotation); } // Join additional color channels to the image if (!baton->joinChannelIn.empty()) { VImage joinImage; sharp::ImageType joinImageType = sharp::ImageType::UNKNOWN; for (unsigned int i = 0; i < baton->joinChannelIn.size(); i++) { baton->joinChannelIn[i]->access = access; std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i]); joinImage = sharp::EnsureColourspace(joinImage, baton->colourspacePipeline); image = image.bandjoin(joinImage); } image = image.copy(VImage::option()->set("interpretation", baton->colourspace)); image = sharp::RemoveGifPalette(image); } inputWidth = image.width(); inputHeight = nPages > 1 ? targetPageHeight : image.height(); // Resolve dimensions if (baton->width <= 0) { baton->width = inputWidth; } if (baton->height <= 0) { baton->height = inputHeight; } // Crop/embed if (inputWidth != baton->width || inputHeight != baton->height) { if (baton->canvas == sharp::Canvas::EMBED) { std::vector
background; std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground, shouldPremultiplyAlpha); // Embed const auto& [left, top] = sharp::CalculateEmbedPosition( inputWidth, inputHeight, baton->width, baton->height, baton->position); const int width = std::max(inputWidth, baton->width); const int height = std::max(inputHeight, baton->height); image = nPages > 1 ? sharp::EmbedMultiPage(image, left, top, width, height, VIPS_EXTEND_BACKGROUND, background, nPages, &targetPageHeight) : image.embed(left, top, width, height, VImage::option() ->set("extend", VIPS_EXTEND_BACKGROUND) ->set("background", background)); } else if (baton->canvas == sharp::Canvas::CROP) { if (baton->width > inputWidth) { baton->width = inputWidth; } if (baton->height > inputHeight) { baton->height = inputHeight; } // Crop if (baton->position < 9) { // Gravity-based crop const auto& [left, top] = sharp::CalculateCrop( inputWidth, inputHeight, baton->width, baton->height, baton->position); const int width = std::min(inputWidth, baton->width); const int height = std::min(inputHeight, baton->height); image = nPages > 1 ? sharp::CropMultiPage(image, left, top, width, height, nPages, &targetPageHeight) : image.extract_area(left, top, width, height); } else { int attention_x; int attention_y; // Attention-based or Entropy-based crop MultiPageUnsupported(nPages, "Resize strategy"); image = sharp::StaySequential(image); image = image.smartcrop(baton->width, baton->height, VImage::option() ->set("interesting", baton->position == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION) ->set("premultiplied", shouldPremultiplyAlpha) ->set("attention_x", &attention_x) ->set("attention_y", &attention_y)); baton->hasCropOffset = true; baton->cropOffsetLeft = static_cast
(image.xoffset()); baton->cropOffsetTop = static_cast
(image.yoffset()); baton->hasAttentionCenter = true; baton->attentionX = static_cast
(attention_x * jpegShrinkOnLoad / scale); baton->attentionY = static_cast
(attention_y * jpegShrinkOnLoad / scale); } } } // Rotate post-extract non-90 angle if (!baton->rotateBefore && baton->rotationAngle != 0.0) { MultiPageUnsupported(nPages, "Rotate"); image = sharp::StaySequential(image); std::vector
background; std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, shouldPremultiplyAlpha); image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background)); } // Post extraction if (baton->topOffsetPost != -1) { if (nPages > 1) { image = sharp::CropMultiPage(image, baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost, nPages, &targetPageHeight); // heightPost is used in the info object, so update to reflect the number of pages baton->heightPost *= nPages; } else { image = image.extract_area( baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost); } } // Affine transform if (!baton->affineMatrix.empty()) { MultiPageUnsupported(nPages, "Affine"); image = sharp::StaySequential(image); std::vector
background; std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha); vips::VInterpolate interp = vips::VInterpolate::new_from_name( const_cast
(baton->affineInterpolator.data())); image = image.affine(baton->affineMatrix, VImage::option()->set("background", background) ->set("idx", baton->affineIdx) ->set("idy", baton->affineIdy) ->set("odx", baton->affineOdx) ->set("ody", baton->affineOdy) ->set("interpolate", interp)); } // Extend edges if (baton->extendTop > 0 || baton->extendBottom > 0 || baton->extendLeft > 0 || baton->extendRight > 0) { // Embed baton->width = image.width() + baton->extendLeft + baton->extendRight; baton->height = (nPages > 1 ? targetPageHeight : image.height()) + baton->extendTop + baton->extendBottom; if (baton->extendWith == VIPS_EXTEND_BACKGROUND) { std::vector
background; std::tie(image, background) = sharp::ApplyAlpha(image, baton->extendBackground, shouldPremultiplyAlpha); image = sharp::StaySequential(image, nPages > 1); image = nPages > 1 ? sharp::EmbedMultiPage(image, baton->extendLeft, baton->extendTop, baton->width, baton->height, baton->extendWith, background, nPages, &targetPageHeight) : image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height, VImage::option()->set("extend", baton->extendWith)->set("background", background)); } else { std::vector
ignoredBackground(1); image = sharp::StaySequential(image); image = nPages > 1 ? sharp::EmbedMultiPage(image, baton->extendLeft, baton->extendTop, baton->width, baton->height, baton->extendWith, ignoredBackground, nPages, &targetPageHeight) : image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height, VImage::option()->set("extend", baton->extendWith)); } } // Median - must happen before blurring, due to the utility of blurring after thresholding if (baton->medianSize > 0) { image = image.median(baton->medianSize); } // Threshold - must happen before blurring, due to the utility of blurring after thresholding // Threshold - must happen before unflatten to enable non-white unflattening if (baton->threshold != 0) { image = sharp::Threshold(image, baton->threshold, baton->thresholdGrayscale); } // Dilate - must happen before blurring, due to the utility of dilating after thresholding if (baton->dilateWidth != 0) { image = sharp::Dilate(image, baton->dilateWidth); } // Erode - must happen before blurring, due to the utility of eroding after thresholding if (baton->erodeWidth != 0) { image = sharp::Erode(image, baton->erodeWidth); } // Blur if (shouldBlur) { image = sharp::Blur(image, baton->blurSigma, baton->precision, baton->minAmpl); } // Unflatten the image if (baton->unflatten) { image = sharp::Unflatten(image); } // Convolve if (shouldConv) { image = sharp::Convolve(image, baton->convKernelWidth, baton->convKernelHeight, baton->convKernelScale, baton->convKernelOffset, baton->convKernel); } // Recomb if (!baton->recombMatrix.empty()) { image = sharp::Recomb(image, baton->recombMatrix); } // Modulate if (baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0 || baton->lightness != 0.0) { image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue, baton->lightness); } // Sharpen if (shouldSharpen) { image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenM1, baton->sharpenM2, baton->sharpenX1, baton->sharpenY2, baton->sharpenY3); } // Reverse premultiplication after all transformations if (shouldPremultiplyAlpha) { image = image.unpremultiply().cast(premultiplyFormat); } baton->premultiplied = shouldPremultiplyAlpha; // Composite if (shouldComposite) { std::vector
images = { image }; std::vector
modes, xs, ys; for (Composite *composite : baton->composite) { VImage compositeImage; sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN; composite->input->access = access; std::tie(compositeImage, compositeImageType) = sharp::OpenInput(composite->input); if (composite->input->autoOrient) { // Respect EXIF Orientation VipsAngle compositeAutoRotation = VIPS_ANGLE_D0; bool compositeAutoFlop = false; std::tie(compositeAutoRotation, compositeAutoFlop) = CalculateExifRotationAndFlop(sharp::ExifOrientation(compositeImage)); compositeImage = sharp::RemoveExifOrientation(compositeImage); compositeImage = sharp::StaySequential(compositeImage, compositeAutoRotation != VIPS_ANGLE_D0); if (compositeAutoRotation != VIPS_ANGLE_D0) { compositeImage = compositeImage.rot(compositeAutoRotation); } if (compositeAutoFlop) { compositeImage = compositeImage.flip(VIPS_DIRECTION_HORIZONTAL); } } // 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"); } // Check if overlay is tiled if (composite->tile) { int across = 0; int down = 0; // Use gravity in overlay if (compositeImage.width() <= image.width()) { across = static_cast
(ceil(static_cast
(image.width()) / compositeImage.width())); // Ensure odd number of tiles across when gravity is centre, north or south if (composite->gravity == 0 || composite->gravity == 1 || composite->gravity == 3) { across |= 1; } } if (compositeImage.height() <= image.height()) { down = static_cast
(ceil(static_cast
(image.height()) / compositeImage.height())); // Ensure odd number of tiles down when gravity is centre, east or west if (composite->gravity == 0 || composite->gravity == 2 || composite->gravity == 4) { down |= 1; } } if (across != 0 || down != 0) { int left; int top; compositeImage = sharp::StaySequential(compositeImage).replicate(across, down); if (composite->hasOffset) { std::tie(left, top) = sharp::CalculateCrop( compositeImage.width(), compositeImage.height(), image.width(), image.height(), composite->left, composite->top); } else { std::tie(left, top) = sharp::CalculateCrop( compositeImage.width(), compositeImage.height(), image.width(), image.height(), composite->gravity); } compositeImage = compositeImage.extract_area(left, top, image.width(), image.height()); } // gravity was used for extract_area, set it back to its default value of 0 composite->gravity = 0; } // Ensure image to composite is with unpremultiplied alpha compositeImage = sharp::EnsureAlpha(compositeImage, 1); if (composite->premultiplied) compositeImage = compositeImage.unpremultiply(); // Calculate position int left; int top; if (composite->hasOffset) { // Composite image at given offsets if (composite->tile) { std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(), compositeImage.width(), compositeImage.height(), composite->left, composite->top); } else { left = composite->left; top = composite->top; } } else { // Composite image with given gravity std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(), compositeImage.width(), compositeImage.height(), composite->gravity); } images.push_back(compositeImage); modes.push_back(composite->mode); xs.push_back(left); ys.push_back(top); } image = VImage::composite(images, modes, VImage::option() ->set("compositing_space", baton->colourspacePipeline == VIPS_INTERPRETATION_LAST ? VIPS_INTERPRETATION_sRGB : baton->colourspacePipeline) ->set("x", xs) ->set("y", ys)); image = sharp::RemoveGifPalette(image); } // Gamma decoding (brighten) if (baton->gammaOut >= 1 && baton->gammaOut <= 3) { image = sharp::Gamma(image, baton->gammaOut); } // Linear adjustment (a * in + b) if (!baton->linearA.empty()) { image = sharp::Linear(image, baton->linearA, baton->linearB); } // Apply normalisation - stretch luminance to cover full dynamic range if (baton->normalise) { image = sharp::StaySequential(image); image = sharp::Normalise(image, baton->normaliseLower, baton->normaliseUpper); } // Apply contrast limiting adaptive histogram equalization (CLAHE) if (baton->claheWidth != 0 && baton->claheHeight != 0) { image = sharp::StaySequential(image); image = sharp::Clahe(image, baton->claheWidth, baton->claheHeight, baton->claheMaxSlope); } // Apply bitwise boolean operation between images if (baton->boolean != nullptr) { VImage booleanImage; sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN; baton->boolean->access = access; std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean); booleanImage = sharp::EnsureColourspace(booleanImage, baton->colourspacePipeline); image = sharp::Boolean(image, booleanImage, baton->booleanOp); image = sharp::RemoveGifPalette(image); } // Apply per-channel Bandbool bitwise operations after all other operations if (baton->bandBoolOp >= VIPS_OPERATION_BOOLEAN_AND && baton->bandBoolOp < VIPS_OPERATION_BOOLEAN_LAST) { image = sharp::Bandbool(image, baton->bandBoolOp); } // Tint the image if (baton->tint[0] >= 0.0) { image = sharp::Tint(image, baton->tint); } // Remove alpha channel, if any if (baton->removeAlpha) { image = sharp::RemoveAlpha(image); } // Ensure alpha channel, if missing if (baton->ensureAlpha != -1) { image = sharp::EnsureAlpha(image, baton->ensureAlpha); } // Ensure output colour space if (sharp::Is16Bit(image.interpretation())) { image = image.cast(VIPS_FORMAT_USHORT); } if (image.interpretation() != baton->colourspace) { image = image.colourspace(baton->colourspace, VImage::option()->set("source_space", image.interpretation())); if (inputProfile.first != nullptr && baton->withIccProfile.empty()) { image = sharp::SetProfile(image, inputProfile); } } // Extract channel if (baton->extractChannel > -1) { if (baton->extractChannel >= image.bands()) { if (baton->extractChannel == 3 && image.has_alpha()) { baton->extractChannel = image.bands() - 1; } else { (baton->err) .append("Cannot extract channel ").append(std::to_string(baton->extractChannel)) .append(" from image with channels 0-").append(std::to_string(image.bands() - 1)); return Error(); } } VipsInterpretation colourspace = sharp::Is16Bit(image.interpretation()) ? VIPS_INTERPRETATION_GREY16 : VIPS_INTERPRETATION_B_W; image = image .extract_band(baton->extractChannel) .copy(VImage::option()->set("interpretation", colourspace)); } // Apply output ICC profile if (!baton->withIccProfile.empty()) { try { image = image.icc_transform(const_cast
(baton->withIccProfile.data()), VImage::option() ->set("input_profile", processingProfile) ->set("embedded", true) ->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8) ->set("intent", VIPS_INTENT_PERCEPTUAL)); } catch(...) { sharp::VipsWarningCallback(nullptr, G_LOG_LEVEL_WARNING, "Invalid profile", nullptr); } } // Negate the colours in the image if (baton->negate) { image = sharp::Negate(image, baton->negateAlpha); } // Override EXIF Orientation tag if (baton->withMetadataOrientation != -1) { image = sharp::SetExifOrientation(image, baton->withMetadataOrientation); } // Override pixel density if (baton->withMetadataDensity > 0) { image = sharp::SetDensity(image, baton->withMetadataDensity); } // EXIF key/value pairs if (baton->keepMetadata & VIPS_FOREIGN_KEEP_EXIF) { image = image.copy(); if (!baton->withExifMerge) { image = sharp::RemoveExif(image); } for (const auto& [key, value] : baton->withExif) { image.set(key.c_str(), value.c_str()); } } // XMP buffer if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_XMP) && !baton->withXmp.empty()) { image = image.copy(); image.set(VIPS_META_XMP_NAME, nullptr, const_cast
(static_cast
(baton->withXmp.c_str())), baton->withXmp.size()); } // Number of channels used in output image baton->channels = image.bands(); baton->width = image.width(); baton->height = image.height(); image = sharp::SetAnimationProperties( image, nPages, targetPageHeight, baton->delay, baton->loop); if (image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT) { baton->pageHeightOut = image.get_int(VIPS_META_PAGE_HEIGHT); baton->pagesOut = image.get_int(VIPS_META_N_PAGES); } // Output sharp::SetTimeout(image, baton->timeoutSeconds); if (baton->fileOut.empty()) { // Buffer output if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) { // Write JPEG to buffer sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG); VipsArea *area = reinterpret_cast
(image.jpegsave_buffer(VImage::option() ->set("keep", baton->keepMetadata) ->set("Q", baton->jpegQuality) ->set("interlace", baton->jpegProgressive) ->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4" ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON) ->set("trellis_quant", baton->jpegTrellisQuantisation) ->set("quant_table", baton->jpegQuantisationTable) ->set("overshoot_deringing", baton->jpegOvershootDeringing) ->set("optimize_scans", baton->jpegOptimiseScans) ->set("optimize_coding", baton->jpegOptimiseCoding))); baton->bufferOut = static_cast
(area->data); baton->bufferOutLength = area->length; area->free_fn = nullptr; vips_area_unref(area); baton->formatOut = "jpeg"; if (baton->colourspace == VIPS_INTERPRETATION_CMYK) { baton->channels = std::min(baton->channels, 4); } else { baton->channels = std::min(baton->channels, 3); } } else if (baton->formatOut == "jp2" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JP2)) { // Write JP2 to Buffer sharp::AssertImageTypeDimensions(image, sharp::ImageType::JP2); VipsArea *area = reinterpret_cast
(image.jp2ksave_buffer(VImage::option() ->set("Q", baton->jp2Quality) ->set("lossless", baton->jp2Lossless) ->set("subsample_mode", baton->jp2ChromaSubsampling == "4:4:4" ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON) ->set("tile_height", baton->jp2TileHeight) ->set("tile_width", baton->jp2TileWidth))); baton->bufferOut = static_cast
(area->data); baton->bufferOutLength = area->length; area->free_fn = nullptr; vips_area_unref(area); baton->formatOut = "jp2"; } else if (baton->formatOut == "png" || (baton->formatOut == "input" && (inputImageType == sharp::ImageType::PNG || inputImageType == sharp::ImageType::SVG))) { // Write PNG to buffer sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG); VipsArea *area = reinterpret_cast
(image.pngsave_buffer(VImage::option() ->set("keep", baton->keepMetadata) ->set("interlace", baton->pngProgressive) ->set("compression", baton->pngCompressionLevel) ->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE) ->set("palette", baton->pngPalette) ->set("Q", baton->pngQuality) ->set("effort", baton->pngEffort) ->set("bitdepth", sharp::Is16Bit(image.interpretation()) ? 16 : baton->pngBitdepth) ->set("dither", baton->pngDither))); baton->bufferOut = static_cast
(area->data); baton->bufferOutLength = area->length; area->free_fn = nullptr; vips_area_unref(area); baton->formatOut = "png"; } else if (baton->formatOut == "webp" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::WEBP)) { // Write WEBP to buffer sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP); VipsArea *area = reinterpret_cast
(image.webpsave_buffer(VImage::option() ->set("keep", baton->keepMetadata) ->set("Q", baton->webpQuality) ->set("lossless", baton->webpLossless) ->set("near_lossless", baton->webpNearLossless) ->set("smart_subsample", baton->webpSmartSubsample) ->set("smart_deblock", baton->webpSmartDeblock) ->set("preset", baton->webpPreset) ->set("effort", baton->webpEffort) ->set("min_size", baton->webpMinSize) ->set("mixed", baton->webpMixed) ->set("alpha_q", baton->webpAlphaQuality))); baton->bufferOut = static_cast
(area->data); baton->bufferOutLength = area->length; area->free_fn = nullptr; vips_area_unref(area); baton->formatOut = "webp"; } else if (baton->formatOut == "gif" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::GIF)) { // Write GIF to buffer sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF); VipsArea *area = reinterpret_cast
(image.gifsave_buffer(VImage::option() ->set("keep", baton->keepMetadata) ->set("bitdepth", baton->gifBitdepth) ->set("effort", baton->gifEffort) ->set("reuse", baton->gifReuse) ->set("interlace", baton->gifProgressive) ->set("interframe_maxerror", baton->gifInterFrameMaxError) ->set("interpalette_maxerror", baton->gifInterPaletteMaxError) ->set("keep_duplicate_frames", baton->gifKeepDuplicateFrames) ->set("dither", baton->gifDither))); baton->bufferOut = static_cast
(area->data); baton->bufferOutLength = area->length; area->free_fn = nullptr; vips_area_unref(area); baton->formatOut = "gif"; } else if (baton->formatOut == "tiff" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::TIFF)) { // Write TIFF to buffer if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) { sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG); baton->channels = std::min(baton->channels, 3); } // Cast pixel values to float, if required if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) { image = image.cast(VIPS_FORMAT_FLOAT); } VipsArea *area = reinterpret_cast
(image.tiffsave_buffer(VImage::option() ->set("keep", baton->keepMetadata) ->set("Q", baton->tiffQuality) ->set("bitdepth", baton->tiffBitdepth) ->set("compression", baton->tiffCompression) ->set("bigtiff", baton->tiffBigtiff) ->set("miniswhite", baton->tiffMiniswhite) ->set("predictor", baton->tiffPredictor) ->set("pyramid", baton->tiffPyramid) ->set("tile", baton->tiffTile) ->set("tile_height", baton->tiffTileHeight) ->set("tile_width", baton->tiffTileWidth) ->set("xres", baton->tiffXres) ->set("yres", baton->tiffYres) ->set("resunit", baton->tiffResolutionUnit))); baton->bufferOut = static_cast
(area->data); baton->bufferOutLength = area->length; area->free_fn = nullptr; vips_area_unref(area); baton->formatOut = "tiff"; } else if (baton->formatOut == "heif" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) { // Write HEIF to buffer sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF); image = sharp::RemoveAnimationProperties(image); VipsArea *area = reinterpret_cast
(image.heifsave_buffer(VImage::option() ->set("keep", baton->keepMetadata) ->set("Q", baton->heifQuality) ->set("compression", baton->heifCompression) ->set("effort", baton->heifEffort) ->set("bitdepth", baton->heifBitdepth) ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4" ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON) ->set("lossless", baton->heifLossless))); baton->bufferOut = static_cast
(area->data); baton->bufferOutLength = area->length; area->free_fn = nullptr; vips_area_unref(area); baton->formatOut = "heif"; } else if (baton->formatOut == "dz") { // Write DZ to buffer baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP; if (!image.has_alpha()) { baton->tileBackground.pop_back(); } image = sharp::StaySequential(image, baton->tileAngle != 0); vips::VOption *options = BuildOptionsDZ(baton); VipsArea *area = reinterpret_cast
(image.dzsave_buffer(options)); baton->bufferOut = static_cast
(area->data); baton->bufferOutLength = area->length; area->free_fn = nullptr; vips_area_unref(area); baton->formatOut = "dz"; } else if (baton->formatOut == "jxl" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JXL)) { // Write JXL to buffer image = sharp::RemoveAnimationProperties(image); VipsArea *area = reinterpret_cast
(image.jxlsave_buffer(VImage::option() ->set("keep", baton->keepMetadata) ->set("distance", baton->jxlDistance) ->set("tier", baton->jxlDecodingTier) ->set("effort", baton->jxlEffort) ->set("lossless", baton->jxlLossless))); baton->bufferOut = static_cast
(area->data); baton->bufferOutLength = area->length; area->free_fn = nullptr; vips_area_unref(area); baton->formatOut = "jxl"; } else if (baton->formatOut == "raw" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::RAW)) { // Write raw, uncompressed image data to buffer if (baton->greyscale || image.interpretation() == VIPS_INTERPRETATION_B_W) { // Extract first band for greyscale image image = image[0]; baton->channels = 1; } if (image.format() != baton->rawDepth) { // Cast pixels to requested format image = image.cast(baton->rawDepth); } // 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(); } baton->formatOut = "raw"; } else { // Unsupported output format (baton->err).append("Unsupported output format "); if (baton->formatOut == "input") { (baton->err).append("when trying to match input format of "); (baton->err).append(ImageTypeId(inputImageType)); } else { (baton->err).append(baton->formatOut); } return Error(); } } else { // File output bool const isJpeg = sharp::IsJpeg(baton->fileOut); bool const isPng = sharp::IsPng(baton->fileOut); bool const isWebp = sharp::IsWebp(baton->fileOut); bool const isGif = sharp::IsGif(baton->fileOut); bool const isTiff = sharp::IsTiff(baton->fileOut); bool const isJp2 = sharp::IsJp2(baton->fileOut); bool const isHeif = sharp::IsHeif(baton->fileOut); bool const isJxl = sharp::IsJxl(baton->fileOut); bool const isDz = sharp::IsDz(baton->fileOut); bool const isDzZip = sharp::IsDzZip(baton->fileOut); bool const isV = sharp::IsV(baton->fileOut); bool const mightMatchInput = baton->formatOut == "input"; bool const willMatchInput = mightMatchInput && !(isJpeg || isPng || isWebp || isGif || isTiff || isJp2 || isHeif || isDz || isDzZip || isV); if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) || (willMatchInput && inputImageType == sharp::ImageType::JPEG)) { // Write JPEG to file sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG); image.jpegsave(const_cast
(baton->fileOut.data()), VImage::option() ->set("keep", baton->keepMetadata) ->set("Q", baton->jpegQuality) ->set("interlace", baton->jpegProgressive) ->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4" ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON) ->set("trellis_quant", baton->jpegTrellisQuantisation) ->set("quant_table", baton->jpegQuantisationTable) ->set("overshoot_deringing", baton->jpegOvershootDeringing) ->set("optimize_scans", baton->jpegOptimiseScans) ->set("optimize_coding", baton->jpegOptimiseCoding)); baton->formatOut = "jpeg"; baton->channels = std::min(baton->channels, 3); } else if (baton->formatOut == "jp2" || (mightMatchInput && isJp2) || (willMatchInput && (inputImageType == sharp::ImageType::JP2))) { // Write JP2 to file sharp::AssertImageTypeDimensions(image, sharp::ImageType::JP2); image.jp2ksave(const_cast
(baton->fileOut.data()), VImage::option() ->set("Q", baton->jp2Quality) ->set("lossless", baton->jp2Lossless) ->set("subsample_mode", baton->jp2ChromaSubsampling == "4:4:4" ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON) ->set("tile_height", baton->jp2TileHeight) ->set("tile_width", baton->jp2TileWidth)); baton->formatOut = "jp2"; } else if (baton->formatOut == "png" || (mightMatchInput && isPng) || (willMatchInput && (inputImageType == sharp::ImageType::PNG || inputImageType == sharp::ImageType::SVG))) { // Write PNG to file sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG); image.pngsave(const_cast
(baton->fileOut.data()), VImage::option() ->set("keep", baton->keepMetadata) ->set("interlace", baton->pngProgressive) ->set("compression", baton->pngCompressionLevel) ->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE) ->set("palette", baton->pngPalette) ->set("Q", baton->pngQuality) ->set("bitdepth", sharp::Is16Bit(image.interpretation()) ? 16 : baton->pngBitdepth) ->set("effort", baton->pngEffort) ->set("dither", baton->pngDither)); baton->formatOut = "png"; } else if (baton->formatOut == "webp" || (mightMatchInput && isWebp) || (willMatchInput && inputImageType == sharp::ImageType::WEBP)) { // Write WEBP to file sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP); image.webpsave(const_cast
(baton->fileOut.data()), VImage::option() ->set("keep", baton->keepMetadata) ->set("Q", baton->webpQuality) ->set("lossless", baton->webpLossless) ->set("near_lossless", baton->webpNearLossless) ->set("smart_subsample", baton->webpSmartSubsample) ->set("smart_deblock", baton->webpSmartDeblock) ->set("preset", baton->webpPreset) ->set("effort", baton->webpEffort) ->set("min_size", baton->webpMinSize) ->set("mixed", baton->webpMixed) ->set("alpha_q", baton->webpAlphaQuality)); baton->formatOut = "webp"; } else if (baton->formatOut == "gif" || (mightMatchInput && isGif) || (willMatchInput && inputImageType == sharp::ImageType::GIF)) { // Write GIF to file sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF); image.gifsave(const_cast
(baton->fileOut.data()), VImage::option() ->set("keep", baton->keepMetadata) ->set("bitdepth", baton->gifBitdepth) ->set("effort", baton->gifEffort) ->set("reuse", baton->gifReuse) ->set("interlace", baton->gifProgressive) ->set("interframe_maxerror", baton->gifInterFrameMaxError) ->set("interpalette_maxerror", baton->gifInterPaletteMaxError) ->set("keep_duplicate_frames", baton->gifKeepDuplicateFrames) ->set("dither", baton->gifDither)); baton->formatOut = "gif"; } else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) || (willMatchInput && inputImageType == sharp::ImageType::TIFF)) { // Write TIFF to file if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) { sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG); baton->channels = std::min(baton->channels, 3); } // Cast pixel values to float, if required if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) { image = image.cast(VIPS_FORMAT_FLOAT); } image.tiffsave(const_cast
(baton->fileOut.data()), VImage::option() ->set("keep", baton->keepMetadata) ->set("Q", baton->tiffQuality) ->set("bitdepth", baton->tiffBitdepth) ->set("compression", baton->tiffCompression) ->set("bigtiff", baton->tiffBigtiff) ->set("miniswhite", baton->tiffMiniswhite) ->set("predictor", baton->tiffPredictor) ->set("pyramid", baton->tiffPyramid) ->set("tile", baton->tiffTile) ->set("tile_height", baton->tiffTileHeight) ->set("tile_width", baton->tiffTileWidth) ->set("xres", baton->tiffXres) ->set("yres", baton->tiffYres) ->set("resunit", baton->tiffResolutionUnit)); baton->formatOut = "tiff"; } else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) || (willMatchInput && inputImageType == sharp::ImageType::HEIF)) { // Write HEIF to file sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF); image = sharp::RemoveAnimationProperties(image); image.heifsave(const_cast
(baton->fileOut.data()), VImage::option() ->set("keep", baton->keepMetadata) ->set("Q", baton->heifQuality) ->set("compression", baton->heifCompression) ->set("effort", baton->heifEffort) ->set("bitdepth", baton->heifBitdepth) ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4" ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON) ->set("lossless", baton->heifLossless)); baton->formatOut = "heif"; } else if (baton->formatOut == "jxl" || (mightMatchInput && isJxl) || (willMatchInput && inputImageType == sharp::ImageType::JXL)) { // Write JXL to file image = sharp::RemoveAnimationProperties(image); image.jxlsave(const_cast
(baton->fileOut.data()), VImage::option() ->set("keep", baton->keepMetadata) ->set("distance", baton->jxlDistance) ->set("tier", baton->jxlDecodingTier) ->set("effort", baton->jxlEffort) ->set("lossless", baton->jxlLossless)); baton->formatOut = "jxl"; } else if (baton->formatOut == "dz" || isDz || isDzZip) { // Write DZ to file if (isDzZip) { baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP; } if (!image.has_alpha()) { baton->tileBackground.pop_back(); } image = sharp::StaySequential(image, baton->tileAngle != 0); vips::VOption *options = BuildOptionsDZ(baton); image.dzsave(const_cast
(baton->fileOut.data()), options); baton->formatOut = "dz"; } else if (baton->formatOut == "v" || (mightMatchInput && isV) || (willMatchInput && inputImageType == sharp::ImageType::VIPS)) { // Write V to file image.vipssave(const_cast
(baton->fileOut.data()), VImage::option() ->set("keep", baton->keepMetadata)); baton->formatOut = "v"; } else { // Unsupported output format (baton->err).append("Unsupported output format " + baton->fileOut); return Error(); } } } catch (vips::VError 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"); } } } // Clean up libvips' per-request data and threads vips_error_clear(); vips_thread_shutdown(); } void OnOK() { 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(); } if (baton->err.empty()) { int width = baton->width; int height = baton->height; if (baton->topOffsetPre != -1 && (baton->width == -1 || baton->height == -1)) { width = baton->widthPre; height = baton->heightPre; } if (baton->topOffsetPost != -1) { width = baton->widthPost; height = baton->heightPost; } // Info Object Napi::Object info = Napi::Object::New(env); info.Set("format", baton->formatOut); info.Set("width", static_cast
(width)); info.Set("height", static_cast
(height)); info.Set("channels", static_cast
(baton->channels)); if (baton->formatOut == "raw") { info.Set("depth", vips_enum_nick(VIPS_TYPE_BAND_FORMAT, baton->rawDepth)); } info.Set("premultiplied", baton->premultiplied); if (baton->hasCropOffset) { info.Set("cropOffsetLeft", static_cast
(baton->cropOffsetLeft)); info.Set("cropOffsetTop", static_cast
(baton->cropOffsetTop)); } if (baton->hasAttentionCenter) { info.Set("attentionX", static_cast
(baton->attentionX)); info.Set("attentionY", static_cast
(baton->attentionY)); } if (baton->trimThreshold >= 0.0) { info.Set("trimOffsetLeft", static_cast
(baton->trimOffsetLeft)); info.Set("trimOffsetTop", static_cast
(baton->trimOffsetTop)); } if (baton->input->textAutofitDpi) { info.Set("textAutofitDpi", static_cast
(baton->input->textAutofitDpi)); } if (baton->pageHeightOut) { info.Set("pageHeight", static_cast
(baton->pageHeightOut)); info.Set("pages", static_cast
(baton->pagesOut)); } if (baton->bufferOutLength > 0) { // Add buffer size to info info.Set("size", static_cast
(baton->bufferOutLength)); // Pass ownership of output data to Buffer instance Napi::Buffer
data = Napi::Buffer
::NewOrCopy(env, static_cast
(baton->bufferOut), baton->bufferOutLength, sharp::FreeCallback); Callback().Call(Receiver().Value(), { env.Null(), data, info }); } else { // Add file size to info if (baton->formatOut != "dz" || sharp::IsDzZip(baton->fileOut)) { try { uint32_t const size = static_cast
( std::filesystem::file_size(std::filesystem::u8path(baton->fileOut))); info.Set("size", size); } catch (...) {} } Callback().Call(Receiver().Value(), { env.Null(), info }); } } else { Callback().Call(Receiver().Value(), { Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() }); } // Delete baton delete baton->input; delete baton->boolean; for (Composite *composite : baton->composite) { delete composite->input; delete composite; } for (sharp::InputDescriptor *input : baton->joinChannelIn) { delete input; } for (sharp::InputDescriptor *input : baton->join) { delete input; } delete baton; // Decrement processing task counter sharp::counterProcess--; Napi::Number queueLength = Napi::Number::New(env, static_cast
(sharp::counterQueue)); queueListener.Call(Receiver().Value(), { queueLength }); } private: PipelineBaton *baton; Napi::FunctionReference debuglog; Napi::FunctionReference queueListener; void MultiPageUnsupported(int const pages, std::string op) { if (pages > 1) { throw vips::VError(op + " is not supported for multi-page images"); } } /* Calculate the angle of rotation and need-to-flip for the given Exif orientation By default, returns zero, i.e. no rotation. */ std::tuple
CalculateExifRotationAndFlop(int const exifOrientation) { VipsAngle rotate = VIPS_ANGLE_D0; bool flop = false; switch (exifOrientation) { case 6: rotate = VIPS_ANGLE_D90; break; case 3: rotate = VIPS_ANGLE_D180; break; case 8: rotate = VIPS_ANGLE_D270; break; case 2: flop = true; break; case 7: flop = true; rotate = VIPS_ANGLE_D270; break; case 4: flop = true; rotate = VIPS_ANGLE_D180; break; case 5: flop = true; rotate = VIPS_ANGLE_D90; break; } return std::make_tuple(rotate, flop); } /* Calculate the rotation for the given angle. Supports any positive or negative angle that is a multiple of 90. */ VipsAngle CalculateAngleRotation(int angle) { angle = angle % 360; if (angle < 0) angle = 360 + angle; switch (angle) { case 90: return VIPS_ANGLE_D90; case 180: return VIPS_ANGLE_D180; case 270: return VIPS_ANGLE_D270; } return VIPS_ANGLE_D0; } /* Assemble the suffix argument to dzsave, which is the format (by extname) alongside comma-separated arguments to the corresponding `formatsave` vips action. */ std::string AssembleSuffixString(std::string extname, std::vector
> options) { std::string argument; for (const auto& [key, value] : options) { if (!argument.empty()) { argument += ","; } argument += key + "=" + value; } return extname + "[" + argument + "]"; } /* Build VOption for dzsave */ vips::VOption* BuildOptionsDZ(PipelineBaton *baton) { // Forward format options through suffix std::string suffix; if (baton->tileFormat == "png") { std::vector
> options { {"interlace", baton->pngProgressive ? "true" : "false"}, {"compression", std::to_string(baton->pngCompressionLevel)}, {"filter", baton->pngAdaptiveFiltering ? "all" : "none"} }; suffix = AssembleSuffixString(".png", options); } else if (baton->tileFormat == "webp") { std::vector
> options { {"Q", std::to_string(baton->webpQuality)}, {"alpha_q", std::to_string(baton->webpAlphaQuality)}, {"lossless", baton->webpLossless ? "true" : "false"}, {"near_lossless", baton->webpNearLossless ? "true" : "false"}, {"smart_subsample", baton->webpSmartSubsample ? "true" : "false"}, {"smart_deblock", baton->webpSmartDeblock ? "true" : "false"}, {"preset", vips_enum_nick(VIPS_TYPE_FOREIGN_WEBP_PRESET, baton->webpPreset)}, {"min_size", baton->webpMinSize ? "true" : "false"}, {"mixed", baton->webpMixed ? "true" : "false"}, {"effort", std::to_string(baton->webpEffort)} }; suffix = AssembleSuffixString(".webp", options); } else { std::vector
> options { {"Q", std::to_string(baton->jpegQuality)}, {"interlace", baton->jpegProgressive ? "true" : "false"}, {"subsample_mode", baton->jpegChromaSubsampling == "4:4:4" ? "off" : "on"}, {"trellis_quant", baton->jpegTrellisQuantisation ? "true" : "false"}, {"quant_table", std::to_string(baton->jpegQuantisationTable)}, {"overshoot_deringing", baton->jpegOvershootDeringing ? "true": "false"}, {"optimize_scans", baton->jpegOptimiseScans ? "true": "false"}, {"optimize_coding", baton->jpegOptimiseCoding ? "true": "false"} }; std::string extname = baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_DZ ? ".jpeg" : ".jpg"; suffix = AssembleSuffixString(extname, options); } vips::VOption *options = VImage::option() ->set("keep", baton->keepMetadata) ->set("tile_size", baton->tileSize) ->set("overlap", baton->tileOverlap) ->set("container", baton->tileContainer) ->set("layout", baton->tileLayout) ->set("suffix", const_cast
(suffix.data())) ->set("angle", CalculateAngleRotation(baton->tileAngle)) ->set("background", baton->tileBackground) ->set("centre", baton->tileCentre) ->set("id", const_cast
(baton->tileId.data())) ->set("skip_blanks", baton->tileSkipBlanks); if (baton->tileDepth < VIPS_FOREIGN_DZ_DEPTH_LAST) { options->set("depth", baton->tileDepth); } if (!baton->tileBasename.empty()) { options->set("basename", const_cast
(baton->tileBasename.data())); } return options; } /* Clear all thread-local data. */ void Error() { // Clean up libvips' per-request data and threads vips_error_clear(); vips_thread_shutdown(); } }; /* pipeline(options, output, callback) */ Napi::Value pipeline(const Napi::CallbackInfo& info) { // V8 objects are converted to non-V8 types held in the baton struct PipelineBaton *baton = new PipelineBaton; Napi::Object options = info[size_t(0)].As
(); // Input baton->input = sharp::CreateInputDescriptor(options.Get("input").As
()); // Join images together if (sharp::HasAttr(options, "join")) { Napi::Array join = options.Get("join").As
(); for (unsigned int i = 0; i < join.Length(); i++) { baton->join.push_back( sharp::CreateInputDescriptor(join.Get(i).As
())); } } // Extract image options baton->topOffsetPre = sharp::AttrAsInt32(options, "topOffsetPre"); baton->leftOffsetPre = sharp::AttrAsInt32(options, "leftOffsetPre"); baton->widthPre = sharp::AttrAsInt32(options, "widthPre"); baton->heightPre = sharp::AttrAsInt32(options, "heightPre"); baton->topOffsetPost = sharp::AttrAsInt32(options, "topOffsetPost"); baton->leftOffsetPost = sharp::AttrAsInt32(options, "leftOffsetPost"); baton->widthPost = sharp::AttrAsInt32(options, "widthPost"); baton->heightPost = sharp::AttrAsInt32(options, "heightPost"); // Output image dimensions baton->width = sharp::AttrAsInt32(options, "width"); baton->height = sharp::AttrAsInt32(options, "height"); // Canvas option std::string canvas = sharp::AttrAsStr(options, "canvas"); if (canvas == "crop") { baton->canvas = sharp::Canvas::CROP; } else if (canvas == "embed") { baton->canvas = sharp::Canvas::EMBED; } else if (canvas == "max") { baton->canvas = sharp::Canvas::MAX; } else if (canvas == "min") { baton->canvas = sharp::Canvas::MIN; } else if (canvas == "ignore_aspect") { baton->canvas = sharp::Canvas::IGNORE_ASPECT; } // Composite Napi::Array compositeArray = options.Get("composite").As
(); for (unsigned int i = 0; i < compositeArray.Length(); i++) { Napi::Object compositeObject = compositeArray.Get(i).As
(); Composite *composite = new Composite; composite->input = sharp::CreateInputDescriptor(compositeObject.Get("input").As
()); composite->mode = sharp::AttrAsEnum
(compositeObject, "blend", VIPS_TYPE_BLEND_MODE); composite->gravity = sharp::AttrAsUint32(compositeObject, "gravity"); composite->left = sharp::AttrAsInt32(compositeObject, "left"); composite->top = sharp::AttrAsInt32(compositeObject, "top"); composite->hasOffset = sharp::AttrAsBool(compositeObject, "hasOffset"); composite->tile = sharp::AttrAsBool(compositeObject, "tile"); composite->premultiplied = sharp::AttrAsBool(compositeObject, "premultiplied"); baton->composite.push_back(composite); } // Resize options baton->withoutEnlargement = sharp::AttrAsBool(options, "withoutEnlargement"); baton->withoutReduction = sharp::AttrAsBool(options, "withoutReduction"); baton->position = sharp::AttrAsInt32(options, "position"); baton->resizeBackground = sharp::AttrAsVectorOfDouble(options, "resizeBackground"); baton->kernel = sharp::AttrAsEnum
(options, "kernel", VIPS_TYPE_KERNEL); baton->fastShrinkOnLoad = sharp::AttrAsBool(options, "fastShrinkOnLoad"); // Join Channel Options if (options.Has("joinChannelIn")) { Napi::Array joinChannelArray = options.Get("joinChannelIn").As
(); for (unsigned int i = 0; i < joinChannelArray.Length(); i++) { baton->joinChannelIn.push_back( sharp::CreateInputDescriptor(joinChannelArray.Get(i).As
())); } } // Operators baton->flatten = sharp::AttrAsBool(options, "flatten"); baton->flattenBackground = sharp::AttrAsVectorOfDouble(options, "flattenBackground"); baton->unflatten = sharp::AttrAsBool(options, "unflatten"); baton->negate = sharp::AttrAsBool(options, "negate"); baton->negateAlpha = sharp::AttrAsBool(options, "negateAlpha"); baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma"); baton->precision = sharp::AttrAsEnum
(options, "precision", VIPS_TYPE_PRECISION); baton->minAmpl = sharp::AttrAsDouble(options, "minAmpl"); baton->brightness = sharp::AttrAsDouble(options, "brightness"); baton->saturation = sharp::AttrAsDouble(options, "saturation"); baton->hue = sharp::AttrAsInt32(options, "hue"); baton->lightness = sharp::AttrAsDouble(options, "lightness"); baton->medianSize = sharp::AttrAsUint32(options, "medianSize"); baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma"); baton->sharpenM1 = sharp::AttrAsDouble(options, "sharpenM1"); baton->sharpenM2 = sharp::AttrAsDouble(options, "sharpenM2"); baton->sharpenX1 = sharp::AttrAsDouble(options, "sharpenX1"); baton->sharpenY2 = sharp::AttrAsDouble(options, "sharpenY2"); baton->sharpenY3 = sharp::AttrAsDouble(options, "sharpenY3"); baton->threshold = sharp::AttrAsInt32(options, "threshold"); baton->thresholdGrayscale = sharp::AttrAsBool(options, "thresholdGrayscale"); baton->trimBackground = sharp::AttrAsVectorOfDouble(options, "trimBackground"); baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold"); baton->trimLineArt = sharp::AttrAsBool(options, "trimLineArt"); baton->gamma = sharp::AttrAsDouble(options, "gamma"); baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut"); baton->linearA = sharp::AttrAsVectorOfDouble(options, "linearA"); baton->linearB = sharp::AttrAsVectorOfDouble(options, "linearB"); baton->dilateWidth = sharp::AttrAsUint32(options, "dilateWidth"); baton->erodeWidth = sharp::AttrAsUint32(options, "erodeWidth"); baton->greyscale = sharp::AttrAsBool(options, "greyscale"); baton->normalise = sharp::AttrAsBool(options, "normalise"); baton->normaliseLower = sharp::AttrAsUint32(options, "normaliseLower"); baton->normaliseUpper = sharp::AttrAsUint32(options, "normaliseUpper"); baton->tint = sharp::AttrAsVectorOfDouble(options, "tint"); baton->claheWidth = sharp::AttrAsUint32(options, "claheWidth"); baton->claheHeight = sharp::AttrAsUint32(options, "claheHeight"); baton->claheMaxSlope = sharp::AttrAsUint32(options, "claheMaxSlope"); baton->angle = sharp::AttrAsInt32(options, "angle"); baton->rotationAngle = sharp::AttrAsDouble(options, "rotationAngle"); baton->rotationBackground = sharp::AttrAsVectorOfDouble(options, "rotationBackground"); baton->rotateBefore = sharp::AttrAsBool(options, "rotateBefore"); baton->orientBefore = sharp::AttrAsBool(options, "orientBefore"); baton->flip = sharp::AttrAsBool(options, "flip"); baton->flop = sharp::AttrAsBool(options, "flop"); baton->extendTop = sharp::AttrAsInt32(options, "extendTop"); baton->extendBottom = sharp::AttrAsInt32(options, "extendBottom"); baton->extendLeft = sharp::AttrAsInt32(options, "extendLeft"); baton->extendRight = sharp::AttrAsInt32(options, "extendRight"); baton->extendBackground = sharp::AttrAsVectorOfDouble(options, "extendBackground"); baton->extendWith = sharp::AttrAsEnum
(options, "extendWith", VIPS_TYPE_EXTEND); baton->extractChannel = sharp::AttrAsInt32(options, "extractChannel"); baton->affineMatrix = sharp::AttrAsVectorOfDouble(options, "affineMatrix"); baton->affineBackground = sharp::AttrAsVectorOfDouble(options, "affineBackground"); baton->affineIdx = sharp::AttrAsDouble(options, "affineIdx"); baton->affineIdy = sharp::AttrAsDouble(options, "affineIdy"); baton->affineOdx = sharp::AttrAsDouble(options, "affineOdx"); baton->affineOdy = sharp::AttrAsDouble(options, "affineOdy"); baton->affineInterpolator = sharp::AttrAsStr(options, "affineInterpolator"); baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha"); baton->ensureAlpha = sharp::AttrAsDouble(options, "ensureAlpha"); if (options.Has("boolean")) { baton->boolean = sharp::CreateInputDescriptor(options.Get("boolean").As
()); baton->booleanOp = sharp::AttrAsEnum
(options, "booleanOp", VIPS_TYPE_OPERATION_BOOLEAN); } if (options.Has("bandBoolOp")) { baton->bandBoolOp = sharp::AttrAsEnum
(options, "bandBoolOp", VIPS_TYPE_OPERATION_BOOLEAN); } if (options.Has("convKernel")) { Napi::Object kernel = options.Get("convKernel").As
(); baton->convKernelWidth = sharp::AttrAsUint32(kernel, "width"); baton->convKernelHeight = sharp::AttrAsUint32(kernel, "height"); baton->convKernelScale = sharp::AttrAsDouble(kernel, "scale"); baton->convKernelOffset = sharp::AttrAsDouble(kernel, "offset"); size_t const kernelSize = static_cast
(baton->convKernelWidth * baton->convKernelHeight); baton->convKernel.resize(kernelSize); Napi::Array kdata = kernel.Get("kernel").As
(); for (unsigned int i = 0; i < kernelSize; i++) { baton->convKernel[i] = sharp::AttrAsDouble(kdata, i); } } if (options.Has("recombMatrix")) { Napi::Array recombMatrix = options.Get("recombMatrix").As
(); unsigned int matrixElements = recombMatrix.Length(); baton->recombMatrix.resize(matrixElements); for (unsigned int i = 0; i < matrixElements; i++) { baton->recombMatrix[i] = sharp::AttrAsDouble(recombMatrix, i); } } baton->colourspacePipeline = sharp::AttrAsEnum
( options, "colourspacePipeline", VIPS_TYPE_INTERPRETATION); if (baton->colourspacePipeline == VIPS_INTERPRETATION_ERROR) { baton->colourspacePipeline = VIPS_INTERPRETATION_LAST; } baton->colourspace = sharp::AttrAsEnum
(options, "colourspace", VIPS_TYPE_INTERPRETATION); if (baton->colourspace == VIPS_INTERPRETATION_ERROR) { baton->colourspace = VIPS_INTERPRETATION_sRGB; } // Output baton->formatOut = sharp::AttrAsStr(options, "formatOut"); baton->fileOut = sharp::AttrAsStr(options, "fileOut"); baton->keepMetadata = sharp::AttrAsUint32(options, "keepMetadata"); baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation"); baton->withMetadataDensity = sharp::AttrAsDouble(options, "withMetadataDensity"); baton->withIccProfile = sharp::AttrAsStr(options, "withIccProfile"); Napi::Object withExif = options.Get("withExif").As
(); Napi::Array withExifKeys = withExif.GetPropertyNames(); for (unsigned int i = 0; i < withExifKeys.Length(); i++) { std::string k = sharp::AttrAsStr(withExifKeys, i); if (withExif.HasOwnProperty(k)) { baton->withExif.insert(std::make_pair(k, sharp::AttrAsStr(withExif, k))); } } baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge"); baton->withXmp = sharp::AttrAsStr(options, "withXmp"); baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds"); baton->loop = sharp::AttrAsUint32(options, "loop"); baton->delay = sharp::AttrAsInt32Vector(options, "delay"); // Format-specific baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality"); baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive"); baton->jpegChromaSubsampling = sharp::AttrAsStr(options, "jpegChromaSubsampling"); baton->jpegTrellisQuantisation = sharp::AttrAsBool(options, "jpegTrellisQuantisation"); baton->jpegQuantisationTable = sharp::AttrAsUint32(options, "jpegQuantisationTable"); baton->jpegOvershootDeringing = sharp::AttrAsBool(options, "jpegOvershootDeringing"); baton->jpegOptimiseScans = sharp::AttrAsBool(options, "jpegOptimiseScans"); baton->jpegOptimiseCoding = sharp::AttrAsBool(options, "jpegOptimiseCoding"); baton->pngProgressive = sharp::AttrAsBool(options, "pngProgressive"); baton->pngCompressionLevel = sharp::AttrAsUint32(options, "pngCompressionLevel"); baton->pngAdaptiveFiltering = sharp::AttrAsBool(options, "pngAdaptiveFiltering"); baton->pngPalette = sharp::AttrAsBool(options, "pngPalette"); baton->pngQuality = sharp::AttrAsUint32(options, "pngQuality"); baton->pngEffort = sharp::AttrAsUint32(options, "pngEffort"); baton->pngBitdepth = sharp::AttrAsUint32(options, "pngBitdepth"); baton->pngDither = sharp::AttrAsDouble(options, "pngDither"); baton->jp2Quality = sharp::AttrAsUint32(options, "jp2Quality"); baton->jp2Lossless = sharp::AttrAsBool(options, "jp2Lossless"); baton->jp2TileHeight = sharp::AttrAsUint32(options, "jp2TileHeight"); baton->jp2TileWidth = sharp::AttrAsUint32(options, "jp2TileWidth"); baton->jp2ChromaSubsampling = sharp::AttrAsStr(options, "jp2ChromaSubsampling"); baton->webpQuality = sharp::AttrAsUint32(options, "webpQuality"); baton->webpAlphaQuality = sharp::AttrAsUint32(options, "webpAlphaQuality"); baton->webpLossless = sharp::AttrAsBool(options, "webpLossless"); baton->webpNearLossless = sharp::AttrAsBool(options, "webpNearLossless"); baton->webpSmartSubsample = sharp::AttrAsBool(options, "webpSmartSubsample"); baton->webpSmartDeblock = sharp::AttrAsBool(options, "webpSmartDeblock"); baton->webpPreset = sharp::AttrAsEnum
(options, "webpPreset", VIPS_TYPE_FOREIGN_WEBP_PRESET); baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort"); baton->webpMinSize = sharp::AttrAsBool(options, "webpMinSize"); baton->webpMixed = sharp::AttrAsBool(options, "webpMixed"); baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth"); baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort"); baton->gifDither = sharp::AttrAsDouble(options, "gifDither"); baton->gifInterFrameMaxError = sharp::AttrAsDouble(options, "gifInterFrameMaxError"); baton->gifInterPaletteMaxError = sharp::AttrAsDouble(options, "gifInterPaletteMaxError"); baton->gifKeepDuplicateFrames = sharp::AttrAsBool(options, "gifKeepDuplicateFrames"); baton->gifReuse = sharp::AttrAsBool(options, "gifReuse"); baton->gifProgressive = sharp::AttrAsBool(options, "gifProgressive"); baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality"); baton->tiffBigtiff = sharp::AttrAsBool(options, "tiffBigtiff"); baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid"); baton->tiffMiniswhite = sharp::AttrAsBool(options, "tiffMiniswhite"); baton->tiffBitdepth = sharp::AttrAsUint32(options, "tiffBitdepth"); baton->tiffTile = sharp::AttrAsBool(options, "tiffTile"); baton->tiffTileWidth = sharp::AttrAsUint32(options, "tiffTileWidth"); baton->tiffTileHeight = sharp::AttrAsUint32(options, "tiffTileHeight"); baton->tiffXres = sharp::AttrAsDouble(options, "tiffXres"); baton->tiffYres = sharp::AttrAsDouble(options, "tiffYres"); if (baton->tiffXres == 1.0 && baton->tiffYres == 1.0 && baton->withMetadataDensity > 0) { baton->tiffXres = baton->tiffYres = baton->withMetadataDensity / 25.4; } baton->tiffCompression = sharp::AttrAsEnum
( options, "tiffCompression", VIPS_TYPE_FOREIGN_TIFF_COMPRESSION); baton->tiffPredictor = sharp::AttrAsEnum
( options, "tiffPredictor", VIPS_TYPE_FOREIGN_TIFF_PREDICTOR); baton->tiffResolutionUnit = sharp::AttrAsEnum
( options, "tiffResolutionUnit", VIPS_TYPE_FOREIGN_TIFF_RESUNIT); baton->heifQuality = sharp::AttrAsUint32(options, "heifQuality"); baton->heifLossless = sharp::AttrAsBool(options, "heifLossless"); baton->heifCompression = sharp::AttrAsEnum
( options, "heifCompression", VIPS_TYPE_FOREIGN_HEIF_COMPRESSION); baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort"); baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling"); baton->heifBitdepth = sharp::AttrAsUint32(options, "heifBitdepth"); baton->jxlDistance = sharp::AttrAsDouble(options, "jxlDistance"); baton->jxlDecodingTier = sharp::AttrAsUint32(options, "jxlDecodingTier"); baton->jxlEffort = sharp::AttrAsUint32(options, "jxlEffort"); baton->jxlLossless = sharp::AttrAsBool(options, "jxlLossless"); baton->rawDepth = sharp::AttrAsEnum
(options, "rawDepth", VIPS_TYPE_BAND_FORMAT); baton->tileSize = sharp::AttrAsUint32(options, "tileSize"); baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap"); baton->tileAngle = sharp::AttrAsInt32(options, "tileAngle"); baton->tileBackground = sharp::AttrAsVectorOfDouble(options, "tileBackground"); baton->tileSkipBlanks = sharp::AttrAsInt32(options, "tileSkipBlanks"); baton->tileContainer = sharp::AttrAsEnum
( options, "tileContainer", VIPS_TYPE_FOREIGN_DZ_CONTAINER); baton->tileLayout = sharp::AttrAsEnum
(options, "tileLayout", VIPS_TYPE_FOREIGN_DZ_LAYOUT); baton->tileFormat = sharp::AttrAsStr(options, "tileFormat"); baton->tileDepth = sharp::AttrAsEnum
(options, "tileDepth", VIPS_TYPE_FOREIGN_DZ_DEPTH); baton->tileCentre = sharp::AttrAsBool(options, "tileCentre"); baton->tileId = sharp::AttrAsStr(options, "tileId"); baton->tileBasename = sharp::AttrAsStr(options, "tileBasename"); // Function to notify of libvips warnings Napi::Function debuglog = options.Get("debuglog").As
(); // Function to notify of queue length changes Napi::Function queueListener = options.Get("queueListener").As
(); // Join queue for worker thread Napi::Function callback = info[size_t(1)].As
(); PipelineWorker *worker = new PipelineWorker(callback, baton, debuglog, queueListener); worker->Receiver().Set("options", options); worker->Queue(); // Increment queued task counter Napi::Number queueLength = Napi::Number::New(info.Env(), static_cast
(++sharp::counterQueue)); queueListener.Call(info.This(), { queueLength }); return info.Env().Undefined(); }