Skip to content

Commit

Permalink
Alpha compositing: support grey+alpha src and non-alpha dst
Browse files Browse the repository at this point in the history
  • Loading branch information
lovell committed Jun 2, 2015
1 parent 36be045 commit 1091be3
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 88 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -417,10 +417,6 @@ If the background contains an alpha value then WebP and PNG format output images

Merge alpha transparency channel, if any, with `background`.

#### Experimental: overlayWith(filename)

**Experimental:** Composite image with transparent overlay. Both input and overlay image must be RGBA and their dimensions must match.

#### rotate([angle])

Rotate the output image by either an explicit angle or auto-orient based on the EXIF `Orientation` tag.
Expand Down Expand Up @@ -498,6 +494,14 @@ The output image will still be web-friendly sRGB and contain three (identical) c

Enhance output image contrast by stretching its luminance to cover the full dynamic range. This typically reduces performance by 30%.

#### overlayWith(filename)

_Experimental_

Alpha composite `filename` over the processed (resized, extracted) image. The dimensions of the two images must match.

* `filename` is a String containing the filename of an image with an alpha channel.

### Output options

#### jpeg()
Expand Down
2 changes: 2 additions & 0 deletions src/common.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#ifndef SRC_COMMON_H_
#define SRC_COMMON_H_

#include <string>

namespace sharp {

enum class ImageType {
Expand Down
93 changes: 44 additions & 49 deletions src/operations.cc
Original file line number Diff line number Diff line change
@@ -1,38 +1,55 @@
#include <vips/vips.h>

#include "common.h"
#include "operations.h"

namespace sharp {

/*
Composite images `src` and `dst` with premultiplied alpha channel and output
image with premultiplied alpha.
Alpha composite src over dst
Assumes alpha channels are already premultiplied and will be unpremultiplied after
*/
int Composite(VipsObject *context, VipsImage *srcPremultiplied, VipsImage *dstPremultiplied, VipsImage **outPremultiplied) {
if (srcPremultiplied->Bands != 4 || dstPremultiplied->Bands != 4)
return -1;

// Extract RGB bands:
VipsImage *srcRGBPremultiplied;
if (vips_extract_band(srcPremultiplied, &srcRGBPremultiplied, 0, "n", srcPremultiplied->Bands - 1, NULL))
return -1;
vips_object_local(context, srcRGBPremultiplied);
int Composite(VipsObject *context, VipsImage *src, VipsImage *dst, VipsImage **out) {
using sharp::HasAlpha;

VipsImage *dstRGBPremultiplied;
if (vips_extract_band(dstPremultiplied, &dstRGBPremultiplied, 0, "n", dstPremultiplied->Bands - 1, NULL))
// Split src into non-alpha and alpha
VipsImage *srcWithoutAlpha;
if (vips_extract_band(src, &srcWithoutAlpha, 0, "n", src->Bands - 1, NULL))
return -1;
vips_object_local(context, dstRGBPremultiplied);

// Extract alpha bands:
vips_object_local(context, srcWithoutAlpha);
VipsImage *srcAlpha;
if (vips_extract_band(srcPremultiplied, &srcAlpha, srcPremultiplied->Bands - 1, "n", 1, NULL))
if (vips_extract_band(src, &srcAlpha, src->Bands - 1, "n", 1, NULL))
return -1;
vips_object_local(context, srcAlpha);

// Split dst into non-alpha and alpha channels
VipsImage *dstWithoutAlpha;
VipsImage *dstAlpha;
if (vips_extract_band(dstPremultiplied, &dstAlpha, dstPremultiplied->Bands - 1, "n", 1, NULL))
return -1;
vips_object_local(context, dstAlpha);
if (HasAlpha(dst)) {
// Non-alpha: extract all-but-last channel
if (vips_extract_band(dst, &dstWithoutAlpha, 0, "n", dst->Bands - 1, NULL)) {
return -1;
}
vips_object_local(context, dstWithoutAlpha);
// Alpha: Extract last channel
if (vips_extract_band(dst, &dstAlpha, dst->Bands - 1, "n", 1, NULL)) {
return -1;
}
vips_object_local(context, dstAlpha);
} else {
// Non-alpha: Copy reference
dstWithoutAlpha = dst;
// Alpha: Use blank, opaque (0xFF) image
VipsImage *black;
if (vips_black(&black, dst->Xsize, dst->Ysize, NULL)) {
return -1;
}
vips_object_local(context, black);
if (vips_invert(black, &dstAlpha, NULL)) {
return -1;
}
vips_object_local(context, dstAlpha);
}

// Compute normalized input alpha channels:
VipsImage *srcAlphaNormalized;
Expand Down Expand Up @@ -85,12 +102,12 @@ namespace sharp {
// externally.
//
VipsImage *t2;
if (vips_multiply(dstRGBPremultiplied, t0, &t2, NULL))
if (vips_multiply(dstWithoutAlpha, t0, &t2, NULL))
return -1;
vips_object_local(context, t2);

VipsImage *outRGBPremultiplied;
if (vips_add(srcRGBPremultiplied, t2, &outRGBPremultiplied, NULL))
if (vips_add(srcWithoutAlpha, t2, &outRGBPremultiplied, NULL))
return -1;
vips_object_local(context, outRGBPremultiplied);

Expand All @@ -101,24 +118,15 @@ namespace sharp {
vips_object_local(context, outAlpha);

// Combine RGB and alpha channel into output image:
VipsImage *joined;
if (vips_bandjoin2(outRGBPremultiplied, outAlpha, &joined, NULL))
return -1;

// Return a reference to the composited output image
*outPremultiplied = joined;
return 0;
return vips_bandjoin2(outRGBPremultiplied, outAlpha, out, NULL);
}

/*
* Premultiply alpha channel of `image`.
*/
int Premultiply(VipsObject *context, VipsImage *image, VipsImage **out) {
VipsImage *imagePremultiplied;

#if (VIPS_MAJOR_VERSION >= 9 || (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 1))
if (vips_premultiply(image, &imagePremultiplied, NULL))
return -1;
return vips_premultiply(image, out, NULL);
#else
VipsImage *imageRGB;
if (vips_extract_band(image, &imageRGB, 0, "n", image->Bands - 1, NULL))
Expand All @@ -140,24 +148,16 @@ namespace sharp {
return -1;
vips_object_local(context, imageRGBPremultiplied);

if (vips_bandjoin2(imageRGBPremultiplied, imageAlpha, &imagePremultiplied, NULL))
return -1;
return vips_bandjoin2(imageRGBPremultiplied, imageAlpha, out, NULL);
#endif

// Return a reference to the premultiplied output image
*out = imagePremultiplied;
return 0;
}

/*
* Unpremultiply alpha channel of `image`.
*/
int Unpremultiply(VipsObject *context, VipsImage *image, VipsImage **out) {
VipsImage *imageUnpremultiplied;

#if (VIPS_MAJOR_VERSION >= 9 || (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 1))
if (vips_unpremultiply(image, &imageUnpremultiplied, NULL))
return -1;
return vips_unpremultiply(image, out, NULL);
#else
VipsImage *imageRGBPremultipliedTransformed;
if (vips_extract_band(image, &imageRGBPremultipliedTransformed, 0, "n", image->Bands - 1, NULL))
Expand All @@ -179,13 +179,8 @@ namespace sharp {
return -1;
vips_object_local(context, imageRGBUnpremultipliedTransformed);

if (vips_bandjoin2(imageRGBUnpremultipliedTransformed, imageAlphaTransformed, &imageUnpremultiplied, NULL))
return -1;
return vips_bandjoin2(imageRGBUnpremultipliedTransformed, imageAlphaTransformed, out, NULL);
#endif

// Return a reference to the unpremultiplied output image
*out = imageUnpremultiplied;
return 0;
}

} // namespace sharp
42 changes: 17 additions & 25 deletions src/pipeline.cc
Original file line number Diff line number Diff line change
Expand Up @@ -484,23 +484,22 @@ class PipelineWorker : public NanAsyncWorker {
}
}

// Premultiply image alpha channel before all transformations to avoid
// dark fringing around bright pixels
// See: http://entropymine.com/imageworsener/resizealpha/
bool shouldAffineTransform = xresidual != 0.0 || yresidual != 0.0;
bool shouldBlur = baton->blurSigma != 0.0;
bool shouldSharpen = baton->sharpenRadius != 0;
bool shouldTransform = shouldAffineTransform || shouldBlur || shouldSharpen;
bool hasOverlay = !baton->overlayPath.empty();
bool shouldPremultiplyAlpha = HasAlpha(image) && image->Bands == 4 && (shouldTransform || hasOverlay);

// Premultiply image alpha channel before all transformations to avoid
// dark fringing around bright pixels
// See: http://entropymine.com/imageworsener/resizealpha/
if (shouldPremultiplyAlpha) {
VipsImage *imagePremultiplied;
if (Premultiply(hook, image, &imagePremultiplied)) {
(baton->err).append("Failed to premultiply alpha channel.");
return Error();
}

vips_object_local(hook, imagePremultiplied);
image = imagePremultiplied;
}
Expand Down Expand Up @@ -820,41 +819,34 @@ class PipelineWorker : public NanAsyncWorker {
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;
(baton->err).append("Overlay image has corrupt header");
return Error();
} else {
vips_object_local(hook, overlayImage);
}
} else {
(baton->err).append("Overlay input file is of an unsupported image format");
}

if (overlayImage == NULL || overlayImageType == ImageType::UNKNOWN) {
(baton->err).append("Overlay image is of an unsupported image format");
return Error();
}

if (!HasAlpha(overlayImage)) {
(baton->err).append("Overlay input must have an alpha channel");
(baton->err).append("Overlay image 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");
if (overlayImage->Xsize != image->Xsize && overlayImage->Ysize != image->Ysize) {
(baton->err).append("Overlay image must have same dimensions as resized image");
return Error();
}

if (image->Bands != 4) {
(baton->err).append("Input image must have 4 channels");
// Ensure overlay is sRGB
VipsImage *overlayImageRGB;
if (vips_colourspace(overlayImage, &overlayImageRGB, VIPS_INTERPRETATION_sRGB, NULL)) {
return Error();
}
vips_object_local(hook, overlayImageRGB);

// Premultiply overlay
VipsImage *overlayImagePremultiplied;
if (Premultiply(hook, overlayImage, &overlayImagePremultiplied)) {
if (Premultiply(hook, overlayImageRGB, &overlayImagePremultiplied)) {
(baton->err).append("Failed to premultiply alpha channel of overlay image.");
return Error();
}
Expand Down Expand Up @@ -883,14 +875,14 @@ class PipelineWorker : public NanAsyncWorker {

// Convert image to sRGB, if not already
if (image->Type != VIPS_INTERPRETATION_sRGB) {
// Switch intrepretation to sRGB
// Switch interpretation to sRGB
VipsImage *rgb;
if (vips_colourspace(image, &rgb, VIPS_INTERPRETATION_sRGB, NULL)) {
return Error();
}
vips_object_local(hook, rgb);
image = rgb;
// Tranform colours from embedded profile to sRGB profile
// Transform colours from embedded profile to sRGB profile
if (baton->withMetadata && HasProfile(image)) {
VipsImage *profiled;
if (vips_icc_transform(image, &profiled, srgbProfile.c_str(), "embedded", TRUE, NULL)) {
Expand Down
Binary file added test/fixtures/5_webp_a.webp
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/fixtures/expected/overlay-jpeg-with-rgb.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/fixtures/expected/overlay-jpeg-with-webp.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions test/fixtures/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ module.exports = {
inputPngAlphaPremultiplicationLarge: getPath('alpha-premultiply-2048x1536-paper.png'),

inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif
inputSvg: getPath('Wikimedia-logo.svg'), // http://commons.wikimedia.org/wiki/File:Wikimedia-logo.svg
Expand Down
Loading

0 comments on commit 1091be3

Please sign in to comment.