mirror of
https://github.com/lovell/sharp.git
synced 2025-12-19 07:15:08 +01:00
Add experimental overlayWith API
Composites an overlay image with alpha channel into the input image (which must have alpha channel) using ‘over’ alpha compositing blend mode. This API requires both images to have the same dimensions. References: - http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending - https://github.com/jcupitt/ruby-vips/issues/28#issuecomment-9014826 See #97.
This commit is contained in:
committed by
Lovell Fuller
parent
c886eaa6b0
commit
64f7f1d662
125
src/composite.c
Normal file
125
src/composite.c
Normal file
@@ -0,0 +1,125 @@
|
||||
#include <stdio.h>
|
||||
#include <vips/vips.h>
|
||||
|
||||
|
||||
const int ALPHA_BAND_INDEX = 3;
|
||||
const int NUM_COLOR_BANDS = 3;
|
||||
|
||||
|
||||
/*
|
||||
Composite images `src` and `dst`
|
||||
*/
|
||||
int Composite(VipsObject *context, VipsImage *src, VipsImage *dst, VipsImage **out) {
|
||||
if (src->Bands != 4 || dst->Bands != 4)
|
||||
return -1;
|
||||
|
||||
// Extract RGB bands:
|
||||
VipsImage *srcRGB;
|
||||
VipsImage *dstRGB;
|
||||
if (vips_extract_band(src, &srcRGB, 0, "n", NUM_COLOR_BANDS, NULL) ||
|
||||
vips_extract_band(dst, &dstRGB, 0, "n", NUM_COLOR_BANDS, NULL))
|
||||
return -1;
|
||||
|
||||
vips_object_local(context, srcRGB);
|
||||
vips_object_local(context, dstRGB);
|
||||
|
||||
// Extract alpha bands:
|
||||
VipsImage *srcAlpha;
|
||||
VipsImage *dstAlpha;
|
||||
if (vips_extract_band(src, &srcAlpha, ALPHA_BAND_INDEX, NULL) ||
|
||||
vips_extract_band(dst, &dstAlpha, ALPHA_BAND_INDEX, NULL))
|
||||
return -1;
|
||||
|
||||
vips_object_local(context, srcAlpha);
|
||||
vips_object_local(context, dstAlpha);
|
||||
|
||||
// Compute normalized input alpha channels:
|
||||
VipsImage *srcAlphaNormalized;
|
||||
VipsImage *dstAlphaNormalized;
|
||||
if (vips_linear1(srcAlpha, &srcAlphaNormalized, 1.0 / 255.0, 0.0, NULL) ||
|
||||
vips_linear1(dstAlpha, &dstAlphaNormalized, 1.0 / 255.0, 0.0, NULL))
|
||||
return -1;
|
||||
|
||||
vips_object_local(context, srcAlphaNormalized);
|
||||
vips_object_local(context, dstAlphaNormalized);
|
||||
|
||||
//
|
||||
// Compute normalized output alpha channel:
|
||||
//
|
||||
// References:
|
||||
// - http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
|
||||
// - https://github.com/jcupitt/ruby-vips/issues/28#issuecomment-9014826
|
||||
//
|
||||
// out_a = src_a + dst_a * (1 - src_a)
|
||||
// ^^^^^^^^^^^
|
||||
// t0
|
||||
// ^^^^^^^^^^^^^^^^^^^
|
||||
// t1
|
||||
VipsImage *t0;
|
||||
VipsImage *t1;
|
||||
VipsImage *outAlphaNormalized;
|
||||
if (vips_linear1(srcAlphaNormalized, &t0, -1.0, 1.0, NULL) ||
|
||||
vips_multiply(dstAlphaNormalized, t0, &t1, NULL) ||
|
||||
vips_add(srcAlphaNormalized, t1, &outAlphaNormalized, NULL))
|
||||
return -1;
|
||||
|
||||
vips_object_local(context, t0);
|
||||
vips_object_local(context, t1);
|
||||
vips_object_local(context, outAlphaNormalized);
|
||||
|
||||
//
|
||||
// Compute output RGB channels:
|
||||
//
|
||||
// Wikipedia:
|
||||
// out_rgb = (src_rgb * src_a + dst_rgb * dst_a * (1 - src_a)) / out_a
|
||||
//
|
||||
// `vips_ifthenelse` with `blend=TRUE`: http://bit.ly/1KoSsga
|
||||
// out = (cond / 255) * in1 + (1 - cond / 255) * in2
|
||||
//
|
||||
// Substitutions:
|
||||
//
|
||||
// cond --> src_a
|
||||
// in1 --> src_rgb
|
||||
// in2 --> dst_rgb * dst_a (premultiplied destination RGB)
|
||||
//
|
||||
// Finally, manually divide by `out_a` to unpremultiply the RGB channels.
|
||||
// Failing to do so results in darker than expected output with low
|
||||
// opacity images.
|
||||
//
|
||||
VipsImage *dstRGBPremultiplied;
|
||||
if (vips_multiply(dstRGB, dstAlphaNormalized, &dstRGBPremultiplied, NULL))
|
||||
return -1;
|
||||
|
||||
vips_object_local(context, dstRGBPremultiplied);
|
||||
|
||||
VipsImage *outRGBPremultiplied;
|
||||
if (vips_ifthenelse(srcAlpha, srcRGB, dstRGBPremultiplied,
|
||||
&outRGBPremultiplied, "blend", TRUE, NULL))
|
||||
return -1;
|
||||
|
||||
vips_object_local(context, outRGBPremultiplied);
|
||||
|
||||
// Unpremultiply RGB channels:
|
||||
VipsImage *outRGB;
|
||||
if (vips_divide(outRGBPremultiplied, outAlphaNormalized, &outRGB, NULL))
|
||||
return -1;
|
||||
|
||||
vips_object_local(context, outRGB);
|
||||
|
||||
// Denormalize output alpha channel:
|
||||
VipsImage *outAlpha;
|
||||
if (vips_linear1(outAlphaNormalized, &outAlpha, 255.0, 0.0, NULL))
|
||||
return -1;
|
||||
|
||||
vips_object_local(context, outAlpha);
|
||||
|
||||
// Combine RGB and alpha channel into output image:
|
||||
VipsImage *joined;
|
||||
if (vips_bandjoin2(outRGB, outAlpha, &joined, NULL))
|
||||
return -1;
|
||||
|
||||
// Return a reference to the output image:
|
||||
*out = joined;
|
||||
|
||||
return 0;
|
||||
}
|
||||
17
src/composite.h
Normal file
17
src/composite.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef SRC_COMPOSITE_H_
|
||||
#define SRC_COMPOSITE_H_
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
/*
|
||||
Composite images `src` and `dst`.
|
||||
*/
|
||||
int Composite(VipsObject *context, VipsImage *src, VipsImage *dst, VipsImage **out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // SRC_COMPOSITE_H_
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "nan.h"
|
||||
|
||||
#include "common.h"
|
||||
#include "composite.h"
|
||||
#include "resize.h"
|
||||
|
||||
using v8::Handle;
|
||||
@@ -81,6 +82,7 @@ struct ResizeBaton {
|
||||
int sharpenRadius;
|
||||
double sharpenFlat;
|
||||
double sharpenJagged;
|
||||
std::string overlayPath;
|
||||
double gamma;
|
||||
bool greyscale;
|
||||
bool normalize;
|
||||
@@ -790,6 +792,54 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
}
|
||||
#endif
|
||||
|
||||
// Composite with overlay, if present
|
||||
if (!baton->overlayPath.empty()) {
|
||||
VipsImage *overlayImage = NULL;
|
||||
ImageType overlayImageType = ImageType::UNKNOWN;
|
||||
overlayImageType = DetermineImageType(baton->overlayPath.c_str());
|
||||
if (overlayImageType != ImageType::UNKNOWN) {
|
||||
overlayImage = InitImage(baton->overlayPath.c_str(), baton->accessMethod);
|
||||
if (overlayImage == NULL) {
|
||||
(baton->err).append("Overlay input file has corrupt header");
|
||||
overlayImageType = ImageType::UNKNOWN;
|
||||
}
|
||||
} else {
|
||||
(baton->err).append("Overlay input file is of an unsupported image format");
|
||||
}
|
||||
|
||||
if (overlayImage == NULL || overlayImageType == ImageType::UNKNOWN) {
|
||||
return Error();
|
||||
}
|
||||
|
||||
if (!HasAlpha(overlayImage)) {
|
||||
(baton->err).append("Overlay input must have an alpha channel");
|
||||
return Error();
|
||||
}
|
||||
|
||||
if (!HasAlpha(image)) {
|
||||
(baton->err).append("Input image must have an alpha channel");
|
||||
return Error();
|
||||
}
|
||||
|
||||
if (overlayImage->Bands != 4) {
|
||||
(baton->err).append("Overlay input image must have 4 channels");
|
||||
return Error();
|
||||
}
|
||||
|
||||
if (image->Bands != 4) {
|
||||
(baton->err).append("Input image must have 4 channels");
|
||||
return Error();
|
||||
}
|
||||
|
||||
VipsImage *composited;
|
||||
if (Composite(hook, overlayImage, image, &composited)) {
|
||||
(baton->err).append("Failed to composite images");
|
||||
return Error();
|
||||
}
|
||||
vips_object_local(hook, composited);
|
||||
image = composited;
|
||||
}
|
||||
|
||||
// Convert image to sRGB, if not already
|
||||
if (image->Type != VIPS_INTERPRETATION_sRGB) {
|
||||
// Switch intrepretation to sRGB
|
||||
@@ -1175,6 +1225,8 @@ NAN_METHOD(resize) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
baton->background[i] = background->Get(i)->NumberValue();
|
||||
}
|
||||
// Overlay options
|
||||
baton->overlayPath = *String::Utf8Value(options->Get(NanNew<String>("overlayPath"))->ToString());
|
||||
// Resize options
|
||||
baton->withoutEnlargement = options->Get(NanNew<String>("withoutEnlargement"))->BooleanValue();
|
||||
baton->gravity = options->Get(NanNew<String>("gravity"))->Int32Value();
|
||||
|
||||
Reference in New Issue
Block a user