diff --git a/src/common.cc b/src/common.cc index 79a38b6a..46b439f2 100644 --- a/src/common.cc +++ b/src/common.cc @@ -88,18 +88,14 @@ namespace sharp { descriptor->buffer = buffer.Data(); descriptor->isBuffer = TRUE; } - descriptor->failOn = static_cast( - vips_enum_from_nick(nullptr, VIPS_TYPE_FAIL_ON, - AttrAsStr(input, "failOn").data())); + descriptor->failOn = AttrAsEnum(input, "failOn", VIPS_TYPE_FAIL_ON); // Density for vector-based input if (HasAttr(input, "density")) { descriptor->density = AttrAsDouble(input, "density"); } // Raw pixel input if (HasAttr(input, "rawChannels")) { - descriptor->rawDepth = static_cast( - vips_enum_from_nick(nullptr, VIPS_TYPE_BAND_FORMAT, - AttrAsStr(input, "rawDepth").data())); + descriptor->rawDepth = AttrAsEnum(input, "rawDepth", VIPS_TYPE_BAND_FORMAT); descriptor->rawChannels = AttrAsUint32(input, "rawChannels"); descriptor->rawWidth = AttrAsUint32(input, "rawWidth"); descriptor->rawHeight = AttrAsUint32(input, "rawHeight"); @@ -149,9 +145,7 @@ namespace sharp { descriptor->textHeight = AttrAsUint32(input, "textHeight"); } if (HasAttr(input, "textAlign")) { - descriptor->textAlign = static_cast( - vips_enum_from_nick(nullptr, VIPS_TYPE_ALIGN, - AttrAsStr(input, "textAlign").data())); + descriptor->textAlign = AttrAsEnum(input, "textAlign", VIPS_TYPE_ALIGN); } if (HasAttr(input, "textJustify")) { descriptor->textJustify = AttrAsBool(input, "textJustify"); @@ -855,22 +849,6 @@ namespace sharp { return Is16Bit(interpretation) ? 65535.0 : 255.0; } - /* - Get boolean operation type from string - */ - VipsOperationBoolean GetBooleanOperation(std::string const opStr) { - return static_cast( - vips_enum_from_nick(nullptr, VIPS_TYPE_OPERATION_BOOLEAN, opStr.data())); - } - - /* - Get interpretation type from string - */ - VipsInterpretation GetInterpretation(std::string const typeStr) { - return static_cast( - vips_enum_from_nick(nullptr, VIPS_TYPE_INTERPRETATION, typeStr.data())); - } - /* Convert RGBA value to another colourspace */ diff --git a/src/common.h b/src/common.h index 4aaf357d..e2f12fbd 100644 --- a/src/common.h +++ b/src/common.h @@ -129,6 +129,10 @@ namespace sharp { bool AttrAsBool(Napi::Object obj, std::string attr); std::vector AttrAsVectorOfDouble(Napi::Object obj, std::string attr); std::vector AttrAsInt32Vector(Napi::Object obj, std::string attr); + template T AttrAsEnum(Napi::Object obj, std::string attr, GType type) { + return static_cast( + vips_enum_from_nick(nullptr, type, AttrAsStr(obj, attr).data())); + } // Create an InputDescriptor instance from a Napi::Object describing an input image InputDescriptor* CreateInputDescriptor(Napi::Object input); @@ -326,16 +330,6 @@ namespace sharp { */ double MaximumImageAlpha(VipsInterpretation const interpretation); - /* - Get boolean operation type from string - */ - VipsOperationBoolean GetBooleanOperation(std::string const opStr); - - /* - Get interpretation type from string - */ - VipsInterpretation GetInterpretation(std::string const typeStr); - /* Convert RGBA value to another colourspace */ diff --git a/src/operations.cc b/src/operations.cc index b288d201..b3339d9a 100644 --- a/src/operations.cc +++ b/src/operations.cc @@ -112,6 +112,19 @@ namespace sharp { } } + /* + * Flatten image to remove alpha channel + */ + VImage Flatten(VImage image, std::vector flattenBackground) { + double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0; + std::vector background { + flattenBackground[0] * multiplier, + flattenBackground[1] * multiplier, + flattenBackground[2] * multiplier + }; + return image.flatten(VImage::option()->set("background", background)); + } + /** * Produce the "negative" of the image. */ diff --git a/src/operations.h b/src/operations.h index c9f2f8c2..0fe1cac9 100644 --- a/src/operations.h +++ b/src/operations.h @@ -45,6 +45,11 @@ namespace sharp { */ VImage Gamma(VImage image, double const exponent); + /* + * Flatten image to remove alpha channel + */ + VImage Flatten(VImage image, std::vector flattenBackground); + /* * Produce the "negative" of the image. */ diff --git a/src/pipeline.cc b/src/pipeline.cc index 9281c0fe..361b509e 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -321,16 +321,7 @@ class PipelineWorker : public Napi::AsyncWorker { // Flatten image to remove alpha channel if (baton->flatten && sharp::HasAlpha(image)) { - // Scale up 8-bit values to match 16-bit input image - double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0; - // Background colour - std::vector background { - baton->flattenBackground[0] * multiplier, - baton->flattenBackground[1] * multiplier, - baton->flattenBackground[2] * multiplier - }; - image = image.flatten(VImage::option() - ->set("background", background)); + image = sharp::Flatten(image, baton->flattenBackground); } // Negate the colours in the image @@ -352,11 +343,7 @@ class PipelineWorker : public Napi::AsyncWorker { bool const shouldBlur = baton->blurSigma != 0.0; bool const shouldConv = baton->convKernelWidth * baton->convKernelHeight > 0; bool const shouldSharpen = baton->sharpenSigma != 0.0; - bool const shouldApplyMedian = baton->medianSize > 0; bool const shouldComposite = !baton->composite.empty(); - bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 || - baton->hue != 0.0 || baton->lightness != 0.0; - bool const shouldApplyClahe = baton->claheWidth != 0 && baton->claheHeight != 0; if (shouldComposite && !sharp::HasAlpha(image)) { image = sharp::EnsureAlpha(image, 1); @@ -365,26 +352,15 @@ class PipelineWorker : public Napi::AsyncWorker { bool const shouldPremultiplyAlpha = sharp::HasAlpha(image) && (shouldResize || shouldBlur || shouldConv || shouldSharpen); - // Premultiply image alpha channel before all transformations to avoid - // dark fringing around bright pixels - // See: http://entropymine.com/imageworsener/resizealpha/ if (shouldPremultiplyAlpha) { image = image.premultiply(); } // Resize if (shouldResize) { - VipsKernel kernel = static_cast( - vips_enum_from_nick(nullptr, VIPS_TYPE_KERNEL, baton->kernel.data())); - if ( - kernel != VIPS_KERNEL_NEAREST && kernel != VIPS_KERNEL_CUBIC && kernel != VIPS_KERNEL_LANCZOS2 && - kernel != VIPS_KERNEL_LANCZOS3 && kernel != VIPS_KERNEL_MITCHELL - ) { - throw vips::VError("Unknown kernel"); - } image = image.resize(1.0 / hshrink, VImage::option() ->set("vscale", 1.0 / vshrink) - ->set("kernel", kernel)); + ->set("kernel", baton->kernel)); } // Flip (mirror about Y axis) @@ -444,18 +420,12 @@ class PipelineWorker : public Napi::AsyncWorker { std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground, shouldPremultiplyAlpha); // Embed - - // Calculate where to position the embedded image if gravity specified, else center. int left; int top; - - left = static_cast(round((baton->width - inputWidth) / 2)); - top = static_cast(round((baton->height - inputHeight) / 2)); - - int width = std::max(inputWidth, baton->width); - int height = std::max(inputHeight, baton->height); std::tie(left, top) = sharp::CalculateEmbedPosition( inputWidth, inputHeight, baton->width, baton->height, baton->position); + int width = std::max(inputWidth, baton->width); + int height = std::max(inputHeight, baton->height); image = nPages > 1 ? sharp::EmbedMultiPage(image, @@ -554,7 +524,7 @@ class PipelineWorker : public Napi::AsyncWorker { VImage::option()->set("extend", VIPS_EXTEND_BACKGROUND)->set("background", background)); } // Median - must happen before blurring, due to the utility of blurring after thresholding - if (shouldApplyMedian) { + if (baton->medianSize > 0) { image = image.median(baton->medianSize); } // Threshold - must happen before blurring, due to the utility of blurring after thresholding @@ -580,7 +550,8 @@ class PipelineWorker : public Napi::AsyncWorker { image = sharp::Recomb(image, baton->recombMatrix); } - if (shouldModulate) { + // 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); } @@ -698,7 +669,7 @@ class PipelineWorker : public Napi::AsyncWorker { } // Apply contrast limiting adaptive histogram equalization (CLAHE) - if (shouldApplyClahe) { + if (baton->claheWidth != 0 && baton->claheHeight != 0) { image = sharp::Clahe(image, baton->claheWidth, baton->claheHeight, baton->claheMaxSlope); } @@ -1398,17 +1369,13 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) { } else if (canvas == "ignore_aspect") { baton->canvas = sharp::Canvas::IGNORE_ASPECT; } - // Tint chroma - baton->tintA = sharp::AttrAsDouble(options, "tintA"); - baton->tintB = sharp::AttrAsDouble(options, "tintB"); // 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 = static_cast( - vips_enum_from_nick(nullptr, VIPS_TYPE_BLEND_MODE, sharp::AttrAsStr(compositeObject, "blend").data())); + 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"); @@ -1422,7 +1389,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) { baton->withoutReduction = sharp::AttrAsBool(options, "withoutReduction"); baton->position = sharp::AttrAsInt32(options, "position"); baton->resizeBackground = sharp::AttrAsVectorOfDouble(options, "resizeBackground"); - baton->kernel = sharp::AttrAsStr(options, "kernel"); + baton->kernel = sharp::AttrAsEnum(options, "kernel", VIPS_TYPE_KERNEL); baton->fastShrinkOnLoad = sharp::AttrAsBool(options, "fastShrinkOnLoad"); // Join Channel Options if (options.Has("joinChannelIn")) { @@ -1459,6 +1426,8 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) { baton->linearB = sharp::AttrAsVectorOfDouble(options, "linearB"); baton->greyscale = sharp::AttrAsBool(options, "greyscale"); baton->normalise = sharp::AttrAsBool(options, "normalise"); + baton->tintA = sharp::AttrAsDouble(options, "tintA"); + baton->tintB = sharp::AttrAsDouble(options, "tintB"); baton->claheWidth = sharp::AttrAsUint32(options, "claheWidth"); baton->claheHeight = sharp::AttrAsUint32(options, "claheHeight"); baton->claheMaxSlope = sharp::AttrAsUint32(options, "claheMaxSlope"); @@ -1486,10 +1455,10 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) { baton->ensureAlpha = sharp::AttrAsDouble(options, "ensureAlpha"); if (options.Has("boolean")) { baton->boolean = sharp::CreateInputDescriptor(options.Get("boolean").As()); - baton->booleanOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "booleanOp")); + baton->booleanOp = sharp::AttrAsEnum(options, "booleanOp", VIPS_TYPE_OPERATION_BOOLEAN); } if (options.Has("bandBoolOp")) { - baton->bandBoolOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "bandBoolOp")); + baton->bandBoolOp = sharp::AttrAsEnum(options, "bandBoolOp", VIPS_TYPE_OPERATION_BOOLEAN); } if (options.Has("convKernel")) { Napi::Object kernel = options.Get("convKernel").As(); @@ -1511,11 +1480,12 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) { baton->recombMatrix[i] = sharp::AttrAsDouble(recombMatrix, i); } } - baton->colourspaceInput = sharp::GetInterpretation(sharp::AttrAsStr(options, "colourspaceInput")); + baton->colourspaceInput = sharp::AttrAsEnum( + options, "colourspaceInput", VIPS_TYPE_INTERPRETATION); if (baton->colourspaceInput == VIPS_INTERPRETATION_ERROR) { baton->colourspaceInput = VIPS_INTERPRETATION_LAST; } - baton->colourspace = sharp::GetInterpretation(sharp::AttrAsStr(options, "colourspace")); + baton->colourspace = sharp::AttrAsEnum(options, "colourspace", VIPS_TYPE_INTERPRETATION); if (baton->colourspace == VIPS_INTERPRETATION_ERROR) { baton->colourspace = VIPS_INTERPRETATION_sRGB; } @@ -1580,28 +1550,19 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) { if (baton->tiffXres == 1.0 && baton->tiffYres == 1.0 && baton->withMetadataDensity > 0) { baton->tiffXres = baton->tiffYres = baton->withMetadataDensity / 25.4; } - // tiff compression options - baton->tiffCompression = static_cast( - vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION, - sharp::AttrAsStr(options, "tiffCompression").data())); - baton->tiffPredictor = static_cast( - vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_PREDICTOR, - sharp::AttrAsStr(options, "tiffPredictor").data())); - baton->tiffResolutionUnit = static_cast( - vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_RESUNIT, - sharp::AttrAsStr(options, "tiffResolutionUnit").data())); - + 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 = static_cast( - vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION, - sharp::AttrAsStr(options, "heifCompression").data())); + baton->heifCompression = sharp::AttrAsEnum( + options, "heifCompression", VIPS_TYPE_FOREIGN_HEIF_COMPRESSION); baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort"); baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling"); - // Raw output - baton->rawDepth = static_cast( - vips_enum_from_nick(nullptr, VIPS_TYPE_BAND_FORMAT, - sharp::AttrAsStr(options, "rawDepth").data())); + baton->rawDepth = sharp::AttrAsEnum(options, "rawDepth", VIPS_TYPE_BAND_FORMAT); // Animated output properties if (sharp::HasAttr(options, "loop")) { baton->loop = sharp::AttrAsUint32(options, "loop"); @@ -1609,22 +1570,16 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) { if (sharp::HasAttr(options, "delay")) { baton->delay = sharp::AttrAsInt32Vector(options, "delay"); } - // Tile output 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 = static_cast( - vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_CONTAINER, - sharp::AttrAsStr(options, "tileContainer").data())); - baton->tileLayout = static_cast( - vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_LAYOUT, - sharp::AttrAsStr(options, "tileLayout").data())); + 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 = static_cast( - vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_DEPTH, - sharp::AttrAsStr(options, "tileDepth").data())); + 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"); diff --git a/src/pipeline.h b/src/pipeline.h index 98b36986..25b0725a 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -67,6 +67,7 @@ struct PipelineBaton { int width; int height; int channels; + VipsKernel kernel; sharp::Canvas canvas; int position; std::vector resizeBackground; @@ -75,7 +76,6 @@ struct PipelineBaton { int cropOffsetTop; bool premultiplied; bool tileCentre; - std::string kernel; bool fastShrinkOnLoad; double tintA; double tintB; @@ -222,6 +222,7 @@ struct PipelineBaton { topOffsetPre(-1), topOffsetPost(-1), channels(0), + kernel(VIPS_KERNEL_LANCZOS3), canvas(sharp::Canvas::CROP), position(0), resizeBackground{ 0.0, 0.0, 0.0, 255.0 },