mirror of
https://github.com/lovell/sharp.git
synced 2025-12-19 07:15:08 +01:00
USAGE: overlayWith('overlayimage.png', { cutout: true } )
This commit is contained in:
committed by
Lovell Fuller
parent
f1ead06645
commit
2e9cd83ed2
@@ -277,4 +277,17 @@ namespace sharp {
|
||||
return std::make_tuple(left, top);
|
||||
}
|
||||
|
||||
/*
|
||||
Return the image alpha maximum. Useful for combining alpha bands. scRGB
|
||||
images are 0 - 1 for image data, but the alpha is 0 - 255.
|
||||
*/
|
||||
int MaximumImageAlpha(VipsInterpretation interpretation) {
|
||||
if(interpretation == VIPS_INTERPRETATION_RGB16 ||
|
||||
interpretation == VIPS_INTERPRETATION_GREY16) {
|
||||
return (65535);
|
||||
} else {
|
||||
return (255);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sharp
|
||||
|
||||
@@ -108,6 +108,8 @@ namespace sharp {
|
||||
std::tuple<int, int> CalculateCrop(int const inWidth, int const inHeight,
|
||||
int const outWidth, int const outHeight, int const gravity);
|
||||
|
||||
int MaximumImageAlpha(VipsInterpretation interpretation);
|
||||
|
||||
} // namespace sharp
|
||||
|
||||
#endif // SRC_COMMON_H_
|
||||
|
||||
@@ -81,6 +81,65 @@ namespace sharp {
|
||||
return outRGBPremultiplied.bandjoin(outAlphaNormalized * 255.0);
|
||||
}
|
||||
|
||||
/*
|
||||
Cutout src over dst with given gravity.
|
||||
*/
|
||||
VImage Cutout(VImage mask, VImage dst, const int gravity) {
|
||||
using sharp::CalculateCrop;
|
||||
using sharp::HasAlpha;
|
||||
using sharp::MaximumImageAlpha;
|
||||
|
||||
bool maskHasAlpha = HasAlpha(mask);
|
||||
|
||||
if (!maskHasAlpha && mask.bands() > 1) {
|
||||
throw VError("Overlay image must have an alpha channel or one band");
|
||||
}
|
||||
if (!HasAlpha(dst)) {
|
||||
throw VError("Image to be overlaid must have an alpha channel");
|
||||
}
|
||||
if (mask.width() > dst.width() || mask.height() > dst.height()) {
|
||||
throw VError("Overlay image must have same dimensions or smaller");
|
||||
}
|
||||
|
||||
// Enlarge overlay mask, if required
|
||||
if (mask.width() < dst.width() || mask.height() < dst.height()) {
|
||||
// Calculate the (left, top) coordinates of the output image within the input image, applying the given gravity.
|
||||
int left;
|
||||
int top;
|
||||
std::tie(left, top) = CalculateCrop(dst.width(), dst.height(), mask.width(), mask.height(), gravity);
|
||||
// Embed onto transparent background
|
||||
std::vector<double> background { 0.0, 0.0, 0.0, 0.0 };
|
||||
mask = mask.embed(left, top, dst.width(), dst.height(), VImage::option()
|
||||
->set("extend", VIPS_EXTEND_BACKGROUND)
|
||||
->set("background", background)
|
||||
);
|
||||
}
|
||||
|
||||
// we use the mask alpha if it has alpha
|
||||
if(maskHasAlpha) {
|
||||
mask = mask.extract_band(mask.bands() - 1, VImage::option()->set("n", 1));;
|
||||
}
|
||||
|
||||
// Split dst into an optional alpha
|
||||
VImage dstAlpha = dst.extract_band(dst.bands() - 1, VImage::option()->set("n", 1));
|
||||
|
||||
// we use the dst non-alpha
|
||||
dst = dst.extract_band(0, VImage::option()->set("n", dst.bands() - 1));
|
||||
|
||||
// the range of the mask and the image need to match .. one could be
|
||||
// 16-bit, one 8-bit
|
||||
int dstMax = MaximumImageAlpha(dst.interpretation());
|
||||
int maskMax = MaximumImageAlpha(mask.interpretation());
|
||||
|
||||
// combine the new mask and the existing alpha ... there are
|
||||
// many ways of doing this, mult is the simplest
|
||||
mask = dstMax * ((mask / maskMax) * (dstAlpha / dstMax));
|
||||
|
||||
// append the mask to the image data ... the mask might be float now,
|
||||
// we must cast the format down to match the image data
|
||||
return dst.bandjoin(mask.cast(dst.format()));
|
||||
}
|
||||
|
||||
/*
|
||||
* Stretch luminance to cover full dynamic range.
|
||||
*/
|
||||
|
||||
@@ -14,6 +14,11 @@ namespace sharp {
|
||||
*/
|
||||
VImage Composite(VImage src, VImage dst, const int gravity);
|
||||
|
||||
/*
|
||||
Cutout src over dst with given gravity.
|
||||
*/
|
||||
VImage Cutout(VImage src, VImage dst, const int gravity);
|
||||
|
||||
/*
|
||||
* Stretch luminance to cover full dynamic range.
|
||||
*/
|
||||
|
||||
@@ -45,6 +45,7 @@ using vips::VOption;
|
||||
using vips::VError;
|
||||
|
||||
using sharp::Composite;
|
||||
using sharp::Cutout;
|
||||
using sharp::Normalize;
|
||||
using sharp::Gamma;
|
||||
using sharp::Blur;
|
||||
@@ -464,8 +465,9 @@ class PipelineWorker : public AsyncWorker {
|
||||
bool shouldBlur = baton->blurSigma != 0.0;
|
||||
bool shouldSharpen = baton->sharpenSigma != 0.0;
|
||||
bool shouldThreshold = baton->threshold != 0;
|
||||
bool shouldCutout = baton->overlayCutout;
|
||||
bool shouldPremultiplyAlpha = HasAlpha(image) &&
|
||||
(shouldAffineTransform || shouldBlur || shouldSharpen || hasOverlay);
|
||||
(shouldAffineTransform || shouldBlur || shouldSharpen || (hasOverlay && !shouldCutout));
|
||||
|
||||
// Premultiply image alpha channel before all transformations to avoid
|
||||
// dark fringing around bright pixels
|
||||
@@ -699,10 +701,15 @@ class PipelineWorker : public AsyncWorker {
|
||||
// the overlayGravity was used for extract_area, therefore set it back to its default value of 0
|
||||
baton->overlayGravity = 0;
|
||||
}
|
||||
// Ensure overlay is premultiplied sRGB
|
||||
overlayImage = overlayImage.colourspace(VIPS_INTERPRETATION_sRGB).premultiply();
|
||||
// Composite images with given gravity
|
||||
image = Composite(overlayImage, image, baton->overlayGravity);
|
||||
if(shouldCutout) {
|
||||
// 'cut out' the image, premultiplication is not required
|
||||
image = Cutout(overlayImage, image, baton->overlayGravity);
|
||||
} else {
|
||||
// Ensure overlay is premultiplied sRGB
|
||||
overlayImage = overlayImage.colourspace(VIPS_INTERPRETATION_sRGB).premultiply();
|
||||
// Composite images with given gravity
|
||||
image = Composite(overlayImage, image, baton->overlayGravity);
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse premultiplication after all transformations:
|
||||
@@ -1086,6 +1093,7 @@ NAN_METHOD(pipeline) {
|
||||
}
|
||||
baton->overlayGravity = attrAs<int32_t>(options, "overlayGravity");
|
||||
baton->overlayTile = attrAs<bool>(options, "overlayTile");
|
||||
baton->overlayCutout = attrAs<bool>(options, "overlayCutout");
|
||||
// Resize options
|
||||
baton->withoutEnlargement = attrAs<bool>(options, "withoutEnlargement");
|
||||
baton->crop = attrAs<int32_t>(options, "crop");
|
||||
|
||||
@@ -34,6 +34,7 @@ struct PipelineBaton {
|
||||
size_t overlayBufferInLength;
|
||||
int overlayGravity;
|
||||
bool overlayTile;
|
||||
bool overlayCutout;
|
||||
int topOffsetPre;
|
||||
int leftOffsetPre;
|
||||
int widthPre;
|
||||
@@ -99,6 +100,7 @@ struct PipelineBaton {
|
||||
overlayBufferInLength(0),
|
||||
overlayGravity(0),
|
||||
overlayTile(false),
|
||||
overlayCutout(false),
|
||||
topOffsetPre(-1),
|
||||
topOffsetPost(-1),
|
||||
channels(0),
|
||||
|
||||
Reference in New Issue
Block a user