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:
Lovell Fuller
2016-07-26 22:26:55 +01:00
parent 36bfbdee0d
commit e6bfa52b0b
11 changed files with 589 additions and 696 deletions

View File

@@ -1,32 +1,53 @@
#include <cstdlib>
#include <string>
#include <string.h>
#include <node.h>
#include <node_buffer.h>
#include <vips/vips8>
#include "nan.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;
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?
volatile int counterQueue = 0;
@@ -149,6 +170,73 @@ namespace sharp {
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?
*/

View File

@@ -4,12 +4,70 @@
#include <string>
#include <tuple>
#include <node.h>
#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;
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 {
JPEG,
PNG,
@@ -57,6 +115,11 @@ namespace sharp {
*/
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?
*/

View File

@@ -1,135 +1,54 @@
#include <numeric>
#include <node.h>
#include <vips/vips8>
#include "nan.h"
#include "common.h"
#include "metadata.h"
using v8::Handle;
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 {
class MetadataWorker : public Nan::AsyncWorker {
public:
MetadataWorker(Callback *callback, MetadataBaton *baton, const Local<Object> &bufferIn) :
AsyncWorker(callback), baton(baton) {
if (baton->bufferInLength > 0) {
SaveToPersistent("bufferIn", bufferIn);
MetadataWorker(
Nan::Callback *callback, MetadataBaton *baton,
std::vector<v8::Local<v8::Object>> const buffersToPersist
) : 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() {}
void Execute() {
// Decrement queued task counter
g_atomic_int_dec_and_test(&counterQueue);
g_atomic_int_dec_and_test(&sharp::counterQueue);
ImageType imageType = ImageType::UNKNOWN;
VImage image;
if (baton->bufferInLength > 0) {
// From buffer
imageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
if (imageType != ImageType::UNKNOWN) {
try {
image = VImage::new_from_buffer(baton->bufferIn, baton->bufferInLength, nullptr);
} catch (...) {
(baton->err).append("Input buffer has corrupt header");
imageType = ImageType::UNKNOWN;
}
} else {
(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");
}
vips::VImage image;
sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
try {
std::tie(image, imageType) = OpenInput(baton->input, VIPS_ACCESS_SEQUENTIAL);
} catch (vips::VError const &err) {
(baton->err).append(err.what());
}
if (imageType != ImageType::UNKNOWN) {
if (imageType != sharp::ImageType::UNKNOWN) {
// Image type
baton->format = ImageTypeId(imageType);
baton->format = sharp::ImageTypeId(imageType);
// VipsImage attributes
baton->width = image.width();
baton->height = image.height();
baton->space = vips_enum_nick(VIPS_TYPE_INTERPRETATION, image.interpretation());
baton->channels = image.bands();
if (HasDensity(image)) {
baton->density = GetDensity(image);
if (sharp::HasDensity(image)) {
baton->density = sharp::GetDensity(image);
}
baton->hasProfile = HasProfile(image);
baton->hasProfile = sharp::HasProfile(image);
// Derived attributes
baton->hasAlpha = HasAlpha(image);
baton->orientation = ExifOrientation(image);
baton->hasAlpha = sharp::HasAlpha(image);
baton->orientation = sharp::ExifOrientation(image);
// EXIF
if (image.get_typeof(VIPS_META_EXIF_NAME) == VIPS_TYPE_BLOB) {
size_t exifLength;
@@ -147,53 +66,59 @@ class MetadataWorker : public AsyncWorker {
baton->iccLength = iccLength;
}
}
// Clean up
vips_error_clear();
vips_thread_shutdown();
}
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()) {
// Error
argv[0] = Error(baton->err.data());
argv[0] = Nan::Error(baton->err.data());
} else {
// Metadata Object
Local<Object> info = New<Object>();
Set(info, New("format").ToLocalChecked(), New<String>(baton->format).ToLocalChecked());
Set(info, New("width").ToLocalChecked(), New<Number>(baton->width));
Set(info, New("height").ToLocalChecked(), New<Number>(baton->height));
Set(info, New("space").ToLocalChecked(), New<String>(baton->space).ToLocalChecked());
Set(info, New("channels").ToLocalChecked(), New<Number>(baton->channels));
v8::Local<v8::Object> info = New<v8::Object>();
Set(info, New("format").ToLocalChecked(), New<v8::String>(baton->format).ToLocalChecked());
Set(info, New("width").ToLocalChecked(), New<v8::Uint32>(baton->width));
Set(info, New("height").ToLocalChecked(), New<v8::Uint32>(baton->height));
Set(info, New("space").ToLocalChecked(), New<v8::String>(baton->space).ToLocalChecked());
Set(info, New("channels").ToLocalChecked(), New<v8::Uint32>(baton->channels));
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("hasAlpha").ToLocalChecked(), New<Boolean>(baton->hasAlpha));
Set(info, New("hasProfile").ToLocalChecked(), New<v8::Boolean>(baton->hasProfile));
Set(info, New("hasAlpha").ToLocalChecked(), New<v8::Boolean>(baton->hasAlpha));
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) {
Set(info,
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) {
Set(info,
New("icc").ToLocalChecked(),
NewBuffer(baton->icc, baton->iccLength, FreeCallback, nullptr).ToLocalChecked()
Nan::NewBuffer(baton->icc, baton->iccLength, sharp::FreeCallback, nullptr).ToLocalChecked()
);
}
argv[1] = info;
}
// Dispose of Persistent wrapper around input Buffer so it can be garbage collected
if (baton->bufferInLength > 0) {
GetFromPersistent("bufferIn");
}
// Dispose of Persistent wrapper around input Buffers so they can be garbage collected
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
GetFromPersistent(index);
return index + 1;
}
);
delete baton->input;
delete baton;
// Return to JavaScript
@@ -202,32 +127,27 @@ class MetadataWorker : public AsyncWorker {
private:
MetadataBaton* baton;
std::vector<v8::Local<v8::Object>> buffersToPersist;
};
/*
metadata(options, callback)
*/
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
MetadataBaton *baton = new MetadataBaton;
Local<Object> options = info[0].As<Object>();
v8::Local<v8::Object> options = info[0].As<v8::Object>();
// Input filename
baton->fileIn = *Utf8String(Get(options, New("fileIn").ToLocalChecked()).ToLocalChecked());
// 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);
}
// Input
baton->input = sharp::CreateInputDescriptor(sharp::AttrAs<v8::Object>(options, "input"), buffersToPersist);
// Join queue for worker thread
Callback *callback = new Callback(info[1].As<Function>());
AsyncQueueWorker(new MetadataWorker(callback, baton, bufferIn));
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>());
Nan::AsyncQueueWorker(new MetadataWorker(callback, baton, buffersToPersist));
// Increment queued task counter
g_atomic_int_inc(&counterQueue);
g_atomic_int_inc(&sharp::counterQueue);
}

View File

@@ -2,6 +2,41 @@
#define SRC_METADATA_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);

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,7 @@
#include <vips/vips8>
#include "nan.h"
#include "common.h"
NAN_METHOD(pipeline);
@@ -18,30 +19,19 @@ enum class Canvas {
};
struct PipelineBaton {
std::string fileIn;
char *bufferIn;
size_t bufferInLength;
sharp::InputDescriptor *input;
std::string iccProfilePath;
int limitInputPixels;
int density;
int rawWidth;
int rawHeight;
int rawChannels;
std::string formatOut;
std::string fileOut;
void *bufferOut;
size_t bufferOutLength;
std::string overlayFileIn;
char *overlayBufferIn;
size_t overlayBufferInLength;
sharp::InputDescriptor *overlay;
int overlayGravity;
int overlayXOffset;
int overlayYOffset;
bool overlayTile;
bool overlayCutout;
std::string booleanFileIn;
char *booleanBufferIn;
size_t booleanBufferInLength;
int topOffsetPre;
int leftOffsetPre;
int widthPre;
@@ -96,8 +86,9 @@ struct PipelineBaton {
int convKernelHeight;
double convKernelScale;
double convKernelOffset;
VipsOperationBoolean bandBoolOp;
sharp::InputDescriptor *boolean;
VipsOperationBoolean booleanOp;
VipsOperationBoolean bandBoolOp;
int extractChannel;
int tileSize;
int tileOverlap;
@@ -105,22 +96,15 @@ struct PipelineBaton {
VipsForeignDzLayout tileLayout;
PipelineBaton():
bufferInLength(0),
input(nullptr),
limitInputPixels(0),
density(72),
rawWidth(0),
rawHeight(0),
rawChannels(0),
formatOut(""),
fileOut(""),
bufferOutLength(0),
overlayBufferInLength(0),
overlay(nullptr),
overlayGravity(0),
overlayXOffset(-1),
overlayYOffset(-1),
overlayTile(false),
overlayCutout(false),
booleanBufferInLength(0),
topOffsetPre(-1),
topOffsetPost(-1),
channels(0),
@@ -160,8 +144,9 @@ struct PipelineBaton {
convKernelHeight(0),
convKernelScale(0.0),
convKernelOffset(0.0),
bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST),
boolean(nullptr),
booleanOp(VIPS_OPERATION_BOOLEAN_LAST),
bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST),
extractChannel(-1),
tileSize(256),
tileOverlap(0),