mirror of
https://github.com/lovell/sharp.git
synced 2025-07-10 19:10:14 +02:00
Add raw pixel data support to boolean and withOverlay ops
The previously-scattered image opening logic has been refactored to a single ImageDescriptor struct/Object available to both JS and C++ code This removed about 150 LOC but more importantly reduces the complexity of adding/exposing new operations that require an input image.
This commit is contained in:
parent
36bfbdee0d
commit
e6bfa52b0b
11
docs/api.md
11
docs/api.md
@ -457,7 +457,7 @@ Overlay (composite) a image containing an alpha channel over the processed (resi
|
|||||||
|
|
||||||
`image` is one of the following, and must be the same size or smaller than the processed image:
|
`image` is one of the following, and must be the same size or smaller than the processed image:
|
||||||
|
|
||||||
* Buffer containing PNG, WebP, GIF or SVG image data, or
|
* Buffer containing PNG, WebP, GIF, SVG, raw pixel image data, or
|
||||||
* String containing the path to an image file, with most major transparency formats supported.
|
* String containing the path to an image file, with most major transparency formats supported.
|
||||||
|
|
||||||
`options`, if present, is an Object with the following optional attributes:
|
`options`, if present, is an Object with the following optional attributes:
|
||||||
@ -467,6 +467,7 @@ Overlay (composite) a image containing an alpha channel over the processed (resi
|
|||||||
* `left` is an integral Number representing the pixel offset from the left edge.
|
* `left` is an integral Number representing the pixel offset from the left edge.
|
||||||
* `tile` is a Boolean, defaulting to `false`. When set to `true` repeats the overlay image across the entire image with the given `gravity`.
|
* `tile` is a Boolean, defaulting to `false`. When set to `true` repeats the overlay image across the entire image with the given `gravity`.
|
||||||
* `cutout` is a Boolean, defaulting to `false`. When set to `true` applies only the alpha channel of the overlay image to the image to be overlaid, giving the appearance of one image being cut out of another.
|
* `cutout` is a Boolean, defaulting to `false`. When set to `true` applies only the alpha channel of the overlay image to the image to be overlaid, giving the appearance of one image being cut out of another.
|
||||||
|
* `raw` an Object containing `width`, `height` and `channels` when providing uncompressed data.
|
||||||
|
|
||||||
If both `top` and `left` are provided, they take precedence over `gravity`.
|
If both `top` and `left` are provided, they take precedence over `gravity`.
|
||||||
|
|
||||||
@ -523,11 +524,11 @@ sharp('input.png')
|
|||||||
In the above example if `input.png` is a 3 channel RGB image, `output.png` will be a 1 channel grayscale image where each pixel `P = R & G & B`.
|
In the above example if `input.png` is a 3 channel RGB image, `output.png` will be a 1 channel grayscale image where each pixel `P = R & G & B`.
|
||||||
For example, if `I(1,1) = [247, 170, 14] = [0b11110111, 0b10101010, 0b00001111]` then `O(1,1) = 0b11110111 & 0b10101010 & 0b00001111 = 0b00000010 = 2`.
|
For example, if `I(1,1) = [247, 170, 14] = [0b11110111, 0b10101010, 0b00001111]` then `O(1,1) = 0b11110111 & 0b10101010 & 0b00001111 = 0b00000010 = 2`.
|
||||||
|
|
||||||
#### boolean(image, operation)
|
#### boolean(image, operation, [options])
|
||||||
|
|
||||||
Perform a bitwise boolean operation with `image`, where `image` is one of the following:
|
Perform a bitwise boolean operation with `image`, where `image` is one of the following:
|
||||||
|
|
||||||
* Buffer containing PNG, WebP, GIF or SVG image data, or
|
* Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
|
||||||
* String containing the path to an image file
|
* String containing the path to an image file
|
||||||
|
|
||||||
This operation creates an output image where each pixel is the result of the selected bitwise boolean `operation` between the corresponding pixels of the input images.
|
This operation creates an output image where each pixel is the result of the selected bitwise boolean `operation` between the corresponding pixels of the input images.
|
||||||
@ -537,6 +538,10 @@ The boolean operation can be one of the following:
|
|||||||
* `or` performs a bitwise or operation, like the c-operator `|`.
|
* `or` performs a bitwise or operation, like the c-operator `|`.
|
||||||
* `eor` performs a bitwise exclusive or operation, like the c-operator `^`.
|
* `eor` performs a bitwise exclusive or operation, like the c-operator `^`.
|
||||||
|
|
||||||
|
`options`, if present, is an Object with the following optional attributes:
|
||||||
|
|
||||||
|
* `raw` an Object containing `width`, `height` and `channels` when providing uncompressed data.
|
||||||
|
|
||||||
### Output
|
### Output
|
||||||
|
|
||||||
#### toFile(path, [callback])
|
#### toFile(path, [callback])
|
||||||
|
@ -21,11 +21,16 @@ Requires libvips v8.3.2
|
|||||||
[#511](https://github.com/lovell/sharp/pull/511)
|
[#511](https://github.com/lovell/sharp/pull/511)
|
||||||
[@mhirsch](https://github.com/mhirsch)
|
[@mhirsch](https://github.com/mhirsch)
|
||||||
|
|
||||||
|
* Add support for raw pixel data with boolean and withOverlay operations.
|
||||||
|
[#516](https://github.com/lovell/sharp/pull/516)
|
||||||
|
[@mhirsch](https://github.com/mhirsch)
|
||||||
|
|
||||||
* Ensure ICC profiles are removed from PNG output unless withMetadata used.
|
* Ensure ICC profiles are removed from PNG output unless withMetadata used.
|
||||||
[#521](https://github.com/lovell/sharp/issues/521)
|
[#521](https://github.com/lovell/sharp/issues/521)
|
||||||
[@ChrisPinewood](https://github.com/ChrisPinewood)
|
[@ChrisPinewood](https://github.com/ChrisPinewood)
|
||||||
|
|
||||||
* Remove deprecated interpolateWith method - use resize(w, h, { interpolator: ... })
|
* Remove deprecated interpolateWith method - use resize(w, h, { interpolator: ... })
|
||||||
|
[#310](https://github.com/lovell/sharp/issues/310)
|
||||||
|
|
||||||
### v0.15 - "*outfit*"
|
### v0.15 - "*outfit*"
|
||||||
|
|
||||||
|
118
index.js
118
index.js
@ -42,14 +42,8 @@ var Sharp = function(input, options) {
|
|||||||
stream.Duplex.call(this);
|
stream.Duplex.call(this);
|
||||||
this.options = {
|
this.options = {
|
||||||
// input options
|
// input options
|
||||||
bufferIn: [],
|
|
||||||
streamIn: false,
|
|
||||||
sequentialRead: false,
|
sequentialRead: false,
|
||||||
limitInputPixels: maximum.pixels,
|
limitInputPixels: maximum.pixels,
|
||||||
density: 72,
|
|
||||||
rawWidth: 0,
|
|
||||||
rawHeight: 0,
|
|
||||||
rawChannels: 0,
|
|
||||||
// ICC profiles
|
// ICC profiles
|
||||||
iccProfilePath: path.join(__dirname, 'icc') + path.sep,
|
iccProfilePath: path.join(__dirname, 'icc') + path.sep,
|
||||||
// resize options
|
// resize options
|
||||||
@ -93,8 +87,6 @@ var Sharp = function(input, options) {
|
|||||||
booleanBufferIn: null,
|
booleanBufferIn: null,
|
||||||
booleanFileIn: '',
|
booleanFileIn: '',
|
||||||
// overlay
|
// overlay
|
||||||
overlayFileIn: '',
|
|
||||||
overlayBufferIn: null,
|
|
||||||
overlayGravity: 0,
|
overlayGravity: 0,
|
||||||
overlayXOffset : -1,
|
overlayXOffset : -1,
|
||||||
overlayYOffset : -1,
|
overlayYOffset : -1,
|
||||||
@ -122,19 +114,7 @@ var Sharp = function(input, options) {
|
|||||||
module.exports.queue.emit('change', queueLength);
|
module.exports.queue.emit('change', queueLength);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (isString(input)) {
|
this.options.input = this._createInputDescriptor(input, options, { allowStream: true });
|
||||||
// input=file
|
|
||||||
this.options.fileIn = input;
|
|
||||||
} else if (isBuffer(input)) {
|
|
||||||
// input=buffer
|
|
||||||
this.options.bufferIn = input;
|
|
||||||
} else if (!isDefined(input)) {
|
|
||||||
// input=stream
|
|
||||||
this.options.streamIn = true;
|
|
||||||
} else {
|
|
||||||
throw new Error('Unsupported input ' + typeof input);
|
|
||||||
}
|
|
||||||
this._inputOptions(options);
|
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
module.exports = Sharp;
|
module.exports = Sharp;
|
||||||
@ -187,37 +167,50 @@ var contains = function(val, list) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Set input-related options
|
Create Object containing input and input-related options
|
||||||
density: DPI at which to load vector images via libmagick
|
|
||||||
*/
|
*/
|
||||||
Sharp.prototype._inputOptions = function(options) {
|
Sharp.prototype._createInputDescriptor = function(input, inputOptions, containerOptions) {
|
||||||
if (isObject(options)) {
|
var inputDescriptor = {};
|
||||||
// Density
|
if (isString(input)) {
|
||||||
if (isDefined(options.density)) {
|
// filesystem
|
||||||
if (isInteger(options.density) && inRange(options.density, 1, 2400)) {
|
inputDescriptor.file = input;
|
||||||
this.options.density = options.density;
|
} else if (isBuffer(input)) {
|
||||||
|
// Buffer
|
||||||
|
inputDescriptor.buffer = input;
|
||||||
|
} else if (!isDefined(input) && isObject(containerOptions) && containerOptions.allowStream) {
|
||||||
|
// Stream
|
||||||
|
inputDescriptor.buffer = [];
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid density (1 to 2400) ' + options.density);
|
throw new Error('Unsupported input ' + typeof input);
|
||||||
|
}
|
||||||
|
if (isObject(inputOptions)) {
|
||||||
|
// Density
|
||||||
|
if (isDefined(inputOptions.density)) {
|
||||||
|
if (isInteger(inputOptions.density) && inRange(inputOptions.density, 1, 2400)) {
|
||||||
|
inputDescriptor.density = inputOptions.density;
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid density (1 to 2400) ' + inputOptions.density);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Raw pixel input
|
// Raw pixel input
|
||||||
if (isDefined(options.raw)) {
|
if (isDefined(inputOptions.raw)) {
|
||||||
if (
|
if (
|
||||||
isObject(options.raw) &&
|
isObject(inputOptions.raw) &&
|
||||||
isInteger(options.raw.width) && inRange(options.raw.width, 1, maximum.width) &&
|
isInteger(inputOptions.raw.width) && inRange(inputOptions.raw.width, 1, maximum.width) &&
|
||||||
isInteger(options.raw.height) && inRange(options.raw.height, 1, maximum.height) &&
|
isInteger(inputOptions.raw.height) && inRange(inputOptions.raw.height, 1, maximum.height) &&
|
||||||
isInteger(options.raw.channels) && inRange(options.raw.channels, 1, 4)
|
isInteger(inputOptions.raw.channels) && inRange(inputOptions.raw.channels, 1, 4)
|
||||||
) {
|
) {
|
||||||
this.options.rawWidth = options.raw.width;
|
inputDescriptor.rawWidth = inputOptions.raw.width;
|
||||||
this.options.rawHeight = options.raw.height;
|
inputDescriptor.rawHeight = inputOptions.raw.height;
|
||||||
this.options.rawChannels = options.raw.channels;
|
inputDescriptor.rawChannels = inputOptions.raw.channels;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Expected width, height and channels for raw pixel input');
|
throw new Error('Expected width, height and channels for raw pixel input');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (isDefined(options)) {
|
} else if (isDefined(inputOptions)) {
|
||||||
throw new Error('Invalid input options ' + options);
|
throw new Error('Invalid input options ' + inputOptions);
|
||||||
}
|
}
|
||||||
|
return inputDescriptor;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -225,9 +218,9 @@ Sharp.prototype._inputOptions = function(options) {
|
|||||||
*/
|
*/
|
||||||
Sharp.prototype._write = function(chunk, encoding, callback) {
|
Sharp.prototype._write = function(chunk, encoding, callback) {
|
||||||
/*jslint unused: false */
|
/*jslint unused: false */
|
||||||
if (this.options.streamIn) {
|
if (Array.isArray(this.options.input.buffer)) {
|
||||||
if (isBuffer(chunk)) {
|
if (isBuffer(chunk)) {
|
||||||
this.options.bufferIn.push(chunk);
|
this.options.input.buffer.push(chunk);
|
||||||
callback();
|
callback();
|
||||||
} else {
|
} else {
|
||||||
callback(new Error('Non-Buffer data on Writable Stream'));
|
callback(new Error('Non-Buffer data on Writable Stream'));
|
||||||
@ -238,13 +231,16 @@ Sharp.prototype._write = function(chunk, encoding, callback) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Flattens the array of chunks in bufferIn
|
Flattens the array of chunks accumulated in input.buffer
|
||||||
*/
|
*/
|
||||||
Sharp.prototype._flattenBufferIn = function() {
|
Sharp.prototype._flattenBufferIn = function() {
|
||||||
if (Array.isArray(this.options.bufferIn)) {
|
if (this._isStreamInput()) {
|
||||||
this.options.bufferIn = Buffer.concat(this.options.bufferIn);
|
this.options.input.buffer = Buffer.concat(this.options.input.buffer);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Sharp.prototype._isStreamInput = function() {
|
||||||
|
return Array.isArray(this.options.input.buffer);
|
||||||
|
};
|
||||||
|
|
||||||
// Weighting to apply to image crop
|
// Weighting to apply to image crop
|
||||||
module.exports.gravity = {
|
module.exports.gravity = {
|
||||||
@ -369,14 +365,8 @@ Sharp.prototype.negate = function(negate) {
|
|||||||
/*
|
/*
|
||||||
Bitwise boolean operations between images
|
Bitwise boolean operations between images
|
||||||
*/
|
*/
|
||||||
Sharp.prototype.boolean = function(operand, operator) {
|
Sharp.prototype.boolean = function(operand, operator, options) {
|
||||||
if (isString(operand)) {
|
this.options.boolean = this._createInputDescriptor(operand, options);
|
||||||
this.options.booleanFileIn = operand;
|
|
||||||
} else if (isBuffer(operand)) {
|
|
||||||
this.options.booleanBufferIn = operand;
|
|
||||||
} else {
|
|
||||||
throw new Error('Unsupported boolean operand ' + typeof operand);
|
|
||||||
}
|
|
||||||
if (isString(operator) && contains(operator, ['and', 'or', 'eor'])) {
|
if (isString(operator) && contains(operator, ['and', 'or', 'eor'])) {
|
||||||
this.options.booleanOp = operator;
|
this.options.booleanOp = operator;
|
||||||
} else {
|
} else {
|
||||||
@ -389,13 +379,9 @@ Sharp.prototype.boolean = function(operand, operator) {
|
|||||||
Overlay with another image, using an optional gravity
|
Overlay with another image, using an optional gravity
|
||||||
*/
|
*/
|
||||||
Sharp.prototype.overlayWith = function(overlay, options) {
|
Sharp.prototype.overlayWith = function(overlay, options) {
|
||||||
if (isString(overlay)) {
|
this.options.overlay = this._createInputDescriptor(overlay, options, {
|
||||||
this.options.overlayFileIn = overlay;
|
allowStream: false
|
||||||
} else if (isBuffer(overlay)) {
|
});
|
||||||
this.options.overlayBufferIn = overlay;
|
|
||||||
} else {
|
|
||||||
throw new Error('Unsupported overlay ' + typeof overlay);
|
|
||||||
}
|
|
||||||
if (isObject(options)) {
|
if (isObject(options)) {
|
||||||
if (isDefined(options.tile)) {
|
if (isDefined(options.tile)) {
|
||||||
if (isBoolean(options.tile)) {
|
if (isBoolean(options.tile)) {
|
||||||
@ -909,7 +895,7 @@ Sharp.prototype.toFile = function(fileOut, callback) {
|
|||||||
return BluebirdPromise.reject(errOutputInvalid);
|
return BluebirdPromise.reject(errOutputInvalid);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this.options.fileIn === fileOut) {
|
if (this.options.input.file === fileOut) {
|
||||||
var errOutputIsInput = new Error('Cannot use same file for input and output');
|
var errOutputIsInput = new Error('Cannot use same file for input and output');
|
||||||
if (typeof callback === 'function') {
|
if (typeof callback === 'function') {
|
||||||
callback(errOutputIsInput);
|
callback(errOutputIsInput);
|
||||||
@ -999,7 +985,7 @@ Sharp.prototype._pipeline = function(callback) {
|
|||||||
var that = this;
|
var that = this;
|
||||||
if (typeof callback === 'function') {
|
if (typeof callback === 'function') {
|
||||||
// output=file/buffer
|
// output=file/buffer
|
||||||
if (this.options.streamIn) {
|
if (this._isStreamInput()) {
|
||||||
// output=file/buffer, input=stream
|
// output=file/buffer, input=stream
|
||||||
this.on('finish', function() {
|
this.on('finish', function() {
|
||||||
that._flattenBufferIn();
|
that._flattenBufferIn();
|
||||||
@ -1012,7 +998,7 @@ Sharp.prototype._pipeline = function(callback) {
|
|||||||
return this;
|
return this;
|
||||||
} else if (this.options.streamOut) {
|
} else if (this.options.streamOut) {
|
||||||
// output=stream
|
// output=stream
|
||||||
if (this.options.streamIn) {
|
if (this._isStreamInput()) {
|
||||||
// output=stream, input=stream
|
// output=stream, input=stream
|
||||||
this.on('finish', function() {
|
this.on('finish', function() {
|
||||||
that._flattenBufferIn();
|
that._flattenBufferIn();
|
||||||
@ -1041,7 +1027,7 @@ Sharp.prototype._pipeline = function(callback) {
|
|||||||
return this;
|
return this;
|
||||||
} else {
|
} else {
|
||||||
// output=promise
|
// output=promise
|
||||||
if (this.options.streamIn) {
|
if (this._isStreamInput()) {
|
||||||
// output=promise, input=stream
|
// output=promise, input=stream
|
||||||
return new BluebirdPromise(function(resolve, reject) {
|
return new BluebirdPromise(function(resolve, reject) {
|
||||||
that.on('finish', function() {
|
that.on('finish', function() {
|
||||||
@ -1077,7 +1063,7 @@ Sharp.prototype._pipeline = function(callback) {
|
|||||||
Sharp.prototype.metadata = function(callback) {
|
Sharp.prototype.metadata = function(callback) {
|
||||||
var that = this;
|
var that = this;
|
||||||
if (typeof callback === 'function') {
|
if (typeof callback === 'function') {
|
||||||
if (this.options.streamIn) {
|
if (this._isStreamInput()) {
|
||||||
this.on('finish', function() {
|
this.on('finish', function() {
|
||||||
that._flattenBufferIn();
|
that._flattenBufferIn();
|
||||||
sharp.metadata(that.options, callback);
|
sharp.metadata(that.options, callback);
|
||||||
@ -1087,7 +1073,7 @@ Sharp.prototype.metadata = function(callback) {
|
|||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
} else {
|
} else {
|
||||||
if (this.options.streamIn) {
|
if (this._isStreamInput()) {
|
||||||
return new BluebirdPromise(function(resolve, reject) {
|
return new BluebirdPromise(function(resolve, reject) {
|
||||||
that.on('finish', function() {
|
that.on('finish', function() {
|
||||||
that._flattenBufferIn();
|
that._flattenBufferIn();
|
||||||
|
124
src/common.cc
124
src/common.cc
@ -1,32 +1,53 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <node.h>
|
||||||
|
#include <node_buffer.h>
|
||||||
#include <vips/vips8>
|
#include <vips/vips8>
|
||||||
|
|
||||||
|
#include "nan.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
// Verify platform and compiler compatibility
|
|
||||||
|
|
||||||
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 2))
|
|
||||||
#error libvips version 8.2.0+ required - see sharp.dimens.io/page/install
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
|
|
||||||
#error GCC version 4.6+ is required for C++11 features - see sharp.dimens.io/page/install#prerequisites
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if (defined(__clang__) && defined(__has_feature))
|
|
||||||
#if (!__has_feature(cxx_range_for))
|
|
||||||
#error clang version 3.0+ is required for C++11 features - see sharp.dimens.io/page/install#prerequisites
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define EXIF_IFD0_ORIENTATION "exif-ifd0-Orientation"
|
|
||||||
|
|
||||||
using vips::VImage;
|
using vips::VImage;
|
||||||
|
|
||||||
namespace sharp {
|
namespace sharp {
|
||||||
|
|
||||||
|
// Convenience methods to access the attributes of a v8::Object
|
||||||
|
bool HasAttr(v8::Handle<v8::Object> obj, std::string attr) {
|
||||||
|
return Nan::Has(obj, Nan::New(attr).ToLocalChecked()).FromJust();
|
||||||
|
}
|
||||||
|
std::string AttrAsStr(v8::Handle<v8::Object> obj, std::string attr) {
|
||||||
|
return *Nan::Utf8String(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an InputDescriptor instance from a v8::Object describing an input image
|
||||||
|
InputDescriptor* CreateInputDescriptor(
|
||||||
|
v8::Handle<v8::Object> input, std::vector<v8::Local<v8::Object>> buffersToPersist
|
||||||
|
) {
|
||||||
|
Nan::HandleScope();
|
||||||
|
InputDescriptor *descriptor = new InputDescriptor;
|
||||||
|
if (HasAttr(input, "file")) {
|
||||||
|
descriptor->file = AttrAsStr(input, "file");
|
||||||
|
} else {
|
||||||
|
v8::Local<v8::Object> buffer = AttrAs<v8::Object>(input, "buffer");
|
||||||
|
descriptor->bufferLength = node::Buffer::Length(buffer);
|
||||||
|
descriptor->buffer = node::Buffer::Data(buffer);
|
||||||
|
buffersToPersist.push_back(buffer);
|
||||||
|
}
|
||||||
|
// Density for vector-based input
|
||||||
|
if (HasAttr(input, "density")) {
|
||||||
|
descriptor->density = AttrTo<uint32_t>(input, "density");
|
||||||
|
}
|
||||||
|
// Raw pixel input
|
||||||
|
if (HasAttr(input, "rawChannels")) {
|
||||||
|
descriptor->rawChannels = AttrTo<uint32_t>(input, "rawChannels");
|
||||||
|
descriptor->rawWidth = AttrTo<uint32_t>(input, "rawWidth");
|
||||||
|
descriptor->rawHeight = AttrTo<uint32_t>(input, "rawHeight");
|
||||||
|
}
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
// How many tasks are in the queue?
|
// How many tasks are in the queue?
|
||||||
volatile int counterQueue = 0;
|
volatile int counterQueue = 0;
|
||||||
|
|
||||||
@ -149,6 +170,73 @@ namespace sharp {
|
|||||||
return imageType;
|
return imageType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
|
||||||
|
*/
|
||||||
|
std::tuple<VImage, ImageType> OpenInput(InputDescriptor *descriptor, VipsAccess accessMethod) {
|
||||||
|
VImage image;
|
||||||
|
ImageType imageType;
|
||||||
|
if (descriptor->buffer != nullptr) {
|
||||||
|
// From buffer
|
||||||
|
if (descriptor->rawChannels > 0) {
|
||||||
|
// Raw, uncompressed pixel data
|
||||||
|
image = VImage::new_from_memory(descriptor->buffer, descriptor->bufferLength,
|
||||||
|
descriptor->rawWidth, descriptor->rawHeight, descriptor->rawChannels, VIPS_FORMAT_UCHAR);
|
||||||
|
if (descriptor->rawChannels < 3) {
|
||||||
|
image.get_image()->Type = VIPS_INTERPRETATION_B_W;
|
||||||
|
} else {
|
||||||
|
image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
|
||||||
|
}
|
||||||
|
imageType = ImageType::RAW;
|
||||||
|
} else {
|
||||||
|
// Compressed data
|
||||||
|
imageType = DetermineImageType(descriptor->buffer, descriptor->bufferLength);
|
||||||
|
if (imageType != ImageType::UNKNOWN) {
|
||||||
|
try {
|
||||||
|
vips::VOption *option = VImage::option()->set("access", accessMethod);
|
||||||
|
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
|
||||||
|
option->set("dpi", static_cast<double>(descriptor->density));
|
||||||
|
}
|
||||||
|
if (imageType == ImageType::MAGICK) {
|
||||||
|
option->set("density", std::to_string(descriptor->density).data());
|
||||||
|
}
|
||||||
|
image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
|
||||||
|
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
|
||||||
|
SetDensity(image, descriptor->density);
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
throw vips::VError("Input buffer has corrupt header");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw vips::VError("Input buffer contains unsupported image format");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// From filesystem
|
||||||
|
imageType = DetermineImageType(descriptor->file.data());
|
||||||
|
if (imageType != ImageType::UNKNOWN) {
|
||||||
|
try {
|
||||||
|
vips::VOption *option = VImage::option()->set("access", accessMethod);
|
||||||
|
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
|
||||||
|
option->set("dpi", static_cast<double>(descriptor->density));
|
||||||
|
}
|
||||||
|
if (imageType == ImageType::MAGICK) {
|
||||||
|
option->set("density", std::to_string(descriptor->density).data());
|
||||||
|
}
|
||||||
|
image = VImage::new_from_file(descriptor->file.data(), option);
|
||||||
|
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
|
||||||
|
SetDensity(image, descriptor->density);
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
throw vips::VError("Input file has corrupt header");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw vips::VError("Input file is missing or of an unsupported image format");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::make_tuple(image, imageType);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Does this image have an embedded profile?
|
Does this image have an embedded profile?
|
||||||
*/
|
*/
|
||||||
|
63
src/common.h
63
src/common.h
@ -4,12 +4,70 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
|
||||||
|
#include <node.h>
|
||||||
#include <vips/vips8>
|
#include <vips/vips8>
|
||||||
|
|
||||||
|
#include "nan.h"
|
||||||
|
|
||||||
|
// Verify platform and compiler compatibility
|
||||||
|
|
||||||
|
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 3))
|
||||||
|
#error libvips version 8.3.x required - see sharp.dimens.io/page/install
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
|
||||||
|
#error GCC version 4.6+ is required for C++11 features - see sharp.dimens.io/page/install#prerequisites
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if (defined(__clang__) && defined(__has_feature))
|
||||||
|
#if (!__has_feature(cxx_range_for))
|
||||||
|
#error clang version 3.0+ is required for C++11 features - see sharp.dimens.io/page/install#prerequisites
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define EXIF_IFD0_ORIENTATION "exif-ifd0-Orientation"
|
||||||
|
|
||||||
using vips::VImage;
|
using vips::VImage;
|
||||||
|
|
||||||
namespace sharp {
|
namespace sharp {
|
||||||
|
|
||||||
|
struct InputDescriptor {
|
||||||
|
std::string name;
|
||||||
|
std::string file;
|
||||||
|
char *buffer;
|
||||||
|
size_t bufferLength;
|
||||||
|
int density;
|
||||||
|
int rawChannels;
|
||||||
|
int rawWidth;
|
||||||
|
int rawHeight;
|
||||||
|
|
||||||
|
InputDescriptor():
|
||||||
|
buffer(nullptr),
|
||||||
|
bufferLength(0),
|
||||||
|
density(72),
|
||||||
|
rawChannels(0),
|
||||||
|
rawWidth(0),
|
||||||
|
rawHeight(0) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convenience methods to access the attributes of a v8::Object
|
||||||
|
bool HasAttr(v8::Handle<v8::Object> obj, std::string attr);
|
||||||
|
std::string AttrAsStr(v8::Handle<v8::Object> obj, std::string attr);
|
||||||
|
template<typename T> v8::Local<T> AttrAs(v8::Handle<v8::Object> obj, std::string attr) {
|
||||||
|
return Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked().As<T>();
|
||||||
|
}
|
||||||
|
template<typename T> T AttrTo(v8::Handle<v8::Object> obj, std::string attr) {
|
||||||
|
return Nan::To<T>(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked()).FromJust();
|
||||||
|
}
|
||||||
|
template<typename T> T AttrTo(v8::Handle<v8::Object> obj, int attr) {
|
||||||
|
return Nan::To<T>(Nan::Get(obj, attr).ToLocalChecked()).FromJust();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an InputDescriptor instance from a v8::Object describing an input image
|
||||||
|
InputDescriptor* CreateInputDescriptor(
|
||||||
|
v8::Handle<v8::Object> input, std::vector<v8::Local<v8::Object>> buffersToPersist
|
||||||
|
);
|
||||||
|
|
||||||
enum class ImageType {
|
enum class ImageType {
|
||||||
JPEG,
|
JPEG,
|
||||||
PNG,
|
PNG,
|
||||||
@ -57,6 +115,11 @@ namespace sharp {
|
|||||||
*/
|
*/
|
||||||
ImageType DetermineImageType(char const *file);
|
ImageType DetermineImageType(char const *file);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
|
||||||
|
*/
|
||||||
|
std::tuple<VImage, ImageType> OpenInput(InputDescriptor *descriptor, VipsAccess accessMethod);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Does this image have an embedded profile?
|
Does this image have an embedded profile?
|
||||||
*/
|
*/
|
||||||
|
200
src/metadata.cc
200
src/metadata.cc
@ -1,135 +1,54 @@
|
|||||||
|
#include <numeric>
|
||||||
|
|
||||||
#include <node.h>
|
#include <node.h>
|
||||||
#include <vips/vips8>
|
#include <vips/vips8>
|
||||||
|
|
||||||
#include "nan.h"
|
#include "nan.h"
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "metadata.h"
|
#include "metadata.h"
|
||||||
|
|
||||||
using v8::Handle;
|
class MetadataWorker : public Nan::AsyncWorker {
|
||||||
using v8::Local;
|
|
||||||
using v8::Value;
|
|
||||||
using v8::Object;
|
|
||||||
using v8::Number;
|
|
||||||
using v8::String;
|
|
||||||
using v8::Boolean;
|
|
||||||
using v8::Function;
|
|
||||||
using v8::Exception;
|
|
||||||
|
|
||||||
using Nan::AsyncQueueWorker;
|
|
||||||
using Nan::AsyncWorker;
|
|
||||||
using Nan::Callback;
|
|
||||||
using Nan::HandleScope;
|
|
||||||
using Nan::Utf8String;
|
|
||||||
using Nan::Has;
|
|
||||||
using Nan::Get;
|
|
||||||
using Nan::Set;
|
|
||||||
using Nan::New;
|
|
||||||
using Nan::NewBuffer;
|
|
||||||
using Nan::Null;
|
|
||||||
using Nan::Error;
|
|
||||||
|
|
||||||
using vips::VImage;
|
|
||||||
using vips::VError;
|
|
||||||
|
|
||||||
using sharp::ImageType;
|
|
||||||
using sharp::ImageTypeId;
|
|
||||||
using sharp::DetermineImageType;
|
|
||||||
using sharp::HasProfile;
|
|
||||||
using sharp::HasAlpha;
|
|
||||||
using sharp::ExifOrientation;
|
|
||||||
using sharp::HasDensity;
|
|
||||||
using sharp::GetDensity;
|
|
||||||
using sharp::FreeCallback;
|
|
||||||
using sharp::counterQueue;
|
|
||||||
|
|
||||||
struct MetadataBaton {
|
|
||||||
// Input
|
|
||||||
std::string fileIn;
|
|
||||||
char *bufferIn;
|
|
||||||
size_t bufferInLength;
|
|
||||||
// Output
|
|
||||||
std::string format;
|
|
||||||
int width;
|
|
||||||
int height;
|
|
||||||
std::string space;
|
|
||||||
int channels;
|
|
||||||
int density;
|
|
||||||
bool hasProfile;
|
|
||||||
bool hasAlpha;
|
|
||||||
int orientation;
|
|
||||||
char *exif;
|
|
||||||
size_t exifLength;
|
|
||||||
char *icc;
|
|
||||||
size_t iccLength;
|
|
||||||
std::string err;
|
|
||||||
|
|
||||||
MetadataBaton():
|
|
||||||
bufferInLength(0),
|
|
||||||
density(0),
|
|
||||||
orientation(0),
|
|
||||||
exifLength(0),
|
|
||||||
iccLength(0) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class MetadataWorker : public AsyncWorker {
|
|
||||||
public:
|
public:
|
||||||
MetadataWorker(Callback *callback, MetadataBaton *baton, const Local<Object> &bufferIn) :
|
MetadataWorker(
|
||||||
AsyncWorker(callback), baton(baton) {
|
Nan::Callback *callback, MetadataBaton *baton,
|
||||||
if (baton->bufferInLength > 0) {
|
std::vector<v8::Local<v8::Object>> const buffersToPersist
|
||||||
SaveToPersistent("bufferIn", bufferIn);
|
) : Nan::AsyncWorker(callback), baton(baton), buffersToPersist(buffersToPersist) {
|
||||||
|
// Protect Buffer objects from GC, keyed on index
|
||||||
|
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
|
||||||
|
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
|
||||||
|
SaveToPersistent(index, buffer);
|
||||||
|
return index + 1;
|
||||||
}
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
~MetadataWorker() {}
|
~MetadataWorker() {}
|
||||||
|
|
||||||
void Execute() {
|
void Execute() {
|
||||||
// Decrement queued task counter
|
// Decrement queued task counter
|
||||||
g_atomic_int_dec_and_test(&counterQueue);
|
g_atomic_int_dec_and_test(&sharp::counterQueue);
|
||||||
|
|
||||||
ImageType imageType = ImageType::UNKNOWN;
|
vips::VImage image;
|
||||||
VImage image;
|
sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
|
||||||
if (baton->bufferInLength > 0) {
|
|
||||||
// From buffer
|
|
||||||
imageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
|
|
||||||
if (imageType != ImageType::UNKNOWN) {
|
|
||||||
try {
|
try {
|
||||||
image = VImage::new_from_buffer(baton->bufferIn, baton->bufferInLength, nullptr);
|
std::tie(image, imageType) = OpenInput(baton->input, VIPS_ACCESS_SEQUENTIAL);
|
||||||
} catch (...) {
|
} catch (vips::VError const &err) {
|
||||||
(baton->err).append("Input buffer has corrupt header");
|
(baton->err).append(err.what());
|
||||||
imageType = ImageType::UNKNOWN;
|
|
||||||
}
|
}
|
||||||
} else {
|
if (imageType != sharp::ImageType::UNKNOWN) {
|
||||||
(baton->err).append("Input buffer contains unsupported image format");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// From file
|
|
||||||
imageType = DetermineImageType(baton->fileIn.data());
|
|
||||||
if (imageType != ImageType::UNKNOWN) {
|
|
||||||
try {
|
|
||||||
image = VImage::new_from_file(baton->fileIn.data());
|
|
||||||
} catch (...) {
|
|
||||||
(baton->err).append("Input file has corrupt header");
|
|
||||||
imageType = ImageType::UNKNOWN;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(baton->err).append("Input file is missing or of an unsupported image format");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (imageType != ImageType::UNKNOWN) {
|
|
||||||
// Image type
|
// Image type
|
||||||
baton->format = ImageTypeId(imageType);
|
baton->format = sharp::ImageTypeId(imageType);
|
||||||
// VipsImage attributes
|
// VipsImage attributes
|
||||||
baton->width = image.width();
|
baton->width = image.width();
|
||||||
baton->height = image.height();
|
baton->height = image.height();
|
||||||
baton->space = vips_enum_nick(VIPS_TYPE_INTERPRETATION, image.interpretation());
|
baton->space = vips_enum_nick(VIPS_TYPE_INTERPRETATION, image.interpretation());
|
||||||
baton->channels = image.bands();
|
baton->channels = image.bands();
|
||||||
if (HasDensity(image)) {
|
if (sharp::HasDensity(image)) {
|
||||||
baton->density = GetDensity(image);
|
baton->density = sharp::GetDensity(image);
|
||||||
}
|
}
|
||||||
baton->hasProfile = HasProfile(image);
|
baton->hasProfile = sharp::HasProfile(image);
|
||||||
// Derived attributes
|
// Derived attributes
|
||||||
baton->hasAlpha = HasAlpha(image);
|
baton->hasAlpha = sharp::HasAlpha(image);
|
||||||
baton->orientation = ExifOrientation(image);
|
baton->orientation = sharp::ExifOrientation(image);
|
||||||
// EXIF
|
// EXIF
|
||||||
if (image.get_typeof(VIPS_META_EXIF_NAME) == VIPS_TYPE_BLOB) {
|
if (image.get_typeof(VIPS_META_EXIF_NAME) == VIPS_TYPE_BLOB) {
|
||||||
size_t exifLength;
|
size_t exifLength;
|
||||||
@ -147,53 +66,59 @@ class MetadataWorker : public AsyncWorker {
|
|||||||
baton->iccLength = iccLength;
|
baton->iccLength = iccLength;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
vips_error_clear();
|
vips_error_clear();
|
||||||
vips_thread_shutdown();
|
vips_thread_shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HandleOKCallback () {
|
void HandleOKCallback () {
|
||||||
HandleScope();
|
using Nan::New;
|
||||||
|
using Nan::Set;
|
||||||
|
Nan::HandleScope();
|
||||||
|
|
||||||
Local<Value> argv[2] = { Null(), Null() };
|
v8::Local<v8::Value> argv[2] = { Nan::Null(), Nan::Null() };
|
||||||
if (!baton->err.empty()) {
|
if (!baton->err.empty()) {
|
||||||
// Error
|
argv[0] = Nan::Error(baton->err.data());
|
||||||
argv[0] = Error(baton->err.data());
|
|
||||||
} else {
|
} else {
|
||||||
// Metadata Object
|
// Metadata Object
|
||||||
Local<Object> info = New<Object>();
|
v8::Local<v8::Object> info = New<v8::Object>();
|
||||||
Set(info, New("format").ToLocalChecked(), New<String>(baton->format).ToLocalChecked());
|
Set(info, New("format").ToLocalChecked(), New<v8::String>(baton->format).ToLocalChecked());
|
||||||
Set(info, New("width").ToLocalChecked(), New<Number>(baton->width));
|
Set(info, New("width").ToLocalChecked(), New<v8::Uint32>(baton->width));
|
||||||
Set(info, New("height").ToLocalChecked(), New<Number>(baton->height));
|
Set(info, New("height").ToLocalChecked(), New<v8::Uint32>(baton->height));
|
||||||
Set(info, New("space").ToLocalChecked(), New<String>(baton->space).ToLocalChecked());
|
Set(info, New("space").ToLocalChecked(), New<v8::String>(baton->space).ToLocalChecked());
|
||||||
Set(info, New("channels").ToLocalChecked(), New<Number>(baton->channels));
|
Set(info, New("channels").ToLocalChecked(), New<v8::Uint32>(baton->channels));
|
||||||
if (baton->density > 0) {
|
if (baton->density > 0) {
|
||||||
Set(info, New("density").ToLocalChecked(), New<Number>(baton->density));
|
Set(info, New("density").ToLocalChecked(), New<v8::Uint32>(baton->density));
|
||||||
}
|
}
|
||||||
Set(info, New("hasProfile").ToLocalChecked(), New<Boolean>(baton->hasProfile));
|
Set(info, New("hasProfile").ToLocalChecked(), New<v8::Boolean>(baton->hasProfile));
|
||||||
Set(info, New("hasAlpha").ToLocalChecked(), New<Boolean>(baton->hasAlpha));
|
Set(info, New("hasAlpha").ToLocalChecked(), New<v8::Boolean>(baton->hasAlpha));
|
||||||
if (baton->orientation > 0) {
|
if (baton->orientation > 0) {
|
||||||
Set(info, New("orientation").ToLocalChecked(), New<Number>(baton->orientation));
|
Set(info, New("orientation").ToLocalChecked(), New<v8::Uint32>(baton->orientation));
|
||||||
}
|
}
|
||||||
if (baton->exifLength > 0) {
|
if (baton->exifLength > 0) {
|
||||||
Set(info,
|
Set(info,
|
||||||
New("exif").ToLocalChecked(),
|
New("exif").ToLocalChecked(),
|
||||||
NewBuffer(baton->exif, baton->exifLength, FreeCallback, nullptr).ToLocalChecked()
|
Nan::NewBuffer(baton->exif, baton->exifLength, sharp::FreeCallback, nullptr).ToLocalChecked()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (baton->iccLength > 0) {
|
if (baton->iccLength > 0) {
|
||||||
Set(info,
|
Set(info,
|
||||||
New("icc").ToLocalChecked(),
|
New("icc").ToLocalChecked(),
|
||||||
NewBuffer(baton->icc, baton->iccLength, FreeCallback, nullptr).ToLocalChecked()
|
Nan::NewBuffer(baton->icc, baton->iccLength, sharp::FreeCallback, nullptr).ToLocalChecked()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
argv[1] = info;
|
argv[1] = info;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {
|
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
|
||||||
GetFromPersistent("bufferIn");
|
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
|
||||||
|
GetFromPersistent(index);
|
||||||
|
return index + 1;
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
delete baton->input;
|
||||||
delete baton;
|
delete baton;
|
||||||
|
|
||||||
// Return to JavaScript
|
// Return to JavaScript
|
||||||
@ -202,32 +127,27 @@ class MetadataWorker : public AsyncWorker {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
MetadataBaton* baton;
|
MetadataBaton* baton;
|
||||||
|
std::vector<v8::Local<v8::Object>> buffersToPersist;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
metadata(options, callback)
|
metadata(options, callback)
|
||||||
*/
|
*/
|
||||||
NAN_METHOD(metadata) {
|
NAN_METHOD(metadata) {
|
||||||
HandleScope();
|
// Input Buffers must not undergo GC compaction during processing
|
||||||
|
std::vector<v8::Local<v8::Object>> buffersToPersist;
|
||||||
|
|
||||||
// V8 objects are converted to non-V8 types held in the baton struct
|
// V8 objects are converted to non-V8 types held in the baton struct
|
||||||
MetadataBaton *baton = new MetadataBaton;
|
MetadataBaton *baton = new MetadataBaton;
|
||||||
Local<Object> options = info[0].As<Object>();
|
v8::Local<v8::Object> options = info[0].As<v8::Object>();
|
||||||
|
|
||||||
// Input filename
|
// Input
|
||||||
baton->fileIn = *Utf8String(Get(options, New("fileIn").ToLocalChecked()).ToLocalChecked());
|
baton->input = sharp::CreateInputDescriptor(sharp::AttrAs<v8::Object>(options, "input"), buffersToPersist);
|
||||||
// Input Buffer object
|
|
||||||
Local<Object> bufferIn;
|
|
||||||
if (node::Buffer::HasInstance(Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked())) {
|
|
||||||
bufferIn = Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked().As<Object>();
|
|
||||||
baton->bufferInLength = node::Buffer::Length(bufferIn);
|
|
||||||
baton->bufferIn = node::Buffer::Data(bufferIn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Join queue for worker thread
|
// Join queue for worker thread
|
||||||
Callback *callback = new Callback(info[1].As<Function>());
|
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>());
|
||||||
AsyncQueueWorker(new MetadataWorker(callback, baton, bufferIn));
|
Nan::AsyncQueueWorker(new MetadataWorker(callback, baton, buffersToPersist));
|
||||||
|
|
||||||
// Increment queued task counter
|
// Increment queued task counter
|
||||||
g_atomic_int_inc(&counterQueue);
|
g_atomic_int_inc(&sharp::counterQueue);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,41 @@
|
|||||||
#define SRC_METADATA_H_
|
#define SRC_METADATA_H_
|
||||||
|
|
||||||
#include "nan.h"
|
#include "nan.h"
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
struct MetadataBaton {
|
||||||
|
// Input
|
||||||
|
sharp::InputDescriptor *input;
|
||||||
|
// Output
|
||||||
|
std::string format;
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
std::string space;
|
||||||
|
int channels;
|
||||||
|
int density;
|
||||||
|
bool hasProfile;
|
||||||
|
bool hasAlpha;
|
||||||
|
int orientation;
|
||||||
|
char *exif;
|
||||||
|
size_t exifLength;
|
||||||
|
char *icc;
|
||||||
|
size_t iccLength;
|
||||||
|
std::string err;
|
||||||
|
|
||||||
|
MetadataBaton():
|
||||||
|
input(nullptr),
|
||||||
|
width(0),
|
||||||
|
height(0),
|
||||||
|
channels(0),
|
||||||
|
density(0),
|
||||||
|
hasProfile(false),
|
||||||
|
hasAlpha(false),
|
||||||
|
orientation(0),
|
||||||
|
exif(nullptr),
|
||||||
|
exifLength(0),
|
||||||
|
icc(nullptr),
|
||||||
|
iccLength(0) {}
|
||||||
|
};
|
||||||
|
|
||||||
NAN_METHOD(metadata);
|
NAN_METHOD(metadata);
|
||||||
|
|
||||||
|
635
src/pipeline.cc
635
src/pipeline.cc
@ -6,94 +6,22 @@
|
|||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
|
||||||
#include <vips/vips8>
|
#include <vips/vips8>
|
||||||
|
|
||||||
#include <node.h>
|
#include <node.h>
|
||||||
#include <node_buffer.h>
|
|
||||||
|
|
||||||
#include "nan.h"
|
#include "nan.h"
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "operations.h"
|
#include "operations.h"
|
||||||
#include "pipeline.h"
|
#include "pipeline.h"
|
||||||
|
|
||||||
using v8::Handle;
|
class PipelineWorker : public Nan::AsyncWorker {
|
||||||
using v8::Local;
|
|
||||||
using v8::Value;
|
|
||||||
using v8::Object;
|
|
||||||
using v8::Integer;
|
|
||||||
using v8::Uint32;
|
|
||||||
using v8::String;
|
|
||||||
using v8::Array;
|
|
||||||
using v8::Function;
|
|
||||||
using v8::Exception;
|
|
||||||
|
|
||||||
using Nan::AsyncQueueWorker;
|
|
||||||
using Nan::AsyncWorker;
|
|
||||||
using Nan::Callback;
|
|
||||||
using Nan::HandleScope;
|
|
||||||
using Nan::Utf8String;
|
|
||||||
using Nan::Has;
|
|
||||||
using Nan::Get;
|
|
||||||
using Nan::Set;
|
|
||||||
using Nan::To;
|
|
||||||
using Nan::New;
|
|
||||||
using Nan::NewBuffer;
|
|
||||||
using Nan::Null;
|
|
||||||
using Nan::Equals;
|
|
||||||
|
|
||||||
using vips::VImage;
|
|
||||||
using vips::VInterpolate;
|
|
||||||
using vips::VOption;
|
|
||||||
using vips::VError;
|
|
||||||
|
|
||||||
using sharp::Composite;
|
|
||||||
using sharp::Cutout;
|
|
||||||
using sharp::Normalize;
|
|
||||||
using sharp::Gamma;
|
|
||||||
using sharp::Blur;
|
|
||||||
using sharp::Convolve;
|
|
||||||
using sharp::Sharpen;
|
|
||||||
using sharp::EntropyCrop;
|
|
||||||
using sharp::TileCache;
|
|
||||||
using sharp::Threshold;
|
|
||||||
using sharp::Bandbool;
|
|
||||||
using sharp::Boolean;
|
|
||||||
using sharp::Trim;
|
|
||||||
|
|
||||||
using sharp::ImageType;
|
|
||||||
using sharp::ImageTypeId;
|
|
||||||
using sharp::DetermineImageType;
|
|
||||||
using sharp::HasProfile;
|
|
||||||
using sharp::HasAlpha;
|
|
||||||
using sharp::ExifOrientation;
|
|
||||||
using sharp::SetExifOrientation;
|
|
||||||
using sharp::RemoveExifOrientation;
|
|
||||||
using sharp::SetDensity;
|
|
||||||
using sharp::IsJpeg;
|
|
||||||
using sharp::IsPng;
|
|
||||||
using sharp::IsWebp;
|
|
||||||
using sharp::IsTiff;
|
|
||||||
using sharp::IsDz;
|
|
||||||
using sharp::IsDzZip;
|
|
||||||
using sharp::IsV;
|
|
||||||
using sharp::FreeCallback;
|
|
||||||
using sharp::CalculateCrop;
|
|
||||||
using sharp::Is16Bit;
|
|
||||||
using sharp::MaximumImageAlpha;
|
|
||||||
using sharp::GetBooleanOperation;
|
|
||||||
|
|
||||||
using sharp::counterProcess;
|
|
||||||
using sharp::counterQueue;
|
|
||||||
|
|
||||||
class PipelineWorker : public AsyncWorker {
|
|
||||||
public:
|
public:
|
||||||
PipelineWorker(
|
PipelineWorker(
|
||||||
Callback *callback, PipelineBaton *baton, Callback *queueListener,
|
Nan::Callback *callback, PipelineBaton *baton, Nan::Callback *queueListener,
|
||||||
std::vector<Local<Object>> const buffersToPersist
|
std::vector<v8::Local<v8::Object>> const buffersToPersist
|
||||||
) : AsyncWorker(callback), baton(baton), queueListener(queueListener), buffersToPersist(buffersToPersist) {
|
) : Nan::AsyncWorker(callback), baton(baton), queueListener(queueListener), buffersToPersist(buffersToPersist) {
|
||||||
// Protect Buffer objects from GC, keyed on index
|
// Protect Buffer objects from GC, keyed on index
|
||||||
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
|
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
|
||||||
[this](uint32_t index, Local<Object> const buffer) -> uint32_t {
|
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
|
||||||
SaveToPersistent(index, buffer);
|
SaveToPersistent(index, buffer);
|
||||||
return index + 1;
|
return index + 1;
|
||||||
}
|
}
|
||||||
@ -101,93 +29,24 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
}
|
}
|
||||||
~PipelineWorker() {}
|
~PipelineWorker() {}
|
||||||
|
|
||||||
/*
|
// libuv worker
|
||||||
libuv worker
|
|
||||||
*/
|
|
||||||
void Execute() {
|
void Execute() {
|
||||||
|
using sharp::HasAlpha;
|
||||||
|
using sharp::ImageType;
|
||||||
|
|
||||||
// Decrement queued task counter
|
// Decrement queued task counter
|
||||||
g_atomic_int_dec_and_test(&counterQueue);
|
g_atomic_int_dec_and_test(&sharp::counterQueue);
|
||||||
// Increment processing task counter
|
// Increment processing task counter
|
||||||
g_atomic_int_inc(&counterProcess);
|
g_atomic_int_inc(&sharp::counterProcess);
|
||||||
|
|
||||||
// Default sRGB ICC profile from https://packages.debian.org/sid/all/icc-profiles-free/filelist
|
// Default sRGB ICC profile from https://packages.debian.org/sid/all/icc-profiles-free/filelist
|
||||||
std::string srgbProfile = baton->iccProfilePath + "sRGB.icc";
|
std::string srgbProfile = baton->iccProfilePath + "sRGB.icc";
|
||||||
|
|
||||||
// Input
|
|
||||||
ImageType inputImageType = ImageType::UNKNOWN;
|
|
||||||
VImage image;
|
|
||||||
if (baton->bufferInLength > 0) {
|
|
||||||
// From buffer
|
|
||||||
if (baton->rawWidth > 0 && baton->rawHeight > 0 && baton->rawChannels > 0) {
|
|
||||||
// Raw, uncompressed pixel data
|
|
||||||
try {
|
try {
|
||||||
image = VImage::new_from_memory(baton->bufferIn, baton->bufferInLength,
|
// Open input
|
||||||
baton->rawWidth, baton->rawHeight, baton->rawChannels, VIPS_FORMAT_UCHAR);
|
vips::VImage image;
|
||||||
if (baton->rawChannels < 3) {
|
ImageType inputImageType;
|
||||||
image.get_image()->Type = VIPS_INTERPRETATION_B_W;
|
std::tie(image, inputImageType) = sharp::OpenInput(baton->input, baton->accessMethod);
|
||||||
} else {
|
|
||||||
image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
|
|
||||||
}
|
|
||||||
inputImageType = ImageType::RAW;
|
|
||||||
} catch(VError const &err) {
|
|
||||||
(baton->err).append(err.what());
|
|
||||||
inputImageType = ImageType::UNKNOWN;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Compressed data
|
|
||||||
inputImageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
|
|
||||||
if (inputImageType != ImageType::UNKNOWN) {
|
|
||||||
try {
|
|
||||||
VOption *option = VImage::option()->set("access", baton->accessMethod);
|
|
||||||
if (inputImageType == ImageType::SVG || inputImageType == ImageType::PDF) {
|
|
||||||
option->set("dpi", static_cast<double>(baton->density));
|
|
||||||
}
|
|
||||||
if (inputImageType == ImageType::MAGICK) {
|
|
||||||
option->set("density", std::to_string(baton->density).data());
|
|
||||||
}
|
|
||||||
image = VImage::new_from_buffer(baton->bufferIn, baton->bufferInLength, nullptr, option);
|
|
||||||
if (inputImageType == ImageType::SVG ||
|
|
||||||
inputImageType == ImageType::PDF ||
|
|
||||||
inputImageType == ImageType::MAGICK) {
|
|
||||||
SetDensity(image, baton->density);
|
|
||||||
}
|
|
||||||
} catch (...) {
|
|
||||||
(baton->err).append("Input buffer has corrupt header");
|
|
||||||
inputImageType = ImageType::UNKNOWN;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(baton->err).append("Input buffer contains unsupported image format");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// From file
|
|
||||||
inputImageType = DetermineImageType(baton->fileIn.data());
|
|
||||||
if (inputImageType != ImageType::UNKNOWN) {
|
|
||||||
try {
|
|
||||||
VOption *option = VImage::option()->set("access", baton->accessMethod);
|
|
||||||
if (inputImageType == ImageType::SVG || inputImageType == ImageType::PDF) {
|
|
||||||
option->set("dpi", static_cast<double>(baton->density));
|
|
||||||
}
|
|
||||||
if (inputImageType == ImageType::MAGICK) {
|
|
||||||
option->set("density", std::to_string(baton->density).data());
|
|
||||||
}
|
|
||||||
image = VImage::new_from_file(baton->fileIn.data(), option);
|
|
||||||
if (inputImageType == ImageType::SVG ||
|
|
||||||
inputImageType == ImageType::PDF ||
|
|
||||||
inputImageType == ImageType::MAGICK) {
|
|
||||||
SetDensity(image, baton->density);
|
|
||||||
}
|
|
||||||
} catch (...) {
|
|
||||||
(baton->err).append("Input file has corrupt header");
|
|
||||||
inputImageType = ImageType::UNKNOWN;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(baton->err).append("Input file is missing or of an unsupported image format");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (inputImageType == ImageType::UNKNOWN) {
|
|
||||||
return Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limit input images to a given number of pixels, where pixels = width * height
|
// Limit input images to a given number of pixels, where pixels = width * height
|
||||||
// Ignore if 0
|
// Ignore if 0
|
||||||
@ -196,7 +55,6 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
return Error();
|
return Error();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
// Calculate angle of rotation
|
// Calculate angle of rotation
|
||||||
VipsAngle rotation;
|
VipsAngle rotation;
|
||||||
bool flip;
|
bool flip;
|
||||||
@ -214,12 +72,12 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
// Rotate pre-extract
|
// Rotate pre-extract
|
||||||
if (baton->rotateBeforePreExtract && rotation != VIPS_ANGLE_D0) {
|
if (baton->rotateBeforePreExtract && rotation != VIPS_ANGLE_D0) {
|
||||||
image = image.rot(rotation);
|
image = image.rot(rotation);
|
||||||
RemoveExifOrientation(image);
|
sharp::RemoveExifOrientation(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trim
|
// Trim
|
||||||
if(baton->trimTolerance != 0) {
|
if(baton->trimTolerance != 0) {
|
||||||
image = Trim(image, baton->trimTolerance);
|
image = sharp::Trim(image, baton->trimTolerance);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre extraction
|
// Pre extraction
|
||||||
@ -362,9 +220,9 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
}
|
}
|
||||||
if (shrink_on_load > 1) {
|
if (shrink_on_load > 1) {
|
||||||
// Reload input using shrink-on-load
|
// Reload input using shrink-on-load
|
||||||
VOption *option = VImage::option()->set("shrink", shrink_on_load);
|
vips::VOption *option = VImage::option()->set("shrink", shrink_on_load);
|
||||||
if (baton->bufferInLength > 1) {
|
if (baton->input->buffer != nullptr) {
|
||||||
VipsBlob *blob = vips_blob_new(nullptr, baton->bufferIn, baton->bufferInLength);
|
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
|
||||||
if (inputImageType == ImageType::JPEG) {
|
if (inputImageType == ImageType::JPEG) {
|
||||||
// Reload JPEG buffer
|
// Reload JPEG buffer
|
||||||
image = VImage::jpegload_buffer(blob, option);
|
image = VImage::jpegload_buffer(blob, option);
|
||||||
@ -376,10 +234,10 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
} else {
|
} else {
|
||||||
if (inputImageType == ImageType::JPEG) {
|
if (inputImageType == ImageType::JPEG) {
|
||||||
// Reload JPEG file
|
// Reload JPEG file
|
||||||
image = VImage::jpegload(const_cast<char*>((baton->fileIn).data()), option);
|
image = VImage::jpegload(const_cast<char*>(baton->input->file.data()), option);
|
||||||
} else {
|
} else {
|
||||||
// Reload WebP file
|
// Reload WebP file
|
||||||
image = VImage::webpload(const_cast<char*>((baton->fileIn).data()), option);
|
image = VImage::webpload(const_cast<char*>(baton->input->file.data()), option);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Recalculate integral shrink and double residual
|
// Recalculate integral shrink and double residual
|
||||||
@ -405,7 +263,7 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we're using a device-independent colour space
|
// Ensure we're using a device-independent colour space
|
||||||
if (HasProfile(image)) {
|
if (sharp::HasProfile(image)) {
|
||||||
// Convert to sRGB using embedded profile
|
// Convert to sRGB using embedded profile
|
||||||
try {
|
try {
|
||||||
image = image.icc_transform(const_cast<char*>(srgbProfile.data()), VImage::option()
|
image = image.icc_transform(const_cast<char*>(srgbProfile.data()), VImage::option()
|
||||||
@ -425,12 +283,12 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate maximum alpha value based on input image pixel depth
|
// Calculate maximum alpha value based on input image pixel depth
|
||||||
double const maxAlpha = MaximumImageAlpha(image.interpretation());
|
double const maxAlpha = sharp::MaximumImageAlpha(image.interpretation());
|
||||||
|
|
||||||
// Flatten image to remove alpha channel
|
// Flatten image to remove alpha channel
|
||||||
if (baton->flatten && HasAlpha(image)) {
|
if (baton->flatten && HasAlpha(image)) {
|
||||||
// Scale up 8-bit values to match 16-bit input image
|
// Scale up 8-bit values to match 16-bit input image
|
||||||
double const multiplier = Is16Bit(image.interpretation()) ? 256.0 : 1.0;
|
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
|
||||||
// Background colour
|
// Background colour
|
||||||
std::vector<double> background {
|
std::vector<double> background {
|
||||||
baton->background[0] * multiplier,
|
baton->background[0] * multiplier,
|
||||||
@ -450,7 +308,7 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
|
|
||||||
// Gamma encoding (darken)
|
// Gamma encoding (darken)
|
||||||
if (baton->gamma >= 1 && baton->gamma <= 3) {
|
if (baton->gamma >= 1 && baton->gamma <= 3) {
|
||||||
image = Gamma(image, 1.0 / baton->gamma);
|
image = sharp::Gamma(image, 1.0 / baton->gamma);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to greyscale (linear, therefore after gamma encoding, if any)
|
// Convert to greyscale (linear, therefore after gamma encoding, if any)
|
||||||
@ -484,9 +342,9 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure image has an alpha channel when there is an overlay
|
// Ensure image has an alpha channel when there is an overlay
|
||||||
bool hasOverlay = baton->overlayBufferInLength > 0 || !baton->overlayFileIn.empty();
|
bool hasOverlay = baton->overlay != nullptr;
|
||||||
if (hasOverlay && !HasAlpha(image)) {
|
if (hasOverlay && !HasAlpha(image)) {
|
||||||
double const multiplier = Is16Bit(image.interpretation()) ? 256.0 : 1.0;
|
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
|
||||||
image = image.bandjoin(
|
image = image.bandjoin(
|
||||||
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier)
|
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier)
|
||||||
);
|
);
|
||||||
@ -511,7 +369,7 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
if (shouldAffineTransform) {
|
if (shouldAffineTransform) {
|
||||||
// Insert tile cache to prevent over-computation of previous operations
|
// Insert tile cache to prevent over-computation of previous operations
|
||||||
if (baton->accessMethod == VIPS_ACCESS_SEQUENTIAL) {
|
if (baton->accessMethod == VIPS_ACCESS_SEQUENTIAL) {
|
||||||
image = TileCache(image, yresidual);
|
image = sharp::TileCache(image, yresidual);
|
||||||
}
|
}
|
||||||
// Perform kernel-based reduction
|
// Perform kernel-based reduction
|
||||||
if (yresidual < 1.0 || xresidual < 1.0) {
|
if (yresidual < 1.0 || xresidual < 1.0) {
|
||||||
@ -519,7 +377,7 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
vips_enum_from_nick(nullptr, VIPS_TYPE_KERNEL, baton->kernel.data())
|
vips_enum_from_nick(nullptr, VIPS_TYPE_KERNEL, baton->kernel.data())
|
||||||
);
|
);
|
||||||
if (kernel != VIPS_KERNEL_CUBIC && kernel != VIPS_KERNEL_LANCZOS2 && kernel != VIPS_KERNEL_LANCZOS3) {
|
if (kernel != VIPS_KERNEL_CUBIC && kernel != VIPS_KERNEL_LANCZOS2 && kernel != VIPS_KERNEL_LANCZOS3) {
|
||||||
throw VError("Unknown kernel");
|
throw vips::VError("Unknown kernel");
|
||||||
}
|
}
|
||||||
if (yresidual < 1.0) {
|
if (yresidual < 1.0) {
|
||||||
image = image.reducev(1.0 / yresidual, VImage::option()->set("kernel", kernel));
|
image = image.reducev(1.0 / yresidual, VImage::option()->set("kernel", kernel));
|
||||||
@ -530,7 +388,7 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
}
|
}
|
||||||
// Perform affine enlargement
|
// Perform affine enlargement
|
||||||
if (yresidual > 1.0 || xresidual > 1.0) {
|
if (yresidual > 1.0 || xresidual > 1.0) {
|
||||||
VInterpolate interpolator = VInterpolate::new_from_name(baton->interpolator.data());
|
vips::VInterpolate interpolator = vips::VInterpolate::new_from_name(baton->interpolator.data());
|
||||||
if (yresidual > 1.0) {
|
if (yresidual > 1.0) {
|
||||||
image = image.affine({1.0, 0.0, 0.0, yresidual}, VImage::option()
|
image = image.affine({1.0, 0.0, 0.0, yresidual}, VImage::option()
|
||||||
->set("interpolate", interpolator)
|
->set("interpolate", interpolator)
|
||||||
@ -547,26 +405,26 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
// Rotate
|
// Rotate
|
||||||
if (!baton->rotateBeforePreExtract && rotation != VIPS_ANGLE_D0) {
|
if (!baton->rotateBeforePreExtract && rotation != VIPS_ANGLE_D0) {
|
||||||
image = image.rot(rotation);
|
image = image.rot(rotation);
|
||||||
RemoveExifOrientation(image);
|
sharp::RemoveExifOrientation(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flip (mirror about Y axis)
|
// Flip (mirror about Y axis)
|
||||||
if (baton->flip) {
|
if (baton->flip) {
|
||||||
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
||||||
RemoveExifOrientation(image);
|
sharp::RemoveExifOrientation(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flop (mirror about X axis)
|
// Flop (mirror about X axis)
|
||||||
if (baton->flop) {
|
if (baton->flop) {
|
||||||
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
||||||
RemoveExifOrientation(image);
|
sharp::RemoveExifOrientation(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crop/embed
|
// Crop/embed
|
||||||
if (image.width() != baton->width || image.height() != baton->height) {
|
if (image.width() != baton->width || image.height() != baton->height) {
|
||||||
if (baton->canvas == Canvas::EMBED) {
|
if (baton->canvas == Canvas::EMBED) {
|
||||||
// Scale up 8-bit values to match 16-bit input image
|
// Scale up 8-bit values to match 16-bit input image
|
||||||
double const multiplier = Is16Bit(image.interpretation()) ? 256.0 : 1.0;
|
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
|
||||||
// Create background colour
|
// Create background colour
|
||||||
std::vector<double> background;
|
std::vector<double> background;
|
||||||
if (image.bands() > 2) {
|
if (image.bands() > 2) {
|
||||||
@ -606,12 +464,12 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
int top;
|
int top;
|
||||||
if (baton->crop < 9) {
|
if (baton->crop < 9) {
|
||||||
// Gravity-based crop
|
// Gravity-based crop
|
||||||
std::tie(left, top) = CalculateCrop(
|
std::tie(left, top) = sharp::CalculateCrop(
|
||||||
image.width(), image.height(), baton->width, baton->height, baton->crop
|
image.width(), image.height(), baton->width, baton->height, baton->crop
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Entropy-based crop
|
// Entropy-based crop
|
||||||
std::tie(left, top) = EntropyCrop(image, baton->width, baton->height);
|
std::tie(left, top) = sharp::EntropyCrop(image, baton->width, baton->height);
|
||||||
}
|
}
|
||||||
int width = std::min(image.width(), baton->width);
|
int width = std::min(image.width(), baton->width);
|
||||||
int height = std::min(image.height(), baton->height);
|
int height = std::min(image.height(), baton->height);
|
||||||
@ -629,7 +487,7 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
// Extend edges
|
// Extend edges
|
||||||
if (baton->extendTop > 0 || baton->extendBottom > 0 || baton->extendLeft > 0 || baton->extendRight > 0) {
|
if (baton->extendTop > 0 || baton->extendBottom > 0 || baton->extendLeft > 0 || baton->extendRight > 0) {
|
||||||
// Scale up 8-bit values to match 16-bit input image
|
// Scale up 8-bit values to match 16-bit input image
|
||||||
double const multiplier = Is16Bit(image.interpretation()) ? 256.0 : 1.0;
|
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
|
||||||
// Create background colour
|
// Create background colour
|
||||||
std::vector<double> background {
|
std::vector<double> background {
|
||||||
baton->background[0] * multiplier,
|
baton->background[0] * multiplier,
|
||||||
@ -656,17 +514,17 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
|
|
||||||
// Threshold - must happen before blurring, due to the utility of blurring after thresholding
|
// Threshold - must happen before blurring, due to the utility of blurring after thresholding
|
||||||
if (baton->threshold != 0) {
|
if (baton->threshold != 0) {
|
||||||
image = Threshold(image, baton->threshold, baton->thresholdGrayscale);
|
image = sharp::Threshold(image, baton->threshold, baton->thresholdGrayscale);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blur
|
// Blur
|
||||||
if (shouldBlur) {
|
if (shouldBlur) {
|
||||||
image = Blur(image, baton->blurSigma);
|
image = sharp::Blur(image, baton->blurSigma);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convolve
|
// Convolve
|
||||||
if (shouldConv) {
|
if (shouldConv) {
|
||||||
image = Convolve(image,
|
image = sharp::Convolve(image,
|
||||||
baton->convKernelWidth, baton->convKernelHeight,
|
baton->convKernelWidth, baton->convKernelHeight,
|
||||||
baton->convKernelScale, baton->convKernelOffset,
|
baton->convKernelScale, baton->convKernelOffset,
|
||||||
baton->convKernel
|
baton->convKernel
|
||||||
@ -675,71 +533,40 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
|
|
||||||
// Sharpen
|
// Sharpen
|
||||||
if (shouldSharpen) {
|
if (shouldSharpen) {
|
||||||
image = Sharpen(image, baton->sharpenSigma, baton->sharpenFlat, baton->sharpenJagged);
|
image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenFlat, baton->sharpenJagged);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Composite with overlay, if present
|
// Composite with overlay, if present
|
||||||
if (hasOverlay) {
|
if (hasOverlay) {
|
||||||
VImage overlayImage;
|
VImage overlayImage;
|
||||||
ImageType overlayImageType = ImageType::UNKNOWN;
|
ImageType overlayImageType = ImageType::UNKNOWN;
|
||||||
if (baton->overlayBufferInLength > 0) {
|
std::tie(overlayImage, overlayImageType) = OpenInput(baton->overlay, baton->accessMethod);
|
||||||
// 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 {
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
// Check if overlay is tiled
|
// Check if overlay is tiled
|
||||||
if (baton->overlayTile) {
|
if (baton->overlayTile) {
|
||||||
int overlayImageWidth = overlayImage.width();
|
int const overlayImageWidth = overlayImage.width();
|
||||||
int overlayImageHeight = overlayImage.height();
|
int const overlayImageHeight = overlayImage.height();
|
||||||
int across = 0;
|
int across = 0;
|
||||||
int down = 0;
|
int down = 0;
|
||||||
|
// Use gravity in overlay
|
||||||
// use gravity in ovelay
|
if (overlayImageWidth <= baton->width) {
|
||||||
if(overlayImageWidth <= baton->width) {
|
|
||||||
across = static_cast<int>(ceil(static_cast<double>(image.width()) / overlayImageWidth));
|
across = static_cast<int>(ceil(static_cast<double>(image.width()) / overlayImageWidth));
|
||||||
}
|
}
|
||||||
if(overlayImageHeight <= baton->height) {
|
if (overlayImageHeight <= baton->height) {
|
||||||
down = static_cast<int>(ceil(static_cast<double>(image.height()) / overlayImageHeight));
|
down = static_cast<int>(ceil(static_cast<double>(image.height()) / overlayImageHeight));
|
||||||
}
|
}
|
||||||
if(across != 0 || down != 0) {
|
if (across != 0 || down != 0) {
|
||||||
int left;
|
int left;
|
||||||
int top;
|
int top;
|
||||||
overlayImage = overlayImage.replicate(across, down);
|
overlayImage = overlayImage.replicate(across, down);
|
||||||
|
if (baton->overlayXOffset >= 0 && baton->overlayYOffset >= 0) {
|
||||||
if(baton->overlayXOffset >= 0 && baton->overlayYOffset >= 0) {
|
|
||||||
// the overlayX/YOffsets will now be used to CalculateCrop for extract_area
|
// the overlayX/YOffsets will now be used to CalculateCrop for extract_area
|
||||||
std::tie(left, top) = CalculateCrop(
|
std::tie(left, top) = sharp::CalculateCrop(
|
||||||
overlayImage.width(), overlayImage.height(), image.width(), image.height(),
|
overlayImage.width(), overlayImage.height(), image.width(), image.height(),
|
||||||
baton->overlayXOffset, baton->overlayYOffset
|
baton->overlayXOffset, baton->overlayYOffset
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// the overlayGravity will now be used to CalculateCrop for extract_area
|
// the overlayGravity will now be used to CalculateCrop for extract_area
|
||||||
std::tie(left, top) = CalculateCrop(
|
std::tie(left, top) = sharp::CalculateCrop(
|
||||||
overlayImage.width(), overlayImage.height(), image.width(), image.height(), baton->overlayGravity
|
overlayImage.width(), overlayImage.height(), image.width(), image.height(), baton->overlayGravity
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -750,18 +577,18 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
// the overlayGravity was used for extract_area, therefore set it back to its default value of 0
|
// the overlayGravity was used for extract_area, therefore set it back to its default value of 0
|
||||||
baton->overlayGravity = 0;
|
baton->overlayGravity = 0;
|
||||||
}
|
}
|
||||||
if(shouldCutout) {
|
if (shouldCutout) {
|
||||||
// 'cut out' the image, premultiplication is not required
|
// 'cut out' the image, premultiplication is not required
|
||||||
image = Cutout(overlayImage, image, baton->overlayGravity);
|
image = sharp::Cutout(overlayImage, image, baton->overlayGravity);
|
||||||
} else {
|
} else {
|
||||||
// Ensure overlay is premultiplied sRGB
|
// Ensure overlay is premultiplied sRGB
|
||||||
overlayImage = overlayImage.colourspace(VIPS_INTERPRETATION_sRGB).premultiply();
|
overlayImage = overlayImage.colourspace(VIPS_INTERPRETATION_sRGB).premultiply();
|
||||||
if(baton->overlayXOffset >= 0 && baton->overlayYOffset >= 0) {
|
if (baton->overlayXOffset >= 0 && baton->overlayYOffset >= 0) {
|
||||||
// Composite images with given offsets
|
// Composite images with given offsets
|
||||||
image = Composite(overlayImage, image, baton->overlayXOffset, baton->overlayYOffset);
|
image = sharp::Composite(overlayImage, image, baton->overlayXOffset, baton->overlayYOffset);
|
||||||
} else {
|
} else {
|
||||||
// Composite images with given gravity
|
// Composite images with given gravity
|
||||||
image = Composite(overlayImage, image, baton->overlayGravity);
|
image = sharp::Composite(overlayImage, image, baton->overlayGravity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -770,7 +597,7 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
if (shouldPremultiplyAlpha) {
|
if (shouldPremultiplyAlpha) {
|
||||||
image = image.unpremultiply(VImage::option()->set("max_alpha", maxAlpha));
|
image = image.unpremultiply(VImage::option()->set("max_alpha", maxAlpha));
|
||||||
// Cast pixel values to integer
|
// Cast pixel values to integer
|
||||||
if (Is16Bit(image.interpretation())) {
|
if (sharp::Is16Bit(image.interpretation())) {
|
||||||
image = image.cast(VIPS_FORMAT_USHORT);
|
image = image.cast(VIPS_FORMAT_USHORT);
|
||||||
} else {
|
} else {
|
||||||
image = image.cast(VIPS_FORMAT_UCHAR);
|
image = image.cast(VIPS_FORMAT_UCHAR);
|
||||||
@ -779,55 +606,25 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
|
|
||||||
// Gamma decoding (brighten)
|
// Gamma decoding (brighten)
|
||||||
if (baton->gamma >= 1 && baton->gamma <= 3) {
|
if (baton->gamma >= 1 && baton->gamma <= 3) {
|
||||||
image = Gamma(image, baton->gamma);
|
image = sharp::Gamma(image, baton->gamma);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply normalization - stretch luminance to cover full dynamic range
|
// Apply normalization - stretch luminance to cover full dynamic range
|
||||||
if (baton->normalize) {
|
if (baton->normalize) {
|
||||||
image = Normalize(image);
|
image = sharp::Normalize(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply bitwise boolean operation between images
|
// Apply bitwise boolean operation between images
|
||||||
if (baton->booleanOp != VIPS_OPERATION_BOOLEAN_LAST &&
|
if (baton->boolean != nullptr) {
|
||||||
(baton->booleanBufferInLength > 0 || !baton->booleanFileIn.empty())) {
|
|
||||||
VImage booleanImage;
|
VImage booleanImage;
|
||||||
ImageType booleanImageType = ImageType::UNKNOWN;
|
ImageType booleanImageType = ImageType::UNKNOWN;
|
||||||
if (baton->booleanBufferInLength > 0) {
|
std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean, baton->accessMethod);
|
||||||
// Buffer input for boolean operation
|
image = sharp::Boolean(image, booleanImage, baton->booleanOp);
|
||||||
booleanImageType = DetermineImageType(baton->booleanBufferIn, baton->booleanBufferInLength);
|
|
||||||
if (booleanImageType != ImageType::UNKNOWN) {
|
|
||||||
try {
|
|
||||||
booleanImage = VImage::new_from_buffer(baton->booleanBufferIn, baton->booleanBufferInLength,
|
|
||||||
nullptr, VImage::option()->set("access", baton->accessMethod));
|
|
||||||
} catch (...) {
|
|
||||||
(baton->err).append("Boolean operation buffer has corrupt header");
|
|
||||||
booleanImageType = ImageType::UNKNOWN;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(baton->err).append("Boolean operation buffer contains unsupported image format");
|
|
||||||
}
|
|
||||||
} else if (!baton->booleanFileIn.empty()) {
|
|
||||||
// File input for boolean operation
|
|
||||||
booleanImageType = DetermineImageType(baton->booleanFileIn.data());
|
|
||||||
if (booleanImageType != ImageType::UNKNOWN) {
|
|
||||||
try {
|
|
||||||
booleanImage = VImage::new_from_file(baton->booleanFileIn.data(),
|
|
||||||
VImage::option()->set("access", baton->accessMethod));
|
|
||||||
} catch (...) {
|
|
||||||
(baton->err).append("Boolean operation file has corrupt header");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (booleanImageType == ImageType::UNKNOWN) {
|
|
||||||
return Error();
|
|
||||||
}
|
|
||||||
// Apply the boolean operation
|
|
||||||
image = Boolean(image, booleanImage, baton->booleanOp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply per-channel Bandbool bitwise operations after all other operations
|
// Apply per-channel Bandbool bitwise operations after all other operations
|
||||||
if (baton->bandBoolOp >= VIPS_OPERATION_BOOLEAN_AND && baton->bandBoolOp < VIPS_OPERATION_BOOLEAN_LAST) {
|
if (baton->bandBoolOp >= VIPS_OPERATION_BOOLEAN_AND && baton->bandBoolOp < VIPS_OPERATION_BOOLEAN_LAST) {
|
||||||
image = Bandbool(image, baton->bandBoolOp);
|
image = sharp::Bandbool(image, baton->bandBoolOp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract an image channel (aka vips band)
|
// Extract an image channel (aka vips band)
|
||||||
@ -840,13 +637,13 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert image to sRGB, if not already
|
// Convert image to sRGB, if not already
|
||||||
if (Is16Bit(image.interpretation())) {
|
if (sharp::Is16Bit(image.interpretation())) {
|
||||||
image = image.cast(VIPS_FORMAT_USHORT);
|
image = image.cast(VIPS_FORMAT_USHORT);
|
||||||
}
|
}
|
||||||
if (image.interpretation() != VIPS_INTERPRETATION_sRGB) {
|
if (image.interpretation() != VIPS_INTERPRETATION_sRGB) {
|
||||||
image = image.colourspace(VIPS_INTERPRETATION_sRGB);
|
image = image.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
// Transform colours from embedded profile to sRGB profile
|
// Transform colours from embedded profile to sRGB profile
|
||||||
if (baton->withMetadata && HasProfile(image)) {
|
if (baton->withMetadata && sharp::HasProfile(image)) {
|
||||||
image = image.icc_transform(const_cast<char*>(srgbProfile.data()), VImage::option()
|
image = image.icc_transform(const_cast<char*>(srgbProfile.data()), VImage::option()
|
||||||
->set("embedded", TRUE)
|
->set("embedded", TRUE)
|
||||||
);
|
);
|
||||||
@ -855,7 +652,7 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
|
|
||||||
// Override EXIF Orientation tag
|
// Override EXIF Orientation tag
|
||||||
if (baton->withMetadata && baton->withMetadataOrientation != -1) {
|
if (baton->withMetadata && baton->withMetadataOrientation != -1) {
|
||||||
SetExifOrientation(image, baton->withMetadataOrientation);
|
sharp::SetExifOrientation(image, baton->withMetadataOrientation);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Number of channels used in output image
|
// Number of channels used in output image
|
||||||
@ -940,13 +737,13 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// File output
|
// File output
|
||||||
bool isJpeg = IsJpeg(baton->fileOut);
|
bool isJpeg = sharp::IsJpeg(baton->fileOut);
|
||||||
bool isPng = IsPng(baton->fileOut);
|
bool isPng = sharp::IsPng(baton->fileOut);
|
||||||
bool isWebp = IsWebp(baton->fileOut);
|
bool isWebp = sharp::IsWebp(baton->fileOut);
|
||||||
bool isTiff = IsTiff(baton->fileOut);
|
bool isTiff = sharp::IsTiff(baton->fileOut);
|
||||||
bool isDz = IsDz(baton->fileOut);
|
bool isDz = sharp::IsDz(baton->fileOut);
|
||||||
bool isDzZip = IsDzZip(baton->fileOut);
|
bool isDzZip = sharp::IsDzZip(baton->fileOut);
|
||||||
bool isV = IsV(baton->fileOut);
|
bool isV = sharp::IsV(baton->fileOut);
|
||||||
bool matchInput = baton->formatOut == "input" &&
|
bool matchInput = baton->formatOut == "input" &&
|
||||||
!(isJpeg || isPng || isWebp || isTiff || isDz || isDzZip || isV);
|
!(isJpeg || isPng || isWebp || isTiff || isDz || isDzZip || isV);
|
||||||
if (baton->formatOut == "jpeg" || isJpeg || (matchInput && inputImageType == ImageType::JPEG)) {
|
if (baton->formatOut == "jpeg" || isJpeg || (matchInput && inputImageType == ImageType::JPEG)) {
|
||||||
@ -1017,7 +814,7 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
return Error();
|
return Error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (VError const &err) {
|
} catch (vips::VError const &err) {
|
||||||
(baton->err).append(err.what());
|
(baton->err).append(err.what());
|
||||||
}
|
}
|
||||||
// Clean up libvips' per-request data and threads
|
// Clean up libvips' per-request data and threads
|
||||||
@ -1026,9 +823,11 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HandleOKCallback () {
|
void HandleOKCallback () {
|
||||||
HandleScope();
|
using Nan::New;
|
||||||
|
using Nan::Set;
|
||||||
|
Nan::HandleScope();
|
||||||
|
|
||||||
Local<Value> argv[3] = { Null(), Null(), Null() };
|
v8::Local<v8::Value> argv[3] = { Nan::Null(), Nan::Null(), Nan::Null() };
|
||||||
if (!baton->err.empty()) {
|
if (!baton->err.empty()) {
|
||||||
// Error
|
// Error
|
||||||
argv[0] = Nan::Error(baton->err.data());
|
argv[0] = Nan::Error(baton->err.data());
|
||||||
@ -1044,41 +843,44 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
height = baton->heightPost;
|
height = baton->heightPost;
|
||||||
}
|
}
|
||||||
// Info Object
|
// Info Object
|
||||||
Local<Object> info = New<Object>();
|
v8::Local<v8::Object> info = New<v8::Object>();
|
||||||
Set(info, New("format").ToLocalChecked(), New<String>(baton->formatOut).ToLocalChecked());
|
Set(info, New("format").ToLocalChecked(), New<v8::String>(baton->formatOut).ToLocalChecked());
|
||||||
Set(info, New("width").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(width)));
|
Set(info, New("width").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(width)));
|
||||||
Set(info, New("height").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(height)));
|
Set(info, New("height").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(height)));
|
||||||
Set(info, New("channels").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(baton->channels)));
|
Set(info, New("channels").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->channels)));
|
||||||
|
|
||||||
if (baton->bufferOutLength > 0) {
|
if (baton->bufferOutLength > 0) {
|
||||||
// Pass ownership of output data to Buffer instance
|
// Pass ownership of output data to Buffer instance
|
||||||
argv[1] = NewBuffer(
|
argv[1] = Nan::NewBuffer(
|
||||||
static_cast<char*>(baton->bufferOut), baton->bufferOutLength, FreeCallback, nullptr
|
static_cast<char*>(baton->bufferOut), baton->bufferOutLength, sharp::FreeCallback, nullptr
|
||||||
).ToLocalChecked();
|
).ToLocalChecked();
|
||||||
// Add buffer size to info
|
// Add buffer size to info
|
||||||
Set(info, New("size").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(baton->bufferOutLength)));
|
Set(info, New("size").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->bufferOutLength)));
|
||||||
argv[2] = info;
|
argv[2] = info;
|
||||||
} else {
|
} else {
|
||||||
// Add file size to info
|
// Add file size to info
|
||||||
GStatBuf st;
|
GStatBuf st;
|
||||||
g_stat(baton->fileOut.data(), &st);
|
g_stat(baton->fileOut.data(), &st);
|
||||||
Set(info, New("size").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(st.st_size)));
|
Set(info, New("size").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(st.st_size)));
|
||||||
argv[1] = info;
|
argv[1] = info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispose of Persistent wrapper around input Buffers so they can be garbage collected
|
// Dispose of Persistent wrapper around input Buffers so they can be garbage collected
|
||||||
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
|
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
|
||||||
[this](uint32_t index, Local<Object> const buffer) -> uint32_t {
|
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
|
||||||
GetFromPersistent(index);
|
GetFromPersistent(index);
|
||||||
return index + 1;
|
return index + 1;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
delete baton->input;
|
||||||
|
delete baton->overlay;
|
||||||
|
delete baton->boolean;
|
||||||
delete baton;
|
delete baton;
|
||||||
|
|
||||||
// Decrement processing task counter
|
// Decrement processing task counter
|
||||||
g_atomic_int_dec_and_test(&counterProcess);
|
g_atomic_int_dec_and_test(&sharp::counterProcess);
|
||||||
Local<Value> queueLength[1] = { New<Uint32>(counterQueue) };
|
v8::Local<v8::Value> queueLength[1] = { New<v8::Uint32>(sharp::counterQueue) };
|
||||||
queueListener->Call(1, queueLength);
|
queueListener->Call(1, queueLength);
|
||||||
delete queueListener;
|
delete queueListener;
|
||||||
|
|
||||||
@ -1088,8 +890,8 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
PipelineBaton *baton;
|
PipelineBaton *baton;
|
||||||
Callback *queueListener;
|
Nan::Callback *queueListener;
|
||||||
std::vector<Local<Object>> buffersToPersist;
|
std::vector<v8::Local<v8::Object>> buffersToPersist;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Calculate the angle of rotation and need-to-flip for the output image.
|
Calculate the angle of rotation and need-to-flip for the output image.
|
||||||
@ -1099,12 +901,12 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
3. Otherwise default to zero, i.e. no rotation
|
3. Otherwise default to zero, i.e. no rotation
|
||||||
*/
|
*/
|
||||||
std::tuple<VipsAngle, bool, bool>
|
std::tuple<VipsAngle, bool, bool>
|
||||||
CalculateRotationAndFlip(int const angle, VImage image) {
|
CalculateRotationAndFlip(int const angle, vips::VImage image) {
|
||||||
VipsAngle rotate = VIPS_ANGLE_D0;
|
VipsAngle rotate = VIPS_ANGLE_D0;
|
||||||
bool flip = FALSE;
|
bool flip = FALSE;
|
||||||
bool flop = FALSE;
|
bool flop = FALSE;
|
||||||
if (angle == -1) {
|
if (angle == -1) {
|
||||||
switch(ExifOrientation(image)) {
|
switch(sharp::ExifOrientation(image)) {
|
||||||
case 6: rotate = VIPS_ANGLE_D90; break;
|
case 6: rotate = VIPS_ANGLE_D90; break;
|
||||||
case 3: rotate = VIPS_ANGLE_D180; break;
|
case 3: rotate = VIPS_ANGLE_D180; break;
|
||||||
case 8: rotate = VIPS_ANGLE_D270; break;
|
case 8: rotate = VIPS_ANGLE_D270; break;
|
||||||
@ -1135,63 +937,46 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Convenience methods to access the attributes of a V8::Object
|
|
||||||
template<typename T> T attrAs(Handle<Object> obj, std::string attr) {
|
|
||||||
return To<T>(Get(obj, New(attr).ToLocalChecked()).ToLocalChecked()).FromJust();
|
|
||||||
}
|
|
||||||
static std::string attrAsStr(Handle<Object> obj, std::string attr) {
|
|
||||||
return *Utf8String(Get(obj, New(attr).ToLocalChecked()).ToLocalChecked());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
pipeline(options, output, callback)
|
pipeline(options, output, callback)
|
||||||
*/
|
*/
|
||||||
NAN_METHOD(pipeline) {
|
NAN_METHOD(pipeline) {
|
||||||
HandleScope();
|
using sharp::HasAttr;
|
||||||
|
using sharp::AttrTo;
|
||||||
|
using sharp::AttrAs;
|
||||||
|
using sharp::AttrAsStr;
|
||||||
|
using sharp::CreateInputDescriptor;
|
||||||
|
|
||||||
|
// Input Buffers must not undergo GC compaction during processing
|
||||||
|
std::vector<v8::Local<v8::Object>> buffersToPersist;
|
||||||
|
|
||||||
// V8 objects are converted to non-V8 types held in the baton struct
|
// V8 objects are converted to non-V8 types held in the baton struct
|
||||||
PipelineBaton *baton = new PipelineBaton;
|
PipelineBaton *baton = new PipelineBaton;
|
||||||
Local<Object> options = info[0].As<Object>();
|
v8::Local<v8::Object> options = info[0].As<v8::Object>();
|
||||||
|
|
||||||
// Input Buffers must not undergo GC compaction during processing
|
// Input
|
||||||
std::vector<Local<Object>> buffersToPersist;
|
baton->input = CreateInputDescriptor(AttrAs<v8::Object>(options, "input"), buffersToPersist);
|
||||||
|
|
||||||
// Input filename
|
|
||||||
baton->fileIn = attrAsStr(options, "fileIn");
|
|
||||||
baton->accessMethod = attrAs<bool>(options, "sequentialRead") ?
|
|
||||||
VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
|
|
||||||
// Input Buffer object
|
|
||||||
Local<Object> bufferIn;
|
|
||||||
if (node::Buffer::HasInstance(Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked())) {
|
|
||||||
bufferIn = Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked().As<Object>();
|
|
||||||
baton->bufferInLength = node::Buffer::Length(bufferIn);
|
|
||||||
baton->bufferIn = node::Buffer::Data(bufferIn);
|
|
||||||
buffersToPersist.push_back(bufferIn);
|
|
||||||
}
|
|
||||||
// ICC profile to use when input CMYK image has no embedded profile
|
// ICC profile to use when input CMYK image has no embedded profile
|
||||||
baton->iccProfilePath = attrAsStr(options, "iccProfilePath");
|
baton->iccProfilePath = AttrAsStr(options, "iccProfilePath");
|
||||||
|
baton->accessMethod = AttrTo<bool>(options, "sequentialRead") ?
|
||||||
|
VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
|
||||||
// Limit input images to a given number of pixels, where pixels = width * height
|
// Limit input images to a given number of pixels, where pixels = width * height
|
||||||
baton->limitInputPixels = attrAs<int32_t>(options, "limitInputPixels");
|
baton->limitInputPixels = AttrTo<int32_t>(options, "limitInputPixels");
|
||||||
// Density/DPI at which to load vector images via libmagick
|
|
||||||
baton->density = attrAs<int32_t>(options, "density");
|
|
||||||
// Raw pixel input
|
|
||||||
baton->rawWidth = attrAs<int32_t>(options, "rawWidth");
|
|
||||||
baton->rawHeight = attrAs<int32_t>(options, "rawHeight");
|
|
||||||
baton->rawChannels = attrAs<int32_t>(options, "rawChannels");
|
|
||||||
// Extract image options
|
// Extract image options
|
||||||
baton->topOffsetPre = attrAs<int32_t>(options, "topOffsetPre");
|
baton->topOffsetPre = AttrTo<int32_t>(options, "topOffsetPre");
|
||||||
baton->leftOffsetPre = attrAs<int32_t>(options, "leftOffsetPre");
|
baton->leftOffsetPre = AttrTo<int32_t>(options, "leftOffsetPre");
|
||||||
baton->widthPre = attrAs<int32_t>(options, "widthPre");
|
baton->widthPre = AttrTo<int32_t>(options, "widthPre");
|
||||||
baton->heightPre = attrAs<int32_t>(options, "heightPre");
|
baton->heightPre = AttrTo<int32_t>(options, "heightPre");
|
||||||
baton->topOffsetPost = attrAs<int32_t>(options, "topOffsetPost");
|
baton->topOffsetPost = AttrTo<int32_t>(options, "topOffsetPost");
|
||||||
baton->leftOffsetPost = attrAs<int32_t>(options, "leftOffsetPost");
|
baton->leftOffsetPost = AttrTo<int32_t>(options, "leftOffsetPost");
|
||||||
baton->widthPost = attrAs<int32_t>(options, "widthPost");
|
baton->widthPost = AttrTo<int32_t>(options, "widthPost");
|
||||||
baton->heightPost = attrAs<int32_t>(options, "heightPost");
|
baton->heightPost = AttrTo<int32_t>(options, "heightPost");
|
||||||
// Output image dimensions
|
// Output image dimensions
|
||||||
baton->width = attrAs<int32_t>(options, "width");
|
baton->width = AttrTo<int32_t>(options, "width");
|
||||||
baton->height = attrAs<int32_t>(options, "height");
|
baton->height = AttrTo<int32_t>(options, "height");
|
||||||
// Canvas option
|
// Canvas option
|
||||||
std::string canvas = attrAsStr(options, "canvas");
|
std::string canvas = AttrAsStr(options, "canvas");
|
||||||
if (canvas == "crop") {
|
if (canvas == "crop") {
|
||||||
baton->canvas = Canvas::CROP;
|
baton->canvas = Canvas::CROP;
|
||||||
} else if (canvas == "embed") {
|
} else if (canvas == "embed") {
|
||||||
@ -1204,87 +989,93 @@ NAN_METHOD(pipeline) {
|
|||||||
baton->canvas = Canvas::IGNORE_ASPECT;
|
baton->canvas = Canvas::IGNORE_ASPECT;
|
||||||
}
|
}
|
||||||
// Background colour
|
// Background colour
|
||||||
Local<Object> background = Get(options, New("background").ToLocalChecked()).ToLocalChecked().As<Object>();
|
v8::Local<v8::Object> background = AttrAs<v8::Object>(options, "background");
|
||||||
for (int i = 0; i < 4; i++) {
|
for (unsigned int i = 0; i < 4; i++) {
|
||||||
baton->background[i] = To<int32_t>(Get(background, i).ToLocalChecked()).FromJust();
|
baton->background[i] = AttrTo<uint32_t>(background, i);
|
||||||
}
|
}
|
||||||
// Overlay options
|
// Overlay options
|
||||||
baton->overlayFileIn = attrAsStr(options, "overlayFileIn");
|
if (HasAttr(options, "overlay")) {
|
||||||
Local<Object> overlayBufferIn;
|
baton->overlay = CreateInputDescriptor(AttrAs<v8::Object>(options, "overlay"), buffersToPersist);
|
||||||
if (node::Buffer::HasInstance(Get(options, New("overlayBufferIn").ToLocalChecked()).ToLocalChecked())) {
|
baton->overlayGravity = AttrTo<int32_t>(options, "overlayGravity");
|
||||||
overlayBufferIn = Get(options, New("overlayBufferIn").ToLocalChecked()).ToLocalChecked().As<Object>();
|
baton->overlayXOffset = AttrTo<int32_t>(options, "overlayXOffset");
|
||||||
baton->overlayBufferInLength = node::Buffer::Length(overlayBufferIn);
|
baton->overlayYOffset = AttrTo<int32_t>(options, "overlayYOffset");
|
||||||
baton->overlayBufferIn = node::Buffer::Data(overlayBufferIn);
|
baton->overlayTile = AttrTo<bool>(options, "overlayTile");
|
||||||
buffersToPersist.push_back(overlayBufferIn);
|
baton->overlayCutout = AttrTo<bool>(options, "overlayCutout");
|
||||||
}
|
|
||||||
baton->overlayGravity = attrAs<int32_t>(options, "overlayGravity");
|
|
||||||
baton->overlayXOffset = attrAs<int32_t>(options, "overlayXOffset");
|
|
||||||
baton->overlayYOffset = attrAs<int32_t>(options, "overlayYOffset");
|
|
||||||
baton->overlayTile = attrAs<bool>(options, "overlayTile");
|
|
||||||
baton->overlayCutout = attrAs<bool>(options, "overlayCutout");
|
|
||||||
// Boolean options
|
|
||||||
baton->booleanFileIn = attrAsStr(options, "booleanFileIn");
|
|
||||||
Local<Object> booleanBufferIn;
|
|
||||||
if (node::Buffer::HasInstance(Get(options, New("booleanBufferIn").ToLocalChecked()).ToLocalChecked())) {
|
|
||||||
booleanBufferIn = Get(options, New("booleanBufferIn").ToLocalChecked()).ToLocalChecked().As<Object>();
|
|
||||||
baton->booleanBufferInLength = node::Buffer::Length(booleanBufferIn);
|
|
||||||
baton->booleanBufferIn = node::Buffer::Data(booleanBufferIn);
|
|
||||||
buffersToPersist.push_back(booleanBufferIn);
|
|
||||||
}
|
}
|
||||||
// Resize options
|
// Resize options
|
||||||
baton->withoutEnlargement = attrAs<bool>(options, "withoutEnlargement");
|
baton->withoutEnlargement = AttrTo<bool>(options, "withoutEnlargement");
|
||||||
baton->crop = attrAs<int32_t>(options, "crop");
|
baton->crop = AttrTo<int32_t>(options, "crop");
|
||||||
baton->kernel = attrAsStr(options, "kernel");
|
baton->kernel = AttrAsStr(options, "kernel");
|
||||||
baton->interpolator = attrAsStr(options, "interpolator");
|
baton->interpolator = AttrAsStr(options, "interpolator");
|
||||||
// Operators
|
// Operators
|
||||||
baton->flatten = attrAs<bool>(options, "flatten");
|
baton->flatten = AttrTo<bool>(options, "flatten");
|
||||||
baton->negate = attrAs<bool>(options, "negate");
|
baton->negate = AttrTo<bool>(options, "negate");
|
||||||
baton->blurSigma = attrAs<double>(options, "blurSigma");
|
baton->blurSigma = AttrTo<double>(options, "blurSigma");
|
||||||
baton->sharpenSigma = attrAs<double>(options, "sharpenSigma");
|
baton->sharpenSigma = AttrTo<double>(options, "sharpenSigma");
|
||||||
baton->sharpenFlat = attrAs<double>(options, "sharpenFlat");
|
baton->sharpenFlat = AttrTo<double>(options, "sharpenFlat");
|
||||||
baton->sharpenJagged = attrAs<double>(options, "sharpenJagged");
|
baton->sharpenJagged = AttrTo<double>(options, "sharpenJagged");
|
||||||
baton->threshold = attrAs<int32_t>(options, "threshold");
|
baton->threshold = AttrTo<int32_t>(options, "threshold");
|
||||||
baton->thresholdGrayscale = attrAs<bool>(options, "thresholdGrayscale");
|
baton->thresholdGrayscale = AttrTo<bool>(options, "thresholdGrayscale");
|
||||||
baton->trimTolerance = attrAs<int32_t>(options, "trimTolerance");
|
baton->trimTolerance = AttrTo<int32_t>(options, "trimTolerance");
|
||||||
if(baton->accessMethod == VIPS_ACCESS_SEQUENTIAL && baton->trimTolerance != 0) {
|
if(baton->accessMethod == VIPS_ACCESS_SEQUENTIAL && baton->trimTolerance != 0) {
|
||||||
baton->accessMethod = VIPS_ACCESS_RANDOM;
|
baton->accessMethod = VIPS_ACCESS_RANDOM;
|
||||||
}
|
}
|
||||||
baton->gamma = attrAs<double>(options, "gamma");
|
baton->gamma = AttrTo<double>(options, "gamma");
|
||||||
baton->greyscale = attrAs<bool>(options, "greyscale");
|
baton->greyscale = AttrTo<bool>(options, "greyscale");
|
||||||
baton->normalize = attrAs<bool>(options, "normalize");
|
baton->normalize = AttrTo<bool>(options, "normalize");
|
||||||
baton->angle = attrAs<int32_t>(options, "angle");
|
baton->angle = AttrTo<int32_t>(options, "angle");
|
||||||
baton->rotateBeforePreExtract = attrAs<bool>(options, "rotateBeforePreExtract");
|
baton->rotateBeforePreExtract = AttrTo<bool>(options, "rotateBeforePreExtract");
|
||||||
baton->flip = attrAs<bool>(options, "flip");
|
baton->flip = AttrTo<bool>(options, "flip");
|
||||||
baton->flop = attrAs<bool>(options, "flop");
|
baton->flop = AttrTo<bool>(options, "flop");
|
||||||
baton->extendTop = attrAs<int32_t>(options, "extendTop");
|
baton->extendTop = AttrTo<int32_t>(options, "extendTop");
|
||||||
baton->extendBottom = attrAs<int32_t>(options, "extendBottom");
|
baton->extendBottom = AttrTo<int32_t>(options, "extendBottom");
|
||||||
baton->extendLeft = attrAs<int32_t>(options, "extendLeft");
|
baton->extendLeft = AttrTo<int32_t>(options, "extendLeft");
|
||||||
baton->extendRight = attrAs<int32_t>(options, "extendRight");
|
baton->extendRight = AttrTo<int32_t>(options, "extendRight");
|
||||||
baton->extractChannel = attrAs<int32_t>(options, "extractChannel");
|
baton->extractChannel = AttrTo<int32_t>(options, "extractChannel");
|
||||||
|
if (HasAttr(options, "boolean")) {
|
||||||
|
baton->boolean = CreateInputDescriptor(AttrAs<v8::Object>(options, "boolean"), buffersToPersist);
|
||||||
|
baton->booleanOp = sharp::GetBooleanOperation(AttrAsStr(options, "booleanOp"));
|
||||||
|
}
|
||||||
|
if (HasAttr(options, "bandBoolOp")) {
|
||||||
|
baton->bandBoolOp = sharp::GetBooleanOperation(AttrAsStr(options, "bandBoolOp"));
|
||||||
|
}
|
||||||
|
if (HasAttr(options, "convKernel")) {
|
||||||
|
v8::Local<v8::Object> kernel = AttrAs<v8::Object>(options, "convKernel");
|
||||||
|
baton->convKernelWidth = AttrTo<uint32_t>(kernel, "width");
|
||||||
|
baton->convKernelHeight = AttrTo<uint32_t>(kernel, "height");
|
||||||
|
baton->convKernelScale = AttrTo<double>(kernel, "scale");
|
||||||
|
baton->convKernelOffset = AttrTo<double>(kernel, "offset");
|
||||||
|
size_t const kernelSize = static_cast<size_t>(baton->convKernelWidth * baton->convKernelHeight);
|
||||||
|
baton->convKernel = std::unique_ptr<double[]>(new double[kernelSize]);
|
||||||
|
v8::Local<v8::Array> kdata = AttrAs<v8::Array>(kernel, "kernel");
|
||||||
|
for (unsigned int i = 0; i < kernelSize; i++) {
|
||||||
|
baton->convKernel[i] = AttrTo<double>(kdata, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
// Output options
|
// Output options
|
||||||
baton->progressive = attrAs<bool>(options, "progressive");
|
baton->progressive = AttrTo<bool>(options, "progressive");
|
||||||
baton->quality = attrAs<int32_t>(options, "quality");
|
baton->quality = AttrTo<int32_t>(options, "quality");
|
||||||
baton->compressionLevel = attrAs<int32_t>(options, "compressionLevel");
|
baton->compressionLevel = AttrTo<int32_t>(options, "compressionLevel");
|
||||||
baton->withoutAdaptiveFiltering = attrAs<bool>(options, "withoutAdaptiveFiltering");
|
baton->withoutAdaptiveFiltering = AttrTo<bool>(options, "withoutAdaptiveFiltering");
|
||||||
baton->withoutChromaSubsampling = attrAs<bool>(options, "withoutChromaSubsampling");
|
baton->withoutChromaSubsampling = AttrTo<bool>(options, "withoutChromaSubsampling");
|
||||||
baton->trellisQuantisation = attrAs<bool>(options, "trellisQuantisation");
|
baton->trellisQuantisation = AttrTo<bool>(options, "trellisQuantisation");
|
||||||
baton->overshootDeringing = attrAs<bool>(options, "overshootDeringing");
|
baton->overshootDeringing = AttrTo<bool>(options, "overshootDeringing");
|
||||||
baton->optimiseScans = attrAs<bool>(options, "optimiseScans");
|
baton->optimiseScans = AttrTo<bool>(options, "optimiseScans");
|
||||||
baton->withMetadata = attrAs<bool>(options, "withMetadata");
|
baton->withMetadata = AttrTo<bool>(options, "withMetadata");
|
||||||
baton->withMetadataOrientation = attrAs<int32_t>(options, "withMetadataOrientation");
|
baton->withMetadataOrientation = AttrTo<uint32_t>(options, "withMetadataOrientation");
|
||||||
// Output
|
// Output
|
||||||
baton->formatOut = attrAsStr(options, "formatOut");
|
baton->formatOut = AttrAsStr(options, "formatOut");
|
||||||
baton->fileOut = attrAsStr(options, "fileOut");
|
baton->fileOut = AttrAsStr(options, "fileOut");
|
||||||
// Tile output
|
// Tile output
|
||||||
baton->tileSize = attrAs<int32_t>(options, "tileSize");
|
baton->tileSize = AttrTo<uint32_t>(options, "tileSize");
|
||||||
baton->tileOverlap = attrAs<int32_t>(options, "tileOverlap");
|
baton->tileOverlap = AttrTo<uint32_t>(options, "tileOverlap");
|
||||||
std::string tileContainer = attrAsStr(options, "tileContainer");
|
std::string tileContainer = AttrAsStr(options, "tileContainer");
|
||||||
if (tileContainer == "zip") {
|
if (tileContainer == "zip") {
|
||||||
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
|
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
|
||||||
} else {
|
} else {
|
||||||
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_FS;
|
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_FS;
|
||||||
}
|
}
|
||||||
std::string tileLayout = attrAsStr(options, "tileLayout");
|
std::string tileLayout = AttrAsStr(options, "tileLayout");
|
||||||
if (tileLayout == "google") {
|
if (tileLayout == "google") {
|
||||||
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_GOOGLE;
|
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_GOOGLE;
|
||||||
} else if (tileLayout == "zoomify") {
|
} else if (tileLayout == "zoomify") {
|
||||||
@ -1292,42 +1083,16 @@ NAN_METHOD(pipeline) {
|
|||||||
} else {
|
} else {
|
||||||
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_DZ;
|
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_DZ;
|
||||||
}
|
}
|
||||||
// Convolution Kernel
|
|
||||||
if(Has(options, New("convKernel").ToLocalChecked()).FromJust()) {
|
|
||||||
Local<Object> kernel = Get(options, New("convKernel").ToLocalChecked()).ToLocalChecked().As<Object>();
|
|
||||||
baton->convKernelWidth = attrAs<uint32_t>(kernel, "width");
|
|
||||||
baton->convKernelHeight = attrAs<uint32_t>(kernel, "height");
|
|
||||||
baton->convKernelScale = attrAs<double>(kernel, "scale");
|
|
||||||
baton->convKernelOffset = attrAs<double>(kernel, "offset");
|
|
||||||
|
|
||||||
size_t const kernelSize = static_cast<size_t>(baton->convKernelWidth * baton->convKernelHeight);
|
|
||||||
baton->convKernel = std::unique_ptr<double[]>(new double[kernelSize]);
|
|
||||||
Local<Array> kdata = Get(kernel, New("kernel").ToLocalChecked()).ToLocalChecked().As<Array>();
|
|
||||||
for(unsigned int i = 0; i < kernelSize; i++) {
|
|
||||||
baton->convKernel[i] = To<double>(Get(kdata, i).ToLocalChecked()).FromJust();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Bandbool operation
|
|
||||||
if(Has(options, New("bandBoolOp").ToLocalChecked()).FromJust()) {
|
|
||||||
baton->bandBoolOp = GetBooleanOperation(attrAsStr(options, "bandBoolOp"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Boolean operation
|
|
||||||
if(Has(options, New("booleanOp").ToLocalChecked()).FromJust()) {
|
|
||||||
baton->booleanOp = GetBooleanOperation(attrAsStr(options, "booleanOp"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to notify of queue length changes
|
// Function to notify of queue length changes
|
||||||
Callback *queueListener = new Callback(
|
Nan::Callback *queueListener = new Nan::Callback(AttrAs<v8::Function>(options, "queueListener"));
|
||||||
Get(options, New("queueListener").ToLocalChecked()).ToLocalChecked().As<Function>()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Join queue for worker thread
|
// Join queue for worker thread
|
||||||
Callback *callback = new Callback(info[1].As<Function>());
|
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>());
|
||||||
AsyncQueueWorker(new PipelineWorker(callback, baton, queueListener, buffersToPersist));
|
Nan::AsyncQueueWorker(new PipelineWorker(callback, baton, queueListener, buffersToPersist));
|
||||||
|
|
||||||
// Increment queued task counter
|
// Increment queued task counter
|
||||||
g_atomic_int_inc(&counterQueue);
|
g_atomic_int_inc(&sharp::counterQueue);
|
||||||
Local<Value> queueLength[1] = { New<Uint32>(counterQueue) };
|
v8::Local<v8::Value> queueLength[1] = { Nan::New<v8::Uint32>(sharp::counterQueue) };
|
||||||
queueListener->Call(1, queueLength);
|
queueListener->Call(1, queueLength);
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include <vips/vips8>
|
#include <vips/vips8>
|
||||||
|
|
||||||
#include "nan.h"
|
#include "nan.h"
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
NAN_METHOD(pipeline);
|
NAN_METHOD(pipeline);
|
||||||
|
|
||||||
@ -18,30 +19,19 @@ enum class Canvas {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct PipelineBaton {
|
struct PipelineBaton {
|
||||||
std::string fileIn;
|
sharp::InputDescriptor *input;
|
||||||
char *bufferIn;
|
|
||||||
size_t bufferInLength;
|
|
||||||
std::string iccProfilePath;
|
std::string iccProfilePath;
|
||||||
int limitInputPixels;
|
int limitInputPixels;
|
||||||
int density;
|
|
||||||
int rawWidth;
|
|
||||||
int rawHeight;
|
|
||||||
int rawChannels;
|
|
||||||
std::string formatOut;
|
std::string formatOut;
|
||||||
std::string fileOut;
|
std::string fileOut;
|
||||||
void *bufferOut;
|
void *bufferOut;
|
||||||
size_t bufferOutLength;
|
size_t bufferOutLength;
|
||||||
std::string overlayFileIn;
|
sharp::InputDescriptor *overlay;
|
||||||
char *overlayBufferIn;
|
|
||||||
size_t overlayBufferInLength;
|
|
||||||
int overlayGravity;
|
int overlayGravity;
|
||||||
int overlayXOffset;
|
int overlayXOffset;
|
||||||
int overlayYOffset;
|
int overlayYOffset;
|
||||||
bool overlayTile;
|
bool overlayTile;
|
||||||
bool overlayCutout;
|
bool overlayCutout;
|
||||||
std::string booleanFileIn;
|
|
||||||
char *booleanBufferIn;
|
|
||||||
size_t booleanBufferInLength;
|
|
||||||
int topOffsetPre;
|
int topOffsetPre;
|
||||||
int leftOffsetPre;
|
int leftOffsetPre;
|
||||||
int widthPre;
|
int widthPre;
|
||||||
@ -96,8 +86,9 @@ struct PipelineBaton {
|
|||||||
int convKernelHeight;
|
int convKernelHeight;
|
||||||
double convKernelScale;
|
double convKernelScale;
|
||||||
double convKernelOffset;
|
double convKernelOffset;
|
||||||
VipsOperationBoolean bandBoolOp;
|
sharp::InputDescriptor *boolean;
|
||||||
VipsOperationBoolean booleanOp;
|
VipsOperationBoolean booleanOp;
|
||||||
|
VipsOperationBoolean bandBoolOp;
|
||||||
int extractChannel;
|
int extractChannel;
|
||||||
int tileSize;
|
int tileSize;
|
||||||
int tileOverlap;
|
int tileOverlap;
|
||||||
@ -105,22 +96,15 @@ struct PipelineBaton {
|
|||||||
VipsForeignDzLayout tileLayout;
|
VipsForeignDzLayout tileLayout;
|
||||||
|
|
||||||
PipelineBaton():
|
PipelineBaton():
|
||||||
bufferInLength(0),
|
input(nullptr),
|
||||||
limitInputPixels(0),
|
limitInputPixels(0),
|
||||||
density(72),
|
|
||||||
rawWidth(0),
|
|
||||||
rawHeight(0),
|
|
||||||
rawChannels(0),
|
|
||||||
formatOut(""),
|
|
||||||
fileOut(""),
|
|
||||||
bufferOutLength(0),
|
bufferOutLength(0),
|
||||||
overlayBufferInLength(0),
|
overlay(nullptr),
|
||||||
overlayGravity(0),
|
overlayGravity(0),
|
||||||
overlayXOffset(-1),
|
overlayXOffset(-1),
|
||||||
overlayYOffset(-1),
|
overlayYOffset(-1),
|
||||||
overlayTile(false),
|
overlayTile(false),
|
||||||
overlayCutout(false),
|
overlayCutout(false),
|
||||||
booleanBufferInLength(0),
|
|
||||||
topOffsetPre(-1),
|
topOffsetPre(-1),
|
||||||
topOffsetPost(-1),
|
topOffsetPost(-1),
|
||||||
channels(0),
|
channels(0),
|
||||||
@ -160,8 +144,9 @@ struct PipelineBaton {
|
|||||||
convKernelHeight(0),
|
convKernelHeight(0),
|
||||||
convKernelScale(0.0),
|
convKernelScale(0.0),
|
||||||
convKernelOffset(0.0),
|
convKernelOffset(0.0),
|
||||||
bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST),
|
boolean(nullptr),
|
||||||
booleanOp(VIPS_OPERATION_BOOLEAN_LAST),
|
booleanOp(VIPS_OPERATION_BOOLEAN_LAST),
|
||||||
|
bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST),
|
||||||
extractChannel(-1),
|
extractChannel(-1),
|
||||||
tileSize(256),
|
tileSize(256),
|
||||||
tileOverlap(0),
|
tileOverlap(0),
|
||||||
|
@ -40,6 +40,23 @@ describe('Boolean operation between two images', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it(op + ' operation, raw', function(done) {
|
||||||
|
sharp(fixtures.inputJpgBooleanTest)
|
||||||
|
.raw()
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.boolean(data, op, { raw: info })
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
fixtures.assertSimilar(fixtures.expected('boolean_' + op + '_result.jpg'), data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Invalid operation', function() {
|
it('Invalid operation', function() {
|
||||||
|
@ -528,4 +528,28 @@ describe('Overlays', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Composite RGBA raw buffer onto JPEG', function(done) {
|
||||||
|
sharp(fixtures.inputPngOverlayLayer1)
|
||||||
|
.raw()
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(2048, 1536)
|
||||||
|
.overlayWith(data, { raw: info })
|
||||||
|
.toBuffer(function(err, data) {
|
||||||
|
if (err) throw err;
|
||||||
|
fixtures.assertSimilar(fixtures.expected('overlay-jpeg-with-rgb.jpg'), data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Throws an error when called with an invalid file', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.overlayWith('notfound.png')
|
||||||
|
.toBuffer(function(err) {
|
||||||
|
assert(err instanceof Error);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user