overlayWith improvements: diff sizes/formats, gravity, buffer input

This commit is contained in:
Lovell Fuller
2016-02-29 14:30:29 +00:00
parent 55f204c6f9
commit d92ea31858
21 changed files with 407 additions and 242 deletions

View File

@@ -1,10 +1,12 @@
#include <tuple>
#include <algorithm>
#include <utility>
#include <cmath>
#include <tuple>
#include <utility>
#include <vips/vips8>
#include <node.h>
#include <node_buffer.h>
#include <vips/vips8>
#include "nan.h"
@@ -62,133 +64,22 @@ using sharp::IsWebp;
using sharp::IsTiff;
using sharp::IsDz;
using sharp::FreeCallback;
using sharp::CalculateCrop;
using sharp::counterProcess;
using sharp::counterQueue;
enum class Canvas {
CROP,
EMBED,
MAX,
MIN,
IGNORE_ASPECT
};
struct PipelineBaton {
std::string fileIn;
char *bufferIn;
size_t bufferInLength;
std::string iccProfilePath;
int limitInputPixels;
std::string density;
int rawWidth;
int rawHeight;
int rawChannels;
std::string formatOut;
std::string fileOut;
void *bufferOut;
size_t bufferOutLength;
int topOffsetPre;
int leftOffsetPre;
int widthPre;
int heightPre;
int topOffsetPost;
int leftOffsetPost;
int widthPost;
int heightPost;
int width;
int height;
int channels;
Canvas canvas;
int gravity;
std::string interpolator;
double background[4];
bool flatten;
bool negate;
double blurSigma;
int sharpenRadius;
double sharpenFlat;
double sharpenJagged;
int threshold;
std::string overlayPath;
double gamma;
bool greyscale;
bool normalize;
int angle;
bool rotateBeforePreExtract;
bool flip;
bool flop;
bool progressive;
bool withoutEnlargement;
VipsAccess accessMethod;
int quality;
int compressionLevel;
bool withoutAdaptiveFiltering;
bool withoutChromaSubsampling;
bool trellisQuantisation;
bool overshootDeringing;
bool optimiseScans;
std::string err;
bool withMetadata;
int withMetadataOrientation;
int tileSize;
int tileOverlap;
PipelineBaton():
bufferInLength(0),
limitInputPixels(0),
density(""),
rawWidth(0),
rawHeight(0),
rawChannels(0),
formatOut(""),
fileOut(""),
bufferOutLength(0),
topOffsetPre(-1),
topOffsetPost(-1),
channels(0),
canvas(Canvas::CROP),
gravity(0),
flatten(false),
negate(false),
blurSigma(0.0),
sharpenRadius(0),
sharpenFlat(1.0),
sharpenJagged(2.0),
threshold(0),
gamma(0.0),
greyscale(false),
normalize(false),
angle(0),
flip(false),
flop(false),
progressive(false),
withoutEnlargement(false),
quality(80),
compressionLevel(6),
withoutAdaptiveFiltering(false),
withoutChromaSubsampling(false),
trellisQuantisation(false),
overshootDeringing(false),
optimiseScans(false),
withMetadata(false),
withMetadataOrientation(-1),
tileSize(256),
tileOverlap(0) {
background[0] = 0.0;
background[1] = 0.0;
background[2] = 0.0;
background[3] = 255.0;
}
};
class PipelineWorker : public AsyncWorker {
public:
PipelineWorker(Callback *callback, PipelineBaton *baton, Callback *queueListener, const Local<Object> &bufferIn) :
PipelineWorker(Callback *callback, PipelineBaton *baton, Callback *queueListener,
const Local<Object> &bufferIn, const Local<Object> &overlayBufferIn) :
AsyncWorker(callback), baton(baton), queueListener(queueListener) {
if (baton->bufferInLength > 0) {
SaveToPersistent("bufferIn", bufferIn);
}
if (baton->overlayBufferInLength > 0) {
SaveToPersistent("overlayBufferIn", overlayBufferIn);
}
}
~PipelineWorker() {}
@@ -508,11 +399,19 @@ class PipelineWorker : public AsyncWorker {
}
}
// Ensure image has an alpha channel when there is an overlay
bool hasOverlay = baton->overlayBufferInLength > 0 || !baton->overlayFileIn.empty();
if (hasOverlay && !HasAlpha(image)) {
double multiplier = (image.interpretation() == VIPS_INTERPRETATION_RGB16) ? 256.0 : 1.0;
image = image.bandjoin(
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier)
);
}
bool shouldAffineTransform = xresidual != 0.0 || yresidual != 0.0;
bool shouldBlur = baton->blurSigma != 0.0;
bool shouldSharpen = baton->sharpenRadius != 0;
bool shouldThreshold = baton->threshold != 0;
bool hasOverlay = !baton->overlayPath.empty();
bool shouldPremultiplyAlpha = HasAlpha(image) &&
(shouldAffineTransform || shouldBlur || shouldSharpen || hasOverlay);
@@ -634,38 +533,41 @@ class PipelineWorker : public AsyncWorker {
// Composite with overlay, if present
if (hasOverlay) {
VImage overlayImage;
ImageType overlayImageType = DetermineImageType(baton->overlayPath.data());
if (overlayImageType != ImageType::UNKNOWN) {
overlayImage = VImage::new_from_file(
baton->overlayPath.data(),
VImage::option()->set("access", baton->accessMethod)
);
ImageType overlayImageType = ImageType::UNKNOWN;
if (baton->overlayBufferInLength > 0) {
// Overlay with image from buffer
overlayImageType = DetermineImageType(baton->overlayBufferIn, baton->overlayBufferInLength);
if (overlayImageType != ImageType::UNKNOWN) {
try {
overlayImage = VImage::new_from_buffer(baton->overlayBufferIn, baton->overlayBufferInLength,
nullptr, VImage::option()->set("access", baton->accessMethod));
} catch (...) {
(baton->err).append("Overlay buffer has corrupt header");
overlayImageType = ImageType::UNKNOWN;
}
} else {
(baton->err).append("Overlay buffer contains unsupported image format");
}
} else {
(baton->err).append("Overlay image is of an unsupported image format");
// Overlay with image from file
overlayImageType = DetermineImageType(baton->overlayFileIn.data());
if (overlayImageType != ImageType::UNKNOWN) {
try {
overlayImage = VImage::new_from_file(baton->overlayFileIn.data(),
VImage::option()->set("access", baton->accessMethod));
} catch (...) {
(baton->err).append("Overlay file has corrupt header");
overlayImageType = ImageType::UNKNOWN;
}
}
}
if (overlayImageType == ImageType::UNKNOWN) {
return Error();
}
if (image.format() != VIPS_FORMAT_UCHAR && image.format() != VIPS_FORMAT_FLOAT) {
(baton->err).append("Expected image band format to be uchar or float: ");
(baton->err).append(vips_enum_nick(VIPS_TYPE_BAND_FORMAT, image.format()));
return Error();
}
if (overlayImage.format() != VIPS_FORMAT_UCHAR && overlayImage.format() != VIPS_FORMAT_FLOAT) {
(baton->err).append("Expected overlay image band format to be uchar or float: ");
(baton->err).append(vips_enum_nick(VIPS_TYPE_BAND_FORMAT, overlayImage.format()));
return Error();
}
if (!HasAlpha(overlayImage)) {
(baton->err).append("Overlay image must have an alpha channel");
return Error();
}
if (overlayImage.width() != image.width() && overlayImage.height() != image.height()) {
(baton->err).append("Overlay image must have same dimensions as resized image");
return Error();
}
// Ensure overlay is sRGB and premutiplied
// Ensure overlay is premultiplied sRGB
overlayImage = overlayImage.colourspace(VIPS_INTERPRETATION_sRGB).premultiply();
image = Composite(overlayImage, image);
// Composite images with given gravity
image = Composite(overlayImage, image, baton->overlayGravity);
}
// Reverse premultiplication after all transformations:
@@ -708,6 +610,9 @@ class PipelineWorker : public AsyncWorker {
SetExifOrientation(image, baton->withMetadataOrientation);
}
// Number of channels used in output image
baton->channels = image.bands();
// Output
if (baton->fileOut == "") {
// Buffer output
@@ -728,6 +633,7 @@ class PipelineWorker : public AsyncWorker {
area->free_fn = nullptr;
vips_area_unref(area);
baton->formatOut = "jpeg";
baton->channels = std::min(baton->channels, 3);
} else if (baton->formatOut == "png" || (baton->formatOut == "input" && inputImageType == ImageType::PNG)) {
// Write PNG to buffer
VipsArea *area = VIPS_AREA(image.pngsave_buffer(VImage::option()
@@ -800,6 +706,7 @@ class PipelineWorker : public AsyncWorker {
->set("interlace", baton->progressive)
);
baton->formatOut = "jpeg";
baton->channels = std::min(baton->channels, 3);
} else if (baton->formatOut == "png" || isPng || (matchInput && inputImageType == ImageType::PNG)) {
// Write PNG to file
image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
@@ -824,6 +731,7 @@ class PipelineWorker : public AsyncWorker {
->set("compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG)
);
baton->formatOut = "tiff";
baton->channels = std::min(baton->channels, 3);
} else if (baton->formatOut == "dz" || IsDz(baton->fileOut)) {
// Write DZ to file
image.dzsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
@@ -838,8 +746,6 @@ class PipelineWorker : public AsyncWorker {
return Error();
}
}
// Number of channels used in output image
baton->channels = image.bands();
} catch (VError const &err) {
(baton->err).append(err.what());
}
@@ -890,10 +796,13 @@ class PipelineWorker : public AsyncWorker {
}
}
// Dispose of Persistent wrapper around input Buffer so it can be garbage collected
// Dispose of Persistent wrapper around input Buffers so they can be garbage collected
if (baton->bufferInLength > 0) {
GetFromPersistent("bufferIn");
}
if (baton->overlayBufferInLength > 0) {
GetFromPersistent("overlayBufferIn");
}
delete baton;
// Decrement processing task counter
@@ -944,46 +853,6 @@ class PipelineWorker : public AsyncWorker {
return std::make_tuple(rotate, flip, flop);
}
/*
Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity.
*/
std::tuple<int, int>
CalculateCrop(int const inWidth, int const inHeight, int const outWidth, int const outHeight, int const gravity) {
int left = 0;
int top = 0;
switch (gravity) {
case 1: // North
left = (inWidth - outWidth + 1) / 2;
break;
case 2: // East
left = inWidth - outWidth;
top = (inHeight - outHeight + 1) / 2;
break;
case 3: // South
left = (inWidth - outWidth + 1) / 2;
top = inHeight - outHeight;
break;
case 4: // West
top = (inHeight - outHeight + 1) / 2;
break;
case 5: // Northeast
left = inWidth - outWidth;
break;
case 6: // Southeast
left = inWidth - outWidth;
top = inHeight - outHeight;
case 7: // Southwest
top = inHeight - outHeight;
case 8: // Northwest
break;
default: // Centre
left = (inWidth - outWidth + 1) / 2;
top = (inHeight - outHeight + 1) / 2;
}
return std::make_tuple(left, top);
}
/*
Calculate integral shrink given factor and interpolator window size
*/
@@ -1088,7 +957,14 @@ NAN_METHOD(pipeline) {
baton->background[i] = To<int32_t>(Get(background, i).ToLocalChecked()).FromJust();
}
// Overlay options
baton->overlayPath = attrAsStr(options, "overlayPath");
baton->overlayFileIn = attrAsStr(options, "overlayFileIn");
Local<Object> overlayBufferIn;
if (node::Buffer::HasInstance(Get(options, New("overlayBufferIn").ToLocalChecked()).ToLocalChecked())) {
overlayBufferIn = Get(options, New("overlayBufferIn").ToLocalChecked()).ToLocalChecked().As<Object>();
baton->overlayBufferInLength = node::Buffer::Length(overlayBufferIn);
baton->overlayBufferIn = node::Buffer::Data(overlayBufferIn);
}
baton->overlayGravity = attrAs<int32_t>(options, "overlayGravity");
// Resize options
baton->withoutEnlargement = attrAs<bool>(options, "withoutEnlargement");
baton->gravity = attrAs<int32_t>(options, "gravity");
@@ -1131,7 +1007,7 @@ NAN_METHOD(pipeline) {
// Join queue for worker thread
Callback *callback = new Callback(info[1].As<Function>());
AsyncQueueWorker(new PipelineWorker(callback, baton, queueListener, bufferIn));
AsyncQueueWorker(new PipelineWorker(callback, baton, queueListener, bufferIn, overlayBufferIn));
// Increment queued task counter
g_atomic_int_inc(&counterQueue);