Brunobkr/llama.cpp_AlgMor24_github
ΩFFFΣLLIa • llama.cpp • AlgMor24 ██████╗ ███████╗███████╗███████╗██╗ ██╗ ██╗ █████╗ ██╔═══██╗██╔════╝██╔════╝██╔════╝██║ ██║ ██║██╔══██╗ ██║ ██║█████╗ █████╗ █████╗ ██║ ██║ ██║███████║ ██║ ██║██╔══╝ ██╔══╝ ██╔══╝ ██║ ██║ ██║██╔══██║ ╚██████╔╝██║ ██║ ███████╗███████╗███████╗██║██║ ██║ ╚═════╝ ╚═╝ ╚═╝ ╚══════╝╚══════╝╚══════╝╚═╝╚═╝ ╚═╝ High-Performance LLM / VLM Inference & Autonomous Agentic Ecosystem… See the full description on the dataset page: https://huggingface.co/datasets/Brunobkr/llama.cpp_AlgMor24_github.
03.1k
1/*!2 Copyright 2013 Lovell Fuller and others.3 SPDX-License-Identifier: Apache-2.04*/5 6#include <algorithm>7#include <cmath>8#include <filesystem> // NOLINT(build/c++17)9#include <map>10#include <memory>11#include <numeric>12#include <string>13#include <tuple>14#include <utility>15#include <vector>16#include <sys/types.h>17#include <sys/stat.h>18 19#include <vips/vips8>20#include <napi.h>21 22#include "./common.h"23#include "./operations.h"24#include "./pipeline.h"25 26class PipelineWorker : public Napi::AsyncWorker {27 public:28 PipelineWorker(Napi::Function callback, PipelineBaton *baton,29 Napi::Function debuglog, Napi::Function queueListener) :30 Napi::AsyncWorker(callback),31 baton(baton),32 debuglog(Napi::Persistent(debuglog)),33 queueListener(Napi::Persistent(queueListener)) {}34 ~PipelineWorker() {}35 36 // libuv worker37 void Execute() {38 // Decrement queued task counter39 sharp::counterQueue--;40 // Increment processing task counter41 sharp::counterProcess++;42 43 try {44 // Open input45 vips::VImage image;46 sharp::ImageType inputImageType;47 if (baton->join.empty()) {48 std::tie(image, inputImageType) = sharp::OpenInput(baton->input);49 } else {50 std::vector<VImage> images;51 bool hasAlpha = false;52 for (auto &join : baton->join) {53 std::tie(image, inputImageType) = sharp::OpenInput(join);54 image = sharp::EnsureColourspace(image, baton->colourspacePipeline);55 images.push_back(image);56 hasAlpha |= image.has_alpha();57 }58 if (hasAlpha) {59 for (auto &image : images) {60 if (!image.has_alpha()) {61 image = sharp::EnsureAlpha(image, 1);62 }63 }64 } else {65 baton->input->joinBackground.pop_back();66 }67 inputImageType = sharp::ImageType::PNG;68 image = VImage::arrayjoin(images, VImage::option()69 ->set("across", baton->input->joinAcross)70 ->set("shim", baton->input->joinShim)71 ->set("background", baton->input->joinBackground)72 ->set("halign", baton->input->joinHalign)73 ->set("valign", baton->input->joinValign));74 if (baton->input->joinAnimated) {75 image = image.copy();76 image.set(VIPS_META_N_PAGES, static_cast<int>(images.size()));77 image.set(VIPS_META_PAGE_HEIGHT, static_cast<int>(image.height() / images.size()));78 }79 }80 VipsAccess access = baton->input->access;81 image = sharp::EnsureColourspace(image, baton->colourspacePipeline);82 83 int nPages = baton->input->pages;84 if (nPages == -1) {85 // Resolve the number of pages if we need to render until the end of the document86 nPages = image.get_typeof(VIPS_META_N_PAGES) != 087 ? image.get_int(VIPS_META_N_PAGES) - std::max(0, baton->input->page)88 : 1;89 }90 91 // Get pre-resize page height92 int pageHeight = sharp::GetPageHeight(image);93 94 // Calculate angle of rotation95 VipsAngle rotation = VIPS_ANGLE_D0;96 VipsAngle autoRotation = VIPS_ANGLE_D0;97 bool autoFlop = false;98 99 if (baton->input->autoOrient) {100 // Rotate and flip image according to Exif orientation101 std::tie(autoRotation, autoFlop) = CalculateExifRotationAndFlop(sharp::ExifOrientation(image));102 }103 104 rotation = CalculateAngleRotation(baton->angle);105 106 bool const shouldRotateBefore = baton->rotateBefore &&107 (rotation != VIPS_ANGLE_D0 || baton->flip || baton->flop || baton->rotationAngle != 0.0);108 bool const shouldOrientBefore = (shouldRotateBefore || baton->orientBefore) &&109 (autoRotation != VIPS_ANGLE_D0 || autoFlop);110 111 if (shouldOrientBefore) {112 image = sharp::StaySequential(image, autoRotation != VIPS_ANGLE_D0);113 if (autoRotation != VIPS_ANGLE_D0) {114 if (autoRotation != VIPS_ANGLE_D180) {115 MultiPageUnsupported(nPages, "Rotate");116 }117 image = image.rot(autoRotation);118 autoRotation = VIPS_ANGLE_D0;119 }120 if (autoFlop) {121 image = image.flip(VIPS_DIRECTION_HORIZONTAL);122 autoFlop = false;123 }124 }125 126 if (shouldRotateBefore) {127 image = sharp::StaySequential(image, rotation != VIPS_ANGLE_D0 || baton->flip || baton->rotationAngle != 0.0);128 if (baton->flip) {129 image = image.flip(VIPS_DIRECTION_VERTICAL);130 baton->flip = false;131 }132 if (baton->flop) {133 image = image.flip(VIPS_DIRECTION_HORIZONTAL);134 baton->flop = false;135 }136 if (rotation != VIPS_ANGLE_D0) {137 if (rotation != VIPS_ANGLE_D180) {138 MultiPageUnsupported(nPages, "Rotate");139 }140 image = image.rot(rotation);141 rotation = VIPS_ANGLE_D0;142 }143 if (baton->rotationAngle != 0.0) {144 MultiPageUnsupported(nPages, "Rotate");145 std::vector<double> background;146 std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, false);147 image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background)).copy_memory();148 baton->rotationAngle = 0.0;149 }150 }151 152 // Trim153 if (baton->trimThreshold >= 0.0) {154 MultiPageUnsupported(nPages, "Trim");155 image = sharp::StaySequential(image);156 image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold, baton->trimLineArt, baton->trimMargin);157 baton->trimOffsetLeft = image.xoffset();158 baton->trimOffsetTop = image.yoffset();159 }160 161 // Pre extraction162 if (baton->topOffsetPre != -1) {163 image = nPages > 1164 ? sharp::CropMultiPage(image,165 baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre, nPages, &pageHeight)166 : image.extract_area(baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre);167 }168 169 // Get pre-resize image width and height170 int inputWidth = image.width();171 int inputHeight = image.height();172 173 // Is there just one page? Shrink to inputHeight instead174 if (nPages == 1) {175 pageHeight = inputHeight;176 }177 178 // Scaling calculations179 double hshrink;180 double vshrink;181 int targetResizeWidth = baton->width;182 int targetResizeHeight = baton->height;183 184 // When auto-rotating by 90 or 270 degrees, swap the target width and185 // height to ensure the behavior aligns with how it would have been if186 // the rotation had taken place *before* resizing.187 if (autoRotation == VIPS_ANGLE_D90 || autoRotation == VIPS_ANGLE_D270) {188 std::swap(targetResizeWidth, targetResizeHeight);189 }190 191 // Shrink to pageHeight, so we work for multi-page images192 std::tie(hshrink, vshrink) = sharp::ResolveShrink(193 inputWidth, pageHeight, targetResizeWidth, targetResizeHeight,194 baton->canvas, baton->withoutEnlargement, baton->withoutReduction);195 196 // The jpeg preload shrink.197 int jpegShrinkOnLoad = 1;198 199 // WebP, PDF, SVG scale200 double scale = 1.0;201 202 // Try to reload input using shrink-on-load for JPEG, WebP, SVG and PDF, when:203 // - the width or height parameters are specified;204 // - gamma correction doesn't need to be applied;205 // - trimming or pre-resize extract isn't required;206 // - gain map processing is not required;207 // - input colourspace is not specified;208 bool const shouldPreShrink = (targetResizeWidth > 0 || targetResizeHeight > 0) &&209 baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold < 0.0 &&210 !baton->keepGainMap && !baton->withGainMap &&211 baton->colourspacePipeline == VIPS_INTERPRETATION_LAST && !(shouldOrientBefore || shouldRotateBefore);212 213 if (shouldPreShrink) {214 // The common part of the shrink: the bit by which both axes must be shrunk215 double shrink = std::min(hshrink, vshrink);216 217 if (inputImageType == sharp::ImageType::JPEG) {218 // Leave at least a factor of two for the final resize step, when fastShrinkOnLoad: false219 // for more consistent results and to avoid extra sharpness to the image220 int factor = baton->fastShrinkOnLoad ? 1 : 2;221 if (shrink >= 8 * factor) {222 jpegShrinkOnLoad = 8;223 } else if (shrink >= 4 * factor) {224 jpegShrinkOnLoad = 4;225 } else if (shrink >= 2 * factor) {226 jpegShrinkOnLoad = 2;227 }228 // Lower shrink-on-load for known libjpeg rounding errors229 if (jpegShrinkOnLoad > 1 && static_cast<int>(shrink) == jpegShrinkOnLoad) {230 jpegShrinkOnLoad /= 2;231 }232 } else if (inputImageType == sharp::ImageType::WEBP && baton->fastShrinkOnLoad && shrink > 1.0) {233 // Avoid upscaling via webp234 scale = 1.0 / shrink;235 } else if (inputImageType == sharp::ImageType::SVG ||236 inputImageType == sharp::ImageType::PDF) {237 scale = 1.0 / shrink;238 }239 }240 241 // Reload input using shrink-on-load, it'll be an integer shrink242 // factor for jpegload*, a double scale factor for webpload*,243 // pdfload* and svgload*244 if (jpegShrinkOnLoad > 1) {245 vips::VOption *option = GetOptionsForImageType(inputImageType, baton->input)->set("shrink", jpegShrinkOnLoad);246 if (baton->input->buffer != nullptr) {247 // Reload JPEG buffer248 VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);249 image = VImage::jpegload_buffer(blob, option);250 vips_area_unref(reinterpret_cast<VipsArea*>(blob));251 } else {252 // Reload JPEG file253 image = VImage::jpegload(const_cast<char*>(baton->input->file.data()), option);254 }255 } else if (scale != 1.0) {256 vips::VOption *option = GetOptionsForImageType(inputImageType, baton->input)->set("scale", scale);257 if (inputImageType == sharp::ImageType::WEBP) {258 if (baton->input->buffer != nullptr) {259 // Reload WebP buffer260 VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);261 image = VImage::webpload_buffer(blob, option);262 vips_area_unref(reinterpret_cast<VipsArea*>(blob));263 } else {264 // Reload WebP file265 image = VImage::webpload(const_cast<char*>(baton->input->file.data()), option);266 }267 } else if (inputImageType == sharp::ImageType::SVG) {268 if (baton->input->buffer != nullptr) {269 // Reload SVG buffer270 VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);271 image = VImage::svgload_buffer(blob, option);272 vips_area_unref(reinterpret_cast<VipsArea*>(blob));273 } else {274 // Reload SVG file275 image = VImage::svgload(const_cast<char*>(baton->input->file.data()), option);276 }277 sharp::SetDensity(image, baton->input->density);278 if (image.width() > 32767 || image.height() > 32767) {279 throw std::runtime_error("Input SVG image will exceed 32767x32767 pixel limit when scaled");280 }281 } else if (inputImageType == sharp::ImageType::PDF) {282 if (baton->input->buffer != nullptr) {283 // Reload PDF buffer284 VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);285 image = VImage::pdfload_buffer(blob, option);286 vips_area_unref(reinterpret_cast<VipsArea*>(blob));287 } else {288 // Reload PDF file289 image = VImage::pdfload(const_cast<char*>(baton->input->file.data()), option);290 }291 sharp::SetDensity(image, baton->input->density);292 }293 } else {294 if (inputImageType == sharp::ImageType::SVG && (image.width() > 32767 || image.height() > 32767)) {295 throw std::runtime_error("Input SVG image exceeds 32767x32767 pixel limit");296 }297 }298 if (baton->input->autoOrient) {299 image = sharp::RemoveExifOrientation(image);300 }301 VImage gainMap;302 int gainMapScaleFactor = 1;303 if (sharp::HasGainMap(image)) {304 if (baton->keepGainMap) {305 gainMap = image.gainmap();306 if (image.get_typeof("gainmap-scale-factor") == G_TYPE_INT) {307 gainMapScaleFactor = image.get_int("gainmap-scale-factor");308 }309 } else if (baton->withGainMap) {310 image = image.uhdr2scRGB();311 }312 image = sharp::RemoveGainMap(image);313 } else {314 baton->keepGainMap = false;315 baton->withGainMap = false;316 }317 318 // Any pre-shrinking may already have been done319 inputWidth = image.width();320 inputHeight = image.height();321 322 // After pre-shrink, but before the main shrink stage323 // Reuse the initial pageHeight if we didn't pre-shrink324 if (shouldPreShrink) {325 pageHeight = sharp::GetPageHeight(image);326 }327 328 // Shrink to pageHeight, so we work for multi-page images329 std::tie(hshrink, vshrink) = sharp::ResolveShrink(330 inputWidth, pageHeight, targetResizeWidth, targetResizeHeight,331 baton->canvas, baton->withoutEnlargement, baton->withoutReduction);332 333 int targetHeight = static_cast<int>(std::rint(static_cast<double>(pageHeight) / vshrink));334 int targetPageHeight = targetHeight;335 336 // In toilet-roll mode, we must adjust vshrink so that we exactly hit337 // pageHeight or we'll have pixels straddling pixel boundaries338 if (inputHeight > pageHeight) {339 targetHeight *= nPages;340 vshrink = static_cast<double>(inputHeight) / targetHeight;341 }342 343 // Ensure we're using a device-independent colour space344 std::pair<char*, size_t> inputProfile(nullptr, 0);345 if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) && baton->withIccProfile.empty()) {346 // Cache input profile for use with output347 inputProfile = sharp::GetProfile(image);348 baton->input->ignoreIcc = true;349 }350 char const *processingProfile = image.interpretation() == VIPS_INTERPRETATION_RGB16 ? "p3" : "srgb";351 if (352 sharp::HasProfile(image) &&353 image.interpretation() != VIPS_INTERPRETATION_LABS &&354 image.interpretation() != VIPS_INTERPRETATION_GREY16 &&355 baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK &&356 !baton->input->ignoreIcc && !baton->withGainMap357 ) {358 // Convert to sRGB/P3 using embedded profile359 try {360 image = image.icc_transform(processingProfile, VImage::option()361 ->set("embedded", true)362 ->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8)363 ->set("intent", VIPS_INTENT_PERCEPTUAL));364 } catch(...) {365 sharp::VipsWarningCallback(nullptr, G_LOG_LEVEL_WARNING, "Invalid embedded profile", nullptr);366 }367 } else if (368 image.interpretation() == VIPS_INTERPRETATION_CMYK &&369 baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK370 ) {371 image = image.icc_transform(processingProfile, VImage::option()372 ->set("input_profile", "cmyk")373 ->set("intent", VIPS_INTENT_PERCEPTUAL));374 }375 376 // Flatten image to remove alpha channel377 if (baton->flatten && image.has_alpha()) {378 image = sharp::Flatten(image, baton->flattenBackground);379 }380 381 // Gamma encoding (darken)382 if (baton->gamma >= 1 && baton->gamma <= 3) {383 image = sharp::Gamma(image, 1.0 / baton->gamma);384 }385 386 // Convert to greyscale (linear, therefore after gamma encoding, if any)387 if (baton->greyscale) {388 image = image.colourspace(VIPS_INTERPRETATION_B_W);389 }390 391 bool const shouldResize = hshrink != 1.0 || vshrink != 1.0;392 bool const shouldBlur = baton->blurSigma != 0.0;393 bool const shouldConv = baton->convKernelWidth * baton->convKernelHeight > 0;394 bool const shouldSharpen = baton->sharpenSigma != 0.0;395 bool const shouldComposite = !baton->composite.empty();396 397 if (shouldComposite && !image.has_alpha()) {398 image = sharp::EnsureAlpha(image, 1);399 }400 401 VipsBandFormat premultiplyFormat = image.format();402 bool const shouldPremultiplyAlpha = image.has_alpha() &&403 (shouldResize || shouldBlur || shouldConv || shouldSharpen);404 405 if (shouldPremultiplyAlpha) {406 image = image.premultiply().cast(premultiplyFormat);407 }408 409 // Resize410 if (shouldResize) {411 image = image.resize(1.0 / hshrink, VImage::option()412 ->set("vscale", 1.0 / vshrink)413 ->set("kernel", baton->kernel));414 if (baton->keepGainMap) {415 gainMap = gainMap.resize(1.0 / hshrink, VImage::option()416 ->set("vscale", 1.0 / vshrink)417 ->set("kernel", baton->kernel));418 }419 }420 421 image = sharp::StaySequential(image,422 autoRotation != VIPS_ANGLE_D0 ||423 baton->flip ||424 rotation != VIPS_ANGLE_D0);425 // Auto-rotate post-extract426 if (autoRotation != VIPS_ANGLE_D0) {427 if (autoRotation != VIPS_ANGLE_D180) {428 MultiPageUnsupported(nPages, "Rotate");429 }430 image = image.rot(autoRotation);431 if (baton->keepGainMap) {432 gainMap = gainMap.rot(autoRotation);433 }434 }435 // Mirror vertically (up-down) about the x-axis436 if (baton->flip) {437 image = image.flip(VIPS_DIRECTION_VERTICAL);438 if (baton->keepGainMap) {439 gainMap = gainMap.flip(VIPS_DIRECTION_VERTICAL);440 }441 }442 // Mirror horizontally (left-right) about the y-axis443 if (baton->flop != autoFlop) {444 image = image.flip(VIPS_DIRECTION_HORIZONTAL);445 if (baton->keepGainMap) {446 gainMap = gainMap.flip(VIPS_DIRECTION_HORIZONTAL);447 }448 }449 // Rotate post-extract 90-angle450 if (rotation != VIPS_ANGLE_D0) {451 if (rotation != VIPS_ANGLE_D180) {452 MultiPageUnsupported(nPages, "Rotate");453 }454 image = image.rot(rotation);455 if (baton->keepGainMap) {456 gainMap = gainMap.rot(rotation);457 }458 }459 460 // Join additional color channels to the image461 if (!baton->joinChannelIn.empty()) {462 VImage joinImage;463 sharp::ImageType joinImageType = sharp::ImageType::UNKNOWN;464 465 for (unsigned int i = 0; i < baton->joinChannelIn.size(); i++) {466 baton->joinChannelIn[i]->access = access;467 std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i]);468 joinImage = sharp::EnsureColourspace(joinImage, baton->colourspacePipeline);469 image = image.bandjoin(joinImage);470 }471 image = image.copy(VImage::option()->set("interpretation", baton->colourspace));472 image = sharp::RemoveGifPalette(image);473 }474 475 inputWidth = image.width();476 inputHeight = nPages > 1 ? targetPageHeight : image.height();477 478 // Resolve dimensions479 if (baton->width <= 0) {480 baton->width = inputWidth;481 }482 if (baton->height <= 0) {483 baton->height = inputHeight;484 }485 486 // Crop/embed487 if (inputWidth != baton->width || inputHeight != baton->height) {488 if (baton->canvas == sharp::Canvas::EMBED) {489 std::vector<double> background;490 std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground, shouldPremultiplyAlpha);491 492 // Embed493 const auto& [left, top] = sharp::CalculateEmbedPosition(494 inputWidth, inputHeight, baton->width, baton->height, baton->position);495 const int width = std::max(inputWidth, baton->width);496 const int height = std::max(inputHeight, baton->height);497 498 image = nPages > 1499 ? sharp::EmbedMultiPage(image,500 left, top, width, height, VIPS_EXTEND_BACKGROUND, background, nPages, &targetPageHeight)501 : image.embed(left, top, width, height, VImage::option()502 ->set("extend", VIPS_EXTEND_BACKGROUND)503 ->set("background", background));504 if (baton->keepGainMap) {505 gainMap = gainMap.embed(left / gainMapScaleFactor, top / gainMapScaleFactor,506 width / gainMapScaleFactor, height / gainMapScaleFactor, VImage::option()507 ->set("extend", VIPS_EXTEND_BACKGROUND)508 ->set("background", 0));509 }510 } else if (baton->canvas == sharp::Canvas::CROP) {511 if (baton->width > inputWidth) {512 baton->width = inputWidth;513 }514 if (baton->height > inputHeight) {515 baton->height = inputHeight;516 }517 518 // Crop519 if (baton->position < 9) {520 // Gravity-based crop521 const auto& [left, top] = sharp::CalculateCrop(522 inputWidth, inputHeight, baton->width, baton->height, baton->position);523 const int width = std::min(inputWidth, baton->width);524 const int height = std::min(inputHeight, baton->height);525 526 image = nPages > 1527 ? sharp::CropMultiPage(image,528 left, top, width, height, nPages, &targetPageHeight)529 : image.extract_area(left, top, width, height);530 if (baton->keepGainMap) {531 gainMap = gainMap.extract_area(left / gainMapScaleFactor, top / gainMapScaleFactor,532 width / gainMapScaleFactor, height / gainMapScaleFactor);533 }534 } else {535 int attention_x;536 int attention_y;537 538 // Attention-based or Entropy-based crop539 MultiPageUnsupported(nPages, "Resize strategy");540 KeepGainMapUnsupported(baton->keepGainMap, "Resize strategy");541 image = sharp::StaySequential(image);542 image = image.smartcrop(baton->width, baton->height, VImage::option()543 ->set("interesting", baton->position == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION)544 ->set("premultiplied", shouldPremultiplyAlpha)545 ->set("attention_x", &attention_x)546 ->set("attention_y", &attention_y));547 baton->hasCropOffset = true;548 baton->cropOffsetLeft = static_cast<int>(image.xoffset());549 baton->cropOffsetTop = static_cast<int>(image.yoffset());550 baton->hasAttentionCenter = true;551 baton->attentionX = static_cast<int>(attention_x * jpegShrinkOnLoad / scale);552 baton->attentionY = static_cast<int>(attention_y * jpegShrinkOnLoad / scale);553 }554 }555 }556 557 // Rotate post-extract non-90 angle558 if (!baton->rotateBefore && baton->rotationAngle != 0.0) {559 MultiPageUnsupported(nPages, "Rotate");560 image = sharp::StaySequential(image);561 std::vector<double> background;562 std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, shouldPremultiplyAlpha);563 image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));564 if (baton->keepGainMap) {565 gainMap = gainMap.rotate(baton->rotationAngle, VImage::option()->set("background", 0));566 }567 }568 569 // Post extraction570 if (baton->topOffsetPost != -1) {571 if (nPages > 1) {572 image = sharp::CropMultiPage(image,573 baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost,574 nPages, &targetPageHeight);575 // heightPost is used in the info object, so update to reflect the number of pages576 baton->heightPost *= nPages;577 } else {578 image = image.extract_area(579 baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost);580 if (baton->keepGainMap) {581 gainMap = gainMap.extract_area(baton->leftOffsetPost / gainMapScaleFactor,582 baton->topOffsetPost / gainMapScaleFactor, baton->widthPost / gainMapScaleFactor,583 baton->heightPost / gainMapScaleFactor);584 }585 }586 }587 588 // Affine transform589 if (!baton->affineMatrix.empty()) {590 MultiPageUnsupported(nPages, "Affine");591 KeepGainMapUnsupported(baton->keepGainMap, "Affine");592 image = sharp::StaySequential(image);593 std::vector<double> background;594 std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha);595 vips::VInterpolate interp = vips::VInterpolate::new_from_name(596 const_cast<char*>(baton->affineInterpolator.data()));597 image = image.affine(baton->affineMatrix, VImage::option()->set("background", background)598 ->set("idx", baton->affineIdx)599 ->set("idy", baton->affineIdy)600 ->set("odx", baton->affineOdx)601 ->set("ody", baton->affineOdy)602 ->set("interpolate", interp));603 }604 605 // Extend edges606 if (baton->extendTop > 0 || baton->extendBottom > 0 || baton->extendLeft > 0 || baton->extendRight > 0) {607 // Embed608 baton->width = image.width() + baton->extendLeft + baton->extendRight;609 baton->height = (nPages > 1 ? targetPageHeight : image.height()) + baton->extendTop + baton->extendBottom;610 611 std::vector<double> background;612 if (baton->extendWith == VIPS_EXTEND_BACKGROUND) {613 std::tie(image, background) = sharp::ApplyAlpha(image, baton->extendBackground, shouldPremultiplyAlpha);614 }615 image = sharp::StaySequential(image, nPages > 1 || baton->extendWith != VIPS_EXTEND_BACKGROUND);616 auto options = VImage::option()->set("extend", baton->extendWith);617 if (baton->extendWith == VIPS_EXTEND_BACKGROUND) {618 options->set("background", background);619 }620 image = nPages > 1621 ? sharp::EmbedMultiPage(image,622 baton->extendLeft, baton->extendTop, baton->width, baton->height,623 baton->extendWith, background, nPages, &targetPageHeight)624 : image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height, options);625 if (baton->keepGainMap) {626 gainMap = gainMap.embed(baton->extendLeft / gainMapScaleFactor, baton->extendTop / gainMapScaleFactor,627 baton->width / gainMapScaleFactor, baton->height / gainMapScaleFactor, VImage::option()628 ->set("extend", baton->extendWith)629 ->set("background", 0));630 }631 }632 // Median - must happen before blurring, due to the utility of blurring after thresholding633 if (baton->medianSize > 0) {634 image = image.median(baton->medianSize);635 }636 637 // Threshold - must happen before blurring, due to the utility of blurring after thresholding638 // Threshold - must happen before unflatten to enable non-white unflattening639 if (baton->threshold != 0) {640 image = sharp::Threshold(image, baton->threshold, baton->thresholdGrayscale);641 }642 643 // Dilate - must happen before blurring, due to the utility of dilating after thresholding644 if (baton->dilateWidth != 0) {645 image = sharp::Dilate(image, baton->dilateWidth);646 }647 648 // Erode - must happen before blurring, due to the utility of eroding after thresholding649 if (baton->erodeWidth != 0) {650 image = sharp::Erode(image, baton->erodeWidth);651 }652 653 // Blur654 if (shouldBlur) {655 image = sharp::Blur(image, baton->blurSigma, baton->precision, baton->minAmpl);656 if (baton->keepGainMap) {657 gainMap = sharp::Blur(gainMap, baton->blurSigma, baton->precision, baton->minAmpl);658 }659 }660 661 // Unflatten the image662 if (baton->unflatten) {663 image = sharp::Unflatten(image);664 }665 666 // Convolve667 if (shouldConv) {668 KeepGainMapUnsupported(baton->keepGainMap, "Convolve");669 image = sharp::Convolve(image,670 baton->convKernelWidth, baton->convKernelHeight,671 baton->convKernelScale, baton->convKernelOffset,672 baton->convKernel);673 }674 675 // Recomb676 if (!baton->recombMatrix.empty()) {677 KeepGainMapUnsupported(baton->keepGainMap, "Recomb");678 image = sharp::Recomb(image, baton->recombMatrix);679 }680 681 // Modulate682 if (baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0 || baton->lightness != 0.0) {683 KeepGainMapUnsupported(baton->keepGainMap, "Modulate");684 image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue, baton->lightness);685 }686 687 // Sharpen688 if (shouldSharpen) {689 image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenM1, baton->sharpenM2,690 baton->sharpenX1, baton->sharpenY2, baton->sharpenY3);691 }692 693 // Reverse premultiplication after all transformations694 if (shouldPremultiplyAlpha) {695 image = image.unpremultiply().cast(premultiplyFormat);696 }697 baton->premultiplied = shouldPremultiplyAlpha;698 699 // Composite700 if (shouldComposite) {701 KeepGainMapUnsupported(baton->keepGainMap, "Composite");702 std::vector<VImage> images = { image };703 std::vector<int> modes, xs, ys;704 for (Composite *composite : baton->composite) {705 VImage compositeImage;706 sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;707 composite->input->access = access;708 std::tie(compositeImage, compositeImageType) = sharp::OpenInput(composite->input);709 710 if (composite->input->autoOrient) {711 // Respect EXIF Orientation712 VipsAngle compositeAutoRotation = VIPS_ANGLE_D0;713 bool compositeAutoFlop = false;714 std::tie(compositeAutoRotation, compositeAutoFlop) =715 CalculateExifRotationAndFlop(sharp::ExifOrientation(compositeImage));716 717 compositeImage = sharp::RemoveExifOrientation(compositeImage);718 compositeImage = sharp::StaySequential(compositeImage, compositeAutoRotation != VIPS_ANGLE_D0);719 720 if (compositeAutoRotation != VIPS_ANGLE_D0) {721 compositeImage = compositeImage.rot(compositeAutoRotation);722 }723 if (compositeAutoFlop) {724 compositeImage = compositeImage.flip(VIPS_DIRECTION_HORIZONTAL);725 }726 }727 728 // Verify within current dimensions729 if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) {730 throw std::runtime_error("Image to composite must have same dimensions or smaller");731 }732 // Check if overlay is tiled733 if (composite->tile) {734 int across = 0;735 int down = 0;736 // Use gravity in overlay737 if (compositeImage.width() <= image.width()) {738 across = static_cast<int>(ceil(static_cast<double>(image.width()) / compositeImage.width()));739 // Ensure odd number of tiles across when gravity is centre, north or south740 if (composite->gravity == 0 || composite->gravity == 1 || composite->gravity == 3) {741 across |= 1;742 }743 }744 if (compositeImage.height() <= image.height()) {745 down = static_cast<int>(ceil(static_cast<double>(image.height()) / compositeImage.height()));746 // Ensure odd number of tiles down when gravity is centre, east or west747 if (composite->gravity == 0 || composite->gravity == 2 || composite->gravity == 4) {748 down |= 1;749 }750 }751 if (across != 0 || down != 0) {752 int left;753 int top;754 compositeImage = sharp::StaySequential(compositeImage).replicate(across, down);755 if (composite->hasOffset) {756 std::tie(left, top) = sharp::CalculateCrop(757 compositeImage.width(), compositeImage.height(), image.width(), image.height(),758 composite->left, composite->top);759 } else {760 std::tie(left, top) = sharp::CalculateCrop(761 compositeImage.width(), compositeImage.height(), image.width(), image.height(), composite->gravity);762 }763 compositeImage = compositeImage.extract_area(left, top, image.width(), image.height());764 }765 // gravity was used for extract_area, set it back to its default value of 0766 composite->gravity = 0;767 }768 // Ensure image to composite is with unpremultiplied alpha769 compositeImage = sharp::EnsureAlpha(compositeImage, 1);770 if (composite->premultiplied) compositeImage = compositeImage.unpremultiply();771 // Calculate position772 int left;773 int top;774 if (composite->hasOffset) {775 // Composite image at given offsets776 if (composite->tile) {777 std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),778 compositeImage.width(), compositeImage.height(), composite->left, composite->top);779 } else {780 left = composite->left;781 top = composite->top;782 }783 } else {784 // Composite image with given gravity785 std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),786 compositeImage.width(), compositeImage.height(), composite->gravity);787 }788 images.push_back(compositeImage);789 modes.push_back(composite->mode);790 xs.push_back(left);791 ys.push_back(top);792 }793 image = VImage::composite(images, modes, VImage::option()794 ->set("compositing_space", baton->colourspacePipeline == VIPS_INTERPRETATION_LAST795 ? VIPS_INTERPRETATION_sRGB796 : baton->colourspacePipeline)797 ->set("x", xs)798 ->set("y", ys));799 image = sharp::RemoveGifPalette(image);800 }801 802 // Gamma decoding (brighten)803 if (baton->gammaOut >= 1 && baton->gammaOut <= 3) {804 image = sharp::Gamma(image, baton->gammaOut);805 }806 807 // Linear adjustment (a * in + b)808 if (!baton->linearA.empty()) {809 image = sharp::Linear(image, baton->linearA, baton->linearB);810 }811 812 // Apply normalisation - stretch luminance to cover full dynamic range813 if (baton->normalise) {814 KeepGainMapUnsupported(baton->keepGainMap, "Normalise");815 image = sharp::StaySequential(image);816 image = sharp::Normalise(image, baton->normaliseLower, baton->normaliseUpper);817 }818 819 // Apply contrast limiting adaptive histogram equalization (CLAHE)820 if (baton->claheWidth != 0 && baton->claheHeight != 0) {821 KeepGainMapUnsupported(baton->keepGainMap, "Clahe");822 image = sharp::StaySequential(image);823 image = sharp::Clahe(image, baton->claheWidth, baton->claheHeight, baton->claheMaxSlope);824 }825 826 // Apply bitwise boolean operation between images827 if (baton->boolean != nullptr) {828 KeepGainMapUnsupported(baton->keepGainMap, "Boolean");829 VImage booleanImage;830 sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN;831 baton->boolean->access = access;832 std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean);833 booleanImage = sharp::EnsureColourspace(booleanImage, baton->colourspacePipeline);834 image = sharp::Boolean(image, booleanImage, baton->booleanOp);835 image = sharp::RemoveGifPalette(image);836 }837 838 // Apply per-channel Bandbool bitwise operations after all other operations839 if (baton->bandBoolOp >= VIPS_OPERATION_BOOLEAN_AND && baton->bandBoolOp < VIPS_OPERATION_BOOLEAN_LAST) {840 image = sharp::Bandbool(image, baton->bandBoolOp);841 }842 843 // Tint the image844 if (baton->tint[0] >= 0.0) {845 image = sharp::Tint(image, baton->tint);846 }847 848 // Remove alpha channel, if any849 if (baton->removeAlpha) {850 image = sharp::RemoveAlpha(image);851 }852 853 // Ensure alpha channel, if missing854 if (baton->ensureAlpha != -1) {855 image = sharp::EnsureAlpha(image, baton->ensureAlpha);856 }857 858 // Ensure output colour space859 if (sharp::Is16Bit(image.interpretation())) {860 image = image.cast(VIPS_FORMAT_USHORT);861 }862 if (image.interpretation() != baton->colourspace) {863 image = image.colourspace(baton->colourspace, VImage::option()->set("source_space", image.interpretation()));864 if (inputProfile.first != nullptr && baton->withIccProfile.empty()) {865 image = sharp::SetProfile(image, inputProfile);866 }867 }868 869 // Extract channel870 if (baton->extractChannel > -1) {871 KeepGainMapUnsupported(baton->keepGainMap, "Extract channel");872 if (baton->extractChannel >= image.bands()) {873 if (baton->extractChannel == 3 && image.has_alpha()) {874 baton->extractChannel = image.bands() - 1;875 } else {876 (baton->err)877 .append("Cannot extract channel ").append(std::to_string(baton->extractChannel))878 .append(" from image with channels 0-").append(std::to_string(image.bands() - 1));879 return Error();880 }881 }882 VipsInterpretation colourspace = sharp::Is16Bit(image.interpretation())883 ? VIPS_INTERPRETATION_GREY16884 : VIPS_INTERPRETATION_B_W;885 image = image886 .extract_band(baton->extractChannel)887 .copy(VImage::option()->set("interpretation", colourspace));888 }889 890 // Apply output ICC profile891 if (!baton->withIccProfile.empty()) {892 try {893 image = image.icc_transform(const_cast<char*>(baton->withIccProfile.data()), VImage::option()894 ->set("input_profile", processingProfile)895 ->set("embedded", true)896 ->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8)897 ->set("intent", VIPS_INTENT_PERCEPTUAL));898 } catch(...) {899 sharp::VipsWarningCallback(nullptr, G_LOG_LEVEL_WARNING, "Invalid profile", nullptr);900 }901 }902 903 // Negate the colours in the image904 if (baton->negate) {905 image = sharp::Negate(image, baton->negateAlpha);906 if (baton->keepGainMap) {907 gainMap = sharp::Negate(gainMap, false);908 }909 }910 911 // Override EXIF Orientation tag912 if (baton->withMetadataOrientation != -1) {913 image = sharp::SetExifOrientation(image, baton->withMetadataOrientation);914 }915 // Override pixel density916 if (baton->withMetadataDensity > 0) {917 image = sharp::SetDensity(image, baton->withMetadataDensity);918 }919 // EXIF key/value pairs920 if (baton->keepMetadata & VIPS_FOREIGN_KEEP_EXIF) {921 image = image.copy();922 if (!baton->withExifMerge) {923 image = sharp::RemoveExif(image);924 }925 for (const auto& [key, value] : baton->withExif) {926 image.set(key.c_str(), value.c_str());927 }928 }929 // XMP buffer930 if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_XMP) && !baton->withXmp.empty()) {931 image = image.copy();932 image.set(VIPS_META_XMP_NAME, nullptr,933 const_cast<void*>(static_cast<void const*>(baton->withXmp.c_str())), baton->withXmp.size());934 }935 // Number of channels used in output image936 baton->channels = image.bands();937 baton->width = image.width();938 baton->height = image.height();939 940 image = sharp::SetAnimationProperties(941 image, nPages, targetPageHeight, baton->delay, baton->loop);942 943 if (image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT) {944 baton->pageHeightOut = image.get_int(VIPS_META_PAGE_HEIGHT);945 baton->pagesOut = image.get_int(VIPS_META_N_PAGES);946 }947 baton->hasAlphaOut = image.has_alpha();948 949 // Output950 sharp::SetTimeout(image, baton->timeoutSeconds);951 if (baton->fileOut.empty()) {952 // Buffer output953 if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) {954 // Write JPEG to buffer955 sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);956 VipsArea *area;957 if (baton->keepGainMap) {958 image = ReattachGainMap(image, gainMap, baton);959 area = reinterpret_cast<VipsArea*>(image.uhdrsave_buffer(VImage::option()960 ->set("keep", baton->keepMetadata)961 ->set("Q", baton->jpegQuality)962 ->set("gainmap_scale_factor", gainMapScaleFactor)));963 } else {964 area = reinterpret_cast<VipsArea*>(image.jpegsave_buffer(VImage::option()965 ->set("keep", baton->keepMetadata)966 ->set("Q", baton->jpegQuality)967 ->set("interlace", baton->jpegProgressive)968 ->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"969 ? VIPS_FOREIGN_SUBSAMPLE_OFF970 : VIPS_FOREIGN_SUBSAMPLE_ON)971 ->set("trellis_quant", baton->jpegTrellisQuantisation)972 ->set("quant_table", baton->jpegQuantisationTable)973 ->set("overshoot_deringing", baton->jpegOvershootDeringing)974 ->set("optimize_scans", baton->jpegOptimiseScans)975 ->set("optimize_coding", baton->jpegOptimiseCoding)));976 }977 baton->bufferOut = static_cast<char*>(area->data);978 baton->bufferOutLength = area->length;979 area->free_fn = nullptr;980 vips_area_unref(area);981 baton->formatOut = "jpeg";982 if (baton->colourspace == VIPS_INTERPRETATION_CMYK) {983 baton->channels = std::min(baton->channels, 4);984 } else {985 baton->channels = std::min(baton->channels, 3);986 }987 baton->hasAlphaOut = false;988 } else if (baton->formatOut == "jp2" || (baton->formatOut == "input"989 && inputImageType == sharp::ImageType::JP2)) {990 // Write JP2 to Buffer991 sharp::AssertImageTypeDimensions(image, sharp::ImageType::JP2);992 VipsArea *area = reinterpret_cast<VipsArea*>(image.jp2ksave_buffer(VImage::option()993 ->set("Q", baton->jp2Quality)994 ->set("lossless", baton->jp2Lossless)995 ->set("subsample_mode", baton->jp2ChromaSubsampling == "4:4:4"996 ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)997 ->set("tile_height", baton->jp2TileHeight)998 ->set("tile_width", baton->jp2TileWidth)));999 baton->bufferOut = static_cast<char*>(area->data);1000 baton->bufferOutLength = area->length;1001 area->free_fn = nullptr;1002 vips_area_unref(area);1003 baton->formatOut = "jp2";1004 } else if (baton->formatOut == "png" || (baton->formatOut == "input" &&1005 (inputImageType == sharp::ImageType::PNG || inputImageType == sharp::ImageType::SVG))) {1006 // Write PNG to buffer1007 sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);1008 VipsArea *area = reinterpret_cast<VipsArea*>(image.pngsave_buffer(VImage::option()1009 ->set("keep", baton->keepMetadata)1010 ->set("interlace", baton->pngProgressive)1011 ->set("compression", baton->pngCompressionLevel)1012 ->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)1013 ->set("palette", baton->pngPalette)1014 ->set("Q", baton->pngQuality)1015 ->set("effort", baton->pngEffort)1016 ->set("bitdepth", sharp::Is16Bit(image.interpretation()) ? 16 : baton->pngBitdepth)1017 ->set("dither", baton->pngDither)));1018 baton->bufferOut = static_cast<char*>(area->data);1019 baton->bufferOutLength = area->length;1020 area->free_fn = nullptr;1021 vips_area_unref(area);1022 baton->formatOut = "png";1023 } else if (baton->formatOut == "webp" ||1024 (baton->formatOut == "input" && inputImageType == sharp::ImageType::WEBP)) {1025 // Write WEBP to buffer1026 sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);1027 VipsArea *area = reinterpret_cast<VipsArea*>(image.webpsave_buffer(VImage::option()1028 ->set("keep", baton->keepMetadata)1029 ->set("Q", baton->webpQuality)1030 ->set("lossless", baton->webpLossless)1031 ->set("near_lossless", baton->webpNearLossless)1032 ->set("smart_subsample", baton->webpSmartSubsample)1033 ->set("smart_deblock", baton->webpSmartDeblock)1034 ->set("preset", baton->webpPreset)1035 ->set("effort", baton->webpEffort)1036 ->set("min_size", baton->webpMinSize)1037 ->set("mixed", baton->webpMixed)1038 ->set("exact", baton->webpExact)1039 ->set("alpha_q", baton->webpAlphaQuality)));1040 baton->bufferOut = static_cast<char*>(area->data);1041 baton->bufferOutLength = area->length;1042 area->free_fn = nullptr;1043 vips_area_unref(area);1044 baton->formatOut = "webp";1045 } else if (baton->formatOut == "gif" ||1046 (baton->formatOut == "input" && inputImageType == sharp::ImageType::GIF)) {1047 // Write GIF to buffer1048 sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);1049 VipsArea *area = reinterpret_cast<VipsArea*>(image.gifsave_buffer(VImage::option()1050 ->set("keep", baton->keepMetadata)1051 ->set("bitdepth", baton->gifBitdepth)1052 ->set("effort", baton->gifEffort)1053 ->set("reuse", baton->gifReuse)1054 ->set("interlace", baton->gifProgressive)1055 ->set("interframe_maxerror", baton->gifInterFrameMaxError)1056 ->set("interpalette_maxerror", baton->gifInterPaletteMaxError)1057 ->set("keep_duplicate_frames", baton->gifKeepDuplicateFrames)1058 ->set("dither", baton->gifDither)));1059 baton->bufferOut = static_cast<char*>(area->data);1060 baton->bufferOutLength = area->length;1061 area->free_fn = nullptr;1062 vips_area_unref(area);1063 baton->formatOut = "gif";1064 } else if (baton->formatOut == "tiff" ||1065 (baton->formatOut == "input" && inputImageType == sharp::ImageType::TIFF)) {1066 // Write TIFF to buffer1067 if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) {1068 sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);1069 baton->channels = std::min(baton->channels, 3);1070 baton->hasAlphaOut = false;1071 }1072 // Cast pixel values to float, if required1073 if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {1074 image = image.cast(VIPS_FORMAT_FLOAT);1075 }1076 VipsArea *area = reinterpret_cast<VipsArea*>(image.tiffsave_buffer(VImage::option()1077 ->set("keep", baton->keepMetadata)1078 ->set("Q", baton->tiffQuality)1079 ->set("bitdepth", baton->tiffBitdepth)1080 ->set("compression", baton->tiffCompression)1081 ->set("bigtiff", baton->tiffBigtiff)1082 ->set("miniswhite", baton->tiffMiniswhite)1083 ->set("predictor", baton->tiffPredictor)1084 ->set("pyramid", baton->tiffPyramid)1085 ->set("tile", baton->tiffTile)1086 ->set("tile_height", baton->tiffTileHeight)1087 ->set("tile_width", baton->tiffTileWidth)1088 ->set("xres", baton->tiffXres)1089 ->set("yres", baton->tiffYres)1090 ->set("resunit", baton->tiffResolutionUnit)));1091 baton->bufferOut = static_cast<char*>(area->data);1092 baton->bufferOutLength = area->length;1093 area->free_fn = nullptr;1094 vips_area_unref(area);1095 baton->formatOut = "tiff";1096 } else if (baton->formatOut == "heif" ||1097 (baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) {1098 // Write HEIF to buffer1099 sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF);1100 image = sharp::RemoveAnimationProperties(image);1101 VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option()1102 ->set("keep", baton->keepMetadata)1103 ->set("Q", baton->heifQuality)1104 ->set("compression", baton->heifCompression)1105 ->set("effort", baton->heifEffort)1106 ->set("bitdepth", baton->heifBitdepth)1107 ->set("tune", baton->heifTune.c_str())1108 ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"1109 ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)1110 ->set("lossless", baton->heifLossless)));1111 baton->bufferOut = static_cast<char*>(area->data);1112 baton->bufferOutLength = area->length;1113 area->free_fn = nullptr;1114 vips_area_unref(area);1115 baton->formatOut = "heif";1116 } else if (baton->formatOut == "dz") {1117 // Write DZ to buffer1118 baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;1119 if (!image.has_alpha()) {1120 baton->tileBackground.pop_back();1121 }1122 image = sharp::StaySequential(image, baton->tileAngle != 0);1123 vips::VOption *options = BuildOptionsDZ(baton);1124 VipsArea *area = reinterpret_cast<VipsArea*>(image.dzsave_buffer(options));1125 baton->bufferOut = static_cast<char*>(area->data);1126 baton->bufferOutLength = area->length;1127 area->free_fn = nullptr;1128 vips_area_unref(area);1129 baton->formatOut = "dz";1130 if (baton->tileFormat == "jpeg") {1131 baton->hasAlphaOut = false;1132 }1133 } else if (baton->formatOut == "jxl" ||1134 (baton->formatOut == "input" && inputImageType == sharp::ImageType::JXL)) {1135 // Write JXL to buffer1136 image = sharp::RemoveAnimationProperties(image);1137 VipsArea *area = reinterpret_cast<VipsArea*>(image.jxlsave_buffer(VImage::option()1138 ->set("keep", baton->keepMetadata)1139 ->set("distance", baton->jxlDistance)1140 ->set("tier", baton->jxlDecodingTier)1141 ->set("effort", baton->jxlEffort)1142 ->set("lossless", baton->jxlLossless)));1143 baton->bufferOut = static_cast<char*>(area->data);1144 baton->bufferOutLength = area->length;1145 area->free_fn = nullptr;1146 vips_area_unref(area);1147 baton->formatOut = "jxl";1148 } else if (baton->formatOut == "raw" ||1149 (baton->formatOut == "input" && inputImageType == sharp::ImageType::RAW)) {1150 // Write raw, uncompressed image data to buffer1151 if (baton->greyscale || image.interpretation() == VIPS_INTERPRETATION_B_W) {1152 // Extract first band for greyscale image1153 image = image[0];1154 baton->channels = 1;1155 }1156 if (image.format() != baton->rawDepth) {1157 // Cast pixels to requested format1158 image = image.cast(baton->rawDepth);1159 }1160 // Get raw image data1161 baton->bufferOut = static_cast<char*>(image.write_to_memory(&baton->bufferOutLength));1162 if (baton->bufferOut == nullptr) {1163 throw std::runtime_error("Could not allocate enough memory for raw output");1164 }1165 baton->formatOut = "raw";1166 } else {1167 // Unsupported output format1168 auto unsupported = std::string("Unsupported output format ");1169 if (baton->formatOut == "input") {1170 unsupported.append("when trying to match input format of ");1171 unsupported.append(ImageTypeId(inputImageType));1172 } else {1173 unsupported.append(baton->formatOut);1174 }1175 throw std::runtime_error(unsupported);1176 }1177 } else {1178 // File output1179 bool const isJpeg = sharp::IsJpeg(baton->fileOut);1180 bool const isPng = sharp::IsPng(baton->fileOut);1181 bool const isWebp = sharp::IsWebp(baton->fileOut);1182 bool const isGif = sharp::IsGif(baton->fileOut);1183 bool const isTiff = sharp::IsTiff(baton->fileOut);1184 bool const isJp2 = sharp::IsJp2(baton->fileOut);1185 bool const isHeif = sharp::IsHeif(baton->fileOut);1186 bool const isJxl = sharp::IsJxl(baton->fileOut);1187 bool const isDz = sharp::IsDz(baton->fileOut);1188 bool const isDzZip = sharp::IsDzZip(baton->fileOut);1189 bool const isV = sharp::IsV(baton->fileOut);1190 bool const mightMatchInput = baton->formatOut == "input";1191 bool const willMatchInput = mightMatchInput &&1192 !(isJpeg || isPng || isWebp || isGif || isTiff || isJp2 || isHeif || isDz || isDzZip || isV);1193 1194 if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) ||1195 (willMatchInput && inputImageType == sharp::ImageType::JPEG)) {1196 // Write JPEG to file1197 sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);1198 if (baton->keepGainMap) {1199 image = ReattachGainMap(image, gainMap, baton);1200 image.uhdrsave(const_cast<char*>(baton->fileOut.data()), VImage::option()