Refactor internal 'resize' to more apt 'pipeline'

Refactor 'composite' C to C++ 'operations'
This commit is contained in:
Lovell Fuller 2015-06-01 16:33:26 +01:00
parent e2c53b59ce
commit 36be0453dd
11 changed files with 265 additions and 273 deletions

View File

@ -3,9 +3,9 @@
'target_name': 'sharp',
'sources': [
'src/common.cc',
'src/composite.c',
'src/metadata.cc',
'src/resize.cc',
'src/operations.cc',
'src/pipeline.cc',
'src/sharp.cc',
'src/utilities.cc'
],

View File

@ -567,7 +567,7 @@ Sharp.prototype.toFile = function(output, callback) {
}
} else {
this.options.output = output;
return this._sharp(callback);
return this._pipeline(callback);
}
}
return this;
@ -577,7 +577,7 @@ Sharp.prototype.toFile = function(output, callback) {
Write output to a Buffer
*/
Sharp.prototype.toBuffer = function(callback) {
return this._sharp(callback);
return this._pipeline(callback);
};
/*
@ -640,7 +640,7 @@ Sharp.prototype.toFormat = function(format) {
Sharp.prototype._read = function() {
if (!this.options.streamOut) {
this.options.streamOut = true;
this._sharp();
this._pipeline();
}
};
@ -648,18 +648,18 @@ Sharp.prototype._read = function() {
Invoke the C++ image processing pipeline
Supports callback, stream and promise variants
*/
Sharp.prototype._sharp = function(callback) {
Sharp.prototype._pipeline = function(callback) {
var that = this;
if (typeof callback === 'function') {
// output=file/buffer
if (this.options.streamIn) {
// output=file/buffer, input=stream
this.on('finish', function() {
sharp.resize(that.options, callback);
sharp.pipeline(that.options, callback);
});
} else {
// output=file/buffer, input=file/buffer
sharp.resize(this.options, callback);
sharp.pipeline(this.options, callback);
}
return this;
} else if (this.options.streamOut) {
@ -667,9 +667,9 @@ Sharp.prototype._sharp = function(callback) {
if (this.options.streamIn) {
// output=stream, input=stream
this.on('finish', function() {
sharp.resize(that.options, function(err, data) {
sharp.pipeline(that.options, function(err, data) {
if (err) {
that.emit('error', new Error(err));
that.emit('error', err);
} else {
that.push(data);
}
@ -678,9 +678,9 @@ Sharp.prototype._sharp = function(callback) {
});
} else {
// output=stream, input=file/buffer
sharp.resize(this.options, function(err, data) {
sharp.pipeline(this.options, function(err, data) {
if (err) {
that.emit('error', new Error(err));
that.emit('error', err);
} else {
that.push(data);
}
@ -694,7 +694,7 @@ Sharp.prototype._sharp = function(callback) {
// output=promise, input=stream
return new BluebirdPromise(function(resolve, reject) {
that.on('finish', function() {
sharp.resize(that.options, function(err, data) {
sharp.pipeline(that.options, function(err, data) {
if (err) {
reject(err);
} else {
@ -706,7 +706,7 @@ Sharp.prototype._sharp = function(callback) {
} else {
// output=promise, input=file/buffer
return new BluebirdPromise(function(resolve, reject) {
sharp.resize(that.options, function(err, data) {
sharp.pipeline(that.options, function(err, data) {
if (err) {
reject(err);
} else {

View File

@ -1,212 +0,0 @@
#include <stdio.h>
#include <vips/vips.h>
// Constants
const int ALPHA_BAND_INDEX = 3;
const int NUM_COLOR_BANDS = 3;
// TODO: Copied from `common.cc`. Deduplicate once this becomes a C++ module.
int HasAlpha(VipsImage *image) {
return (
(image->Bands == 2 && image->Type == VIPS_INTERPRETATION_B_W) ||
(image->Bands == 4 && image->Type != VIPS_INTERPRETATION_CMYK) ||
(image->Bands == 5 && image->Type == VIPS_INTERPRETATION_CMYK)
);
}
/*
Composite images `src` and `dst` with premultiplied alpha channel and output
image with premultiplied alpha.
*/
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", NUM_COLOR_BANDS, NULL))
return -1;
vips_object_local(context, srcRGBPremultiplied);
VipsImage *dstRGBPremultiplied;
if (vips_extract_band(dstPremultiplied, &dstRGBPremultiplied, 0, "n", NUM_COLOR_BANDS, NULL))
return -1;
vips_object_local(context, dstRGBPremultiplied);
// Extract alpha bands:
VipsImage *srcAlpha;
if (vips_extract_band(srcPremultiplied, &srcAlpha, ALPHA_BAND_INDEX, "n", 1, NULL))
return -1;
vips_object_local(context, srcAlpha);
VipsImage *dstAlpha;
if (vips_extract_band(dstPremultiplied, &dstAlpha, ALPHA_BAND_INDEX, "n", 1, NULL))
return -1;
vips_object_local(context, dstAlpha);
// Compute normalized input alpha channels:
VipsImage *srcAlphaNormalized;
if (vips_linear1(srcAlpha, &srcAlphaNormalized, 1.0 / 255.0, 0.0, NULL))
return -1;
vips_object_local(context, srcAlphaNormalized);
VipsImage *dstAlphaNormalized;
if (vips_linear1(dstAlpha, &dstAlphaNormalized, 1.0 / 255.0, 0.0, NULL))
return -1;
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;
if (vips_linear1(srcAlphaNormalized, &t0, -1.0, 1.0, NULL))
return -1;
vips_object_local(context, t0);
VipsImage *t1;
if (vips_multiply(dstAlphaNormalized, t0, &t1, NULL))
return -1;
vips_object_local(context, t1);
VipsImage *outAlphaNormalized;
if (vips_add(srcAlphaNormalized, t1, &outAlphaNormalized, NULL))
return -1;
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
// ^^^^^^^^^^^
// t0
//
// Omit division by `out_a` since `Compose` is supposed to output a
// premultiplied RGBA image as reversal of premultiplication is handled
// externally.
//
VipsImage *t2;
if (vips_multiply(dstRGBPremultiplied, t0, &t2, NULL))
return -1;
vips_object_local(context, t2);
VipsImage *outRGBPremultiplied;
if (vips_add(srcRGBPremultiplied, t2, &outRGBPremultiplied, NULL))
return -1;
vips_object_local(context, outRGBPremultiplied);
// 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(outRGBPremultiplied, outAlpha, &joined, NULL))
return -1;
// Return a reference to the composited output image:
*outPremultiplied = joined;
return 0;
}
/*
* 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;
#else
VipsImage *imageRGB;
if (vips_extract_band(image, &imageRGB, 0, "n", NUM_COLOR_BANDS, NULL))
return -1;
vips_object_local(context, imageRGB);
VipsImage *imageAlpha;
if (vips_extract_band(image, &imageAlpha, ALPHA_BAND_INDEX, "n", 1, NULL))
return -1;
vips_object_local(context, imageAlpha);
VipsImage *imageAlphaNormalized;
if (vips_linear1(imageAlpha, &imageAlphaNormalized, 1.0 / 255.0, 0.0, NULL))
return -1;
vips_object_local(context, imageAlphaNormalized);
VipsImage *imageRGBPremultiplied;
if (vips_multiply(imageRGB, imageAlphaNormalized, &imageRGBPremultiplied, NULL))
return -1;
vips_object_local(context, imageRGBPremultiplied);
if (vips_bandjoin2(imageRGBPremultiplied, imageAlpha, &imagePremultiplied, NULL))
return -1;
#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;
#else
VipsImage *imageRGBPremultipliedTransformed;
if (vips_extract_band(image, &imageRGBPremultipliedTransformed, 0, "n", NUM_COLOR_BANDS, NULL))
return -1;
vips_object_local(context, imageRGBPremultipliedTransformed);
VipsImage *imageAlphaTransformed;
if (vips_extract_band(image, &imageAlphaTransformed, ALPHA_BAND_INDEX, "n", 1, NULL))
return -1;
vips_object_local(context, imageAlphaTransformed);
VipsImage *imageAlphaNormalizedTransformed;
if (vips_linear1(imageAlphaTransformed, &imageAlphaNormalizedTransformed, 1.0 / 255.0, 0.0, NULL))
return -1;
vips_object_local(context, imageAlphaNormalizedTransformed);
VipsImage *imageRGBUnpremultipliedTransformed;
if (vips_divide(imageRGBPremultipliedTransformed, imageAlphaNormalizedTransformed, &imageRGBUnpremultipliedTransformed, NULL))
return -1;
vips_object_local(context, imageRGBUnpremultipliedTransformed);
if (vips_bandjoin2(imageRGBUnpremultipliedTransformed, imageAlphaTransformed, &imageUnpremultiplied, NULL))
return -1;
#endif
// Return a reference to the unpremultiplied output image:
*out = imageUnpremultiplied;
return 0;
}

View File

@ -1,17 +0,0 @@
#ifndef SRC_COMPOSITE_H_
#define SRC_COMPOSITE_H_
#ifdef __cplusplus
extern "C" {
#endif
int Composite(VipsObject *context, VipsImage *src, VipsImage *dst, VipsImage **out);
int Premultiply(VipsObject *context, VipsImage *image, VipsImage **out);
int Unpremultiply(VipsObject *context, VipsImage *image, VipsImage **out);
#ifdef __cplusplus
}
#endif
#endif // SRC_COMPOSITE_H_

191
src/operations.cc Executable file
View File

@ -0,0 +1,191 @@
#include <vips/vips.h>
#include "operations.h"
namespace sharp {
/*
Composite images `src` and `dst` with premultiplied alpha channel and output
image with premultiplied alpha.
*/
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);
VipsImage *dstRGBPremultiplied;
if (vips_extract_band(dstPremultiplied, &dstRGBPremultiplied, 0, "n", dstPremultiplied->Bands - 1, NULL))
return -1;
vips_object_local(context, dstRGBPremultiplied);
// Extract alpha bands:
VipsImage *srcAlpha;
if (vips_extract_band(srcPremultiplied, &srcAlpha, srcPremultiplied->Bands - 1, "n", 1, NULL))
return -1;
vips_object_local(context, srcAlpha);
VipsImage *dstAlpha;
if (vips_extract_band(dstPremultiplied, &dstAlpha, dstPremultiplied->Bands - 1, "n", 1, NULL))
return -1;
vips_object_local(context, dstAlpha);
// Compute normalized input alpha channels:
VipsImage *srcAlphaNormalized;
if (vips_linear1(srcAlpha, &srcAlphaNormalized, 1.0 / 255.0, 0.0, NULL))
return -1;
vips_object_local(context, srcAlphaNormalized);
VipsImage *dstAlphaNormalized;
if (vips_linear1(dstAlpha, &dstAlphaNormalized, 1.0 / 255.0, 0.0, NULL))
return -1;
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;
if (vips_linear1(srcAlphaNormalized, &t0, -1.0, 1.0, NULL))
return -1;
vips_object_local(context, t0);
VipsImage *t1;
if (vips_multiply(dstAlphaNormalized, t0, &t1, NULL))
return -1;
vips_object_local(context, t1);
VipsImage *outAlphaNormalized;
if (vips_add(srcAlphaNormalized, t1, &outAlphaNormalized, NULL))
return -1;
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
// ^^^^^^^^^^^
// t0
//
// Omit division by `out_a` since `Compose` is supposed to output a
// premultiplied RGBA image as reversal of premultiplication is handled
// externally.
//
VipsImage *t2;
if (vips_multiply(dstRGBPremultiplied, t0, &t2, NULL))
return -1;
vips_object_local(context, t2);
VipsImage *outRGBPremultiplied;
if (vips_add(srcRGBPremultiplied, t2, &outRGBPremultiplied, NULL))
return -1;
vips_object_local(context, outRGBPremultiplied);
// 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(outRGBPremultiplied, outAlpha, &joined, NULL))
return -1;
// Return a reference to the composited output image
*outPremultiplied = joined;
return 0;
}
/*
* 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;
#else
VipsImage *imageRGB;
if (vips_extract_band(image, &imageRGB, 0, "n", image->Bands - 1, NULL))
return -1;
vips_object_local(context, imageRGB);
VipsImage *imageAlpha;
if (vips_extract_band(image, &imageAlpha, image->Bands - 1, "n", 1, NULL))
return -1;
vips_object_local(context, imageAlpha);
VipsImage *imageAlphaNormalized;
if (vips_linear1(imageAlpha, &imageAlphaNormalized, 1.0 / 255.0, 0.0, NULL))
return -1;
vips_object_local(context, imageAlphaNormalized);
VipsImage *imageRGBPremultiplied;
if (vips_multiply(imageRGB, imageAlphaNormalized, &imageRGBPremultiplied, NULL))
return -1;
vips_object_local(context, imageRGBPremultiplied);
if (vips_bandjoin2(imageRGBPremultiplied, imageAlpha, &imagePremultiplied, NULL))
return -1;
#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;
#else
VipsImage *imageRGBPremultipliedTransformed;
if (vips_extract_band(image, &imageRGBPremultipliedTransformed, 0, "n", image->Bands - 1, NULL))
return -1;
vips_object_local(context, imageRGBPremultipliedTransformed);
VipsImage *imageAlphaTransformed;
if (vips_extract_band(image, &imageAlphaTransformed, image->Bands - 1, "n", 1, NULL))
return -1;
vips_object_local(context, imageAlphaTransformed);
VipsImage *imageAlphaNormalizedTransformed;
if (vips_linear1(imageAlphaTransformed, &imageAlphaNormalizedTransformed, 1.0 / 255.0, 0.0, NULL))
return -1;
vips_object_local(context, imageAlphaNormalizedTransformed);
VipsImage *imageRGBUnpremultipliedTransformed;
if (vips_divide(imageRGBPremultipliedTransformed, imageAlphaNormalizedTransformed, &imageRGBUnpremultipliedTransformed, NULL))
return -1;
vips_object_local(context, imageRGBUnpremultipliedTransformed);
if (vips_bandjoin2(imageRGBUnpremultipliedTransformed, imageAlphaTransformed, &imageUnpremultiplied, NULL))
return -1;
#endif
// Return a reference to the unpremultiplied output image
*out = imageUnpremultiplied;
return 0;
}
} // namespace sharp

24
src/operations.h Executable file
View File

@ -0,0 +1,24 @@
#ifndef SRC_OPERATIONS_H_
#define SRC_OPERATIONS_H_
namespace sharp {
/*
Composite images `src` and `dst` with premultiplied alpha channel and output
image with premultiplied alpha.
*/
int Composite(VipsObject *context, VipsImage *src, VipsImage *dst, VipsImage **out);
/*
* Premultiply alpha channel of `image`.
*/
int Premultiply(VipsObject *context, VipsImage *image, VipsImage **out);
/*
* Unpremultiply alpha channel of `image`.
*/
int Unpremultiply(VipsObject *context, VipsImage *image, VipsImage **out);
} // namespace sharp
#endif // SRC_OPERATIONS_H_

View File

@ -8,8 +8,8 @@
#include "nan.h"
#include "common.h"
#include "composite.h"
#include "resize.h"
#include "operations.h"
#include "pipeline.h"
using v8::Handle;
using v8::Local;
@ -22,6 +22,10 @@ using v8::Array;
using v8::Function;
using v8::Exception;
using sharp::Composite;
using sharp::Premultiply;
using sharp::Unpremultiply;
using sharp::ImageType;
using sharp::DetermineImageType;
using sharp::InitImage;
@ -53,7 +57,7 @@ enum class Angle {
DLAST
};
struct ResizeBaton {
struct PipelineBaton {
std::string fileIn;
char *bufferIn;
size_t bufferInLength;
@ -105,7 +109,7 @@ struct ResizeBaton {
int tileSize;
int tileOverlap;
ResizeBaton():
PipelineBaton():
bufferInLength(0),
limitInputPixels(0),
outputFormat(""),
@ -154,12 +158,12 @@ static void DeleteBuffer(VipsObject *object, char *buffer) {
}
}
class ResizeWorker : public NanAsyncWorker {
class PipelineWorker : public NanAsyncWorker {
public:
ResizeWorker(NanCallback *callback, ResizeBaton *baton, NanCallback *queueListener) :
PipelineWorker(NanCallback *callback, PipelineBaton *baton, NanCallback *queueListener) :
NanAsyncWorker(callback), baton(baton), queueListener(queueListener) {}
~ResizeWorker() {}
~PipelineWorker() {}
/*
libuv worker
@ -1099,7 +1103,7 @@ class ResizeWorker : public NanAsyncWorker {
}
private:
ResizeBaton *baton;
PipelineBaton *baton;
NanCallback *queueListener;
VipsObject *hook;
@ -1207,13 +1211,13 @@ class ResizeWorker : public NanAsyncWorker {
};
/*
resize(options, output, callback)
pipeline(options, output, callback)
*/
NAN_METHOD(resize) {
NAN_METHOD(pipeline) {
NanScope();
// V8 objects are converted to non-V8 types held in the baton struct
ResizeBaton *baton = new ResizeBaton;
PipelineBaton *baton = new PipelineBaton;
Local<Object> options = args[0]->ToObject();
// Input filename
@ -1300,7 +1304,7 @@ NAN_METHOD(resize) {
// Join queue for worker thread
NanCallback *callback = new NanCallback(args[1].As<Function>());
NanAsyncQueueWorker(new ResizeWorker(callback, baton, queueListener));
NanAsyncQueueWorker(new PipelineWorker(callback, baton, queueListener));
// Increment queued task counter
g_atomic_int_inc(&counterQueue);

8
src/pipeline.h Executable file
View File

@ -0,0 +1,8 @@
#ifndef SRC_PIPELINE_H_
#define SRC_PIPELINE_H_
#include "nan.h"
NAN_METHOD(pipeline);
#endif // SRC_PIPELINE_H_

View File

@ -1,8 +0,0 @@
#ifndef SRC_RESIZE_H_
#define SRC_RESIZE_H_
#include "nan.h"
NAN_METHOD(resize);
#endif // SRC_RESIZE_H_

View File

@ -5,7 +5,7 @@
#include "common.h"
#include "metadata.h"
#include "resize.h"
#include "pipeline.h"
#include "utilities.h"
extern "C" void init(v8::Handle<v8::Object> target) {
@ -18,7 +18,7 @@ extern "C" void init(v8::Handle<v8::Object> target) {
// Methods available to JavaScript
NODE_SET_METHOD(target, "metadata", metadata);
NODE_SET_METHOD(target, "resize", resize);
NODE_SET_METHOD(target, "pipeline", pipeline);
NODE_SET_METHOD(target, "cache", cache);
NODE_SET_METHOD(target, "concurrency", concurrency);
NODE_SET_METHOD(target, "counters", counters);

View File

@ -4,8 +4,8 @@
#include "nan.h"
#include "common.h"
#include "operations.h"
#include "utilities.h"
#include "composite.h"
using v8::Local;
using v8::Object;
@ -13,13 +13,6 @@ using v8::Number;
using v8::String;
using v8::Boolean;
using sharp::DetermineImageType;
using sharp::ImageType;
using sharp::InitImage;
using sharp::HasAlpha;
using sharp::counterQueue;
using sharp::counterProcess;
/*
Get and set cache memory and item limits
*/
@ -68,6 +61,9 @@ NAN_METHOD(concurrency) {
Get internal counters (queued tasks, processing tasks)
*/
NAN_METHOD(counters) {
using sharp::counterProcess;
using sharp::counterQueue;
NanScope();
Local<Object> counters = NanNew<Object>();
counters->Set(NanNew<String>("queue"), NanNew<Number>(counterQueue));
@ -154,6 +150,12 @@ NAN_METHOD(format) {
between two images of the same dimensions and number of channels.
*/
NAN_METHOD(_maxColourDistance) {
using sharp::Premultiply;
using sharp::DetermineImageType;
using sharp::ImageType;
using sharp::InitImage;
using sharp::HasAlpha;
NanScope();
// Create "hook" VipsObject to hang image references from