Less C, more C++ e.g. namespace, enum class

Improve image reference handling
This commit is contained in:
Lovell Fuller 2014-11-11 18:28:23 +00:00
parent e465306d97
commit ee513ac7a7
5 changed files with 354 additions and 335 deletions

View File

@ -4,127 +4,161 @@
#include "common.h"
// How many tasks are in the queue?
volatile int counter_queue = 0;
namespace sharp {
// How many tasks are being processed?
volatile int counter_process = 0;
// How many tasks are in the queue?
volatile int counterQueue = 0;
// Filename extension checkers
static bool ends_with(std::string const &str, std::string const &end) {
return str.length() >= end.length() && 0 == str.compare(str.length() - end.length(), end.length(), end);
}
bool is_jpeg(std::string const &str) {
return ends_with(str, ".jpg") || ends_with(str, ".jpeg") || ends_with(str, ".JPG") || ends_with(str, ".JPEG");
}
bool is_png(std::string const &str) {
return ends_with(str, ".png") || ends_with(str, ".PNG");
}
bool is_webp(std::string const &str) {
return ends_with(str, ".webp") || ends_with(str, ".WEBP");
}
bool is_tiff(std::string const &str) {
return ends_with(str, ".tif") || ends_with(str, ".tiff") || ends_with(str, ".TIF") || ends_with(str, ".TIFF");
}
// How many tasks are being processed?
volatile int counterProcess = 0;
// Buffer content checkers
// Filename extension checkers
static bool EndsWith(std::string const &str, std::string const &end) {
return str.length() >= end.length() && 0 == str.compare(str.length() - end.length(), end.length(), end);
}
bool IsJpeg(std::string const &str) {
return EndsWith(str, ".jpg") || EndsWith(str, ".jpeg") || EndsWith(str, ".JPG") || EndsWith(str, ".JPEG");
}
bool IsPng(std::string const &str) {
return EndsWith(str, ".png") || EndsWith(str, ".PNG");
}
bool IsWebp(std::string const &str) {
return EndsWith(str, ".webp") || EndsWith(str, ".WEBP");
}
bool IsTiff(std::string const &str) {
return EndsWith(str, ".tif") || EndsWith(str, ".tiff") || EndsWith(str, ".TIF") || EndsWith(str, ".TIFF");
}
// Buffer content checkers
unsigned char const MARKER_JPEG[] = {0xff, 0xd8};
unsigned char const MARKER_PNG[] = {0x89, 0x50};
unsigned char const MARKER_WEBP[] = {0x52, 0x49};
#if (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 40)
static bool buffer_is_tiff(char *buffer, size_t len) {
return (
len >= 4 && (
(buffer[0] == 'M' && buffer[1] == 'M' && buffer[2] == '\0' && (buffer[3] == '*' || buffer[3] == '+')) ||
(buffer[0] == 'I' && buffer[1] == 'I' && (buffer[2] == '*' || buffer[2] == '+') && buffer[3] == '\0')
)
);
}
static bool buffer_is_tiff(char *buffer, size_t len) {
return (
len >= 4 && (
(buffer[0] == 'M' && buffer[1] == 'M' && buffer[2] == '\0' && (buffer[3] == '*' || buffer[3] == '+')) ||
(buffer[0] == 'I' && buffer[1] == 'I' && (buffer[2] == '*' || buffer[2] == '+') && buffer[3] == '\0')
)
);
}
#endif
unsigned char const MARKER_JPEG[] = {0xff, 0xd8};
unsigned char const MARKER_PNG[] = {0x89, 0x50};
unsigned char const MARKER_WEBP[] = {0x52, 0x49};
/*
Initialise a VipsImage from a buffer. Supports JPEG, PNG, WebP and TIFF.
Returns the ImageType detected, if any.
*/
ImageType
sharp_init_image_from_buffer(VipsImage **image, void *buffer, size_t const length, VipsAccess const access) {
ImageType imageType = UNKNOWN;
if (memcmp(MARKER_JPEG, buffer, 2) == 0) {
if (!vips_jpegload_buffer(buffer, length, image, "access", access, NULL)) {
imageType = JPEG;
}
} else if (memcmp(MARKER_PNG, buffer, 2) == 0) {
if (!vips_pngload_buffer(buffer, length, image, "access", access, NULL)) {
imageType = PNG;
}
} else if (memcmp(MARKER_WEBP, buffer, 2) == 0) {
if (!vips_webpload_buffer(buffer, length, image, "access", access, NULL)) {
imageType = WEBP;
}
/*
Determine image format of a buffer.
*/
ImageType DetermineImageType(void *buffer, size_t const length) {
ImageType imageType = ImageType::UNKNOWN;
if (length >= 4) {
if (memcmp(MARKER_JPEG, buffer, 2) == 0) {
imageType = ImageType::JPEG;
} else if (memcmp(MARKER_PNG, buffer, 2) == 0) {
imageType = ImageType::PNG;
} else if (memcmp(MARKER_WEBP, buffer, 2) == 0) {
imageType = ImageType::WEBP;
#if (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 40)
} else if (buffer_is_tiff(static_cast<char*>(buffer), length)) {
if (!vips_tiffload_buffer(buffer, length, image, "access", access, NULL)) {
imageType = TIFF;
}
} else if (buffer_is_tiff(static_cast<char*>(buffer), length)) {
imageType = ImageType::TIFF;
#endif
}
}
return imageType;
}
return imageType;
}
/*
Initialise a VipsImage from a file.
Returns the ImageType detected, if any.
*/
ImageType
sharp_init_image_from_file(VipsImage **image, char const *file, VipsAccess const access) {
ImageType imageType = UNKNOWN;
if (vips_foreign_is_a("jpegload", file)) {
if (!vips_jpegload(file, image, "access", access, NULL)) {
imageType = JPEG;
}
} else if (vips_foreign_is_a("pngload", file)) {
if (!vips_pngload(file, image, "access", access, NULL)) {
imageType = PNG;
}
} else if (vips_foreign_is_a("webpload", file)) {
if (!vips_webpload(file, image, "access", access, NULL)) {
imageType = WEBP;
}
} else if (vips_foreign_is_a("tiffload", file)) {
if (!vips_tiffload(file, image, "access", access, NULL)) {
imageType = TIFF;
}
} else if(vips_foreign_is_a("magickload", file)) {
if (!vips_magickload(file, image, "access", access, NULL)) {
imageType = MAGICK;
/*
Initialise and return a VipsImage from a buffer. Supports JPEG, PNG, WebP and TIFF.
*/
VipsImage* InitImage(ImageType imageType, void *buffer, size_t const length, VipsAccess const access) {
VipsImage *image = NULL;
if (imageType == ImageType::JPEG) {
vips_jpegload_buffer(buffer, length, &image, "access", access, NULL);
} else if (imageType == ImageType::PNG) {
vips_pngload_buffer(buffer, length, &image, "access", access, NULL);
} else if (imageType == ImageType::WEBP) {
vips_webpload_buffer(buffer, length, &image, "access", access, NULL);
#if (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 40)
} else if (imageType == ImageType::TIFF) {
vips_tiffload_buffer(buffer, length, &image, "access", access, NULL);
#endif
}
return image;
}
return imageType;
}
/*
Does this image have an alpha channel?
Uses colour space interpretation with number of channels to guess this.
*/
bool
sharp_image_has_alpha(VipsImage *image) {
return (
(image->Bands == 2 && image->Type == VIPS_INTERPRETATION_B_W) ||
(image->Bands == 4 && image->Type != VIPS_INTERPRETATION_CMYK) ||
(image->Bands == 5 && image->Type == VIPS_INTERPRETATION_CMYK)
);
}
/*
Inpect the first 2-4 bytes of a file to determine image format
*/
ImageType DetermineImageType(char const *file) {
ImageType imageType = ImageType::UNKNOWN;
if (vips_foreign_is_a("jpegload", file)) {
imageType = ImageType::JPEG;
} else if (vips_foreign_is_a("pngload", file)) {
imageType = ImageType::PNG;
} else if (vips_foreign_is_a("webpload", file)) {
imageType = ImageType::WEBP;
} else if (vips_foreign_is_a("tiffload", file)) {
imageType = ImageType::TIFF;
} else if(vips_foreign_is_a("magickload", file)) {
imageType = ImageType::MAGICK;
}
return imageType;
}
/*
Returns the window size for the named interpolator. For example,
a window size of 3 means a 3x3 pixel grid is used for the calculation.
*/
int
sharp_interpolator_window_size(char const *name) {
VipsInterpolate *interpolator = vips_interpolate_new(name);
int window_size = vips_interpolate_get_window_size(interpolator);
g_object_unref(interpolator);
return window_size;
}
/*
Initialise and return a VipsImage from a file.
*/
VipsImage* InitImage(ImageType imageType, char const *file, VipsAccess const access) {
VipsImage *image = NULL;
if (imageType == ImageType::JPEG) {
vips_jpegload(file, &image, "access", access, NULL);
} else if (imageType == ImageType::PNG) {
vips_pngload(file, &image, "access", access, NULL);
} else if (imageType == ImageType::WEBP) {
vips_webpload(file, &image, "access", access, NULL);
} else if (imageType == ImageType::TIFF) {
vips_tiffload(file, &image, "access", access, NULL);
} else if (imageType == ImageType::MAGICK) {
vips_magickload(file, &image, "access", access, NULL);
}
return image;
}
/*
Does this image have an alpha channel?
Uses colour space interpretation with number of channels to guess this.
*/
bool HasAlpha(VipsImage *image) {
return (
(image->Bands == 2 && image->Type == VIPS_INTERPRETATION_B_W) ||
(image->Bands == 4 && image->Type != VIPS_INTERPRETATION_CMYK) ||
(image->Bands == 5 && image->Type == VIPS_INTERPRETATION_CMYK)
);
}
/*
Get EXIF Orientation of image, if any.
*/
int ExifOrientation(VipsImage const *image) {
int orientation = 0;
const char *exif;
if (
vips_image_get_typeof(image, "exif-ifd0-Orientation") != 0 &&
!vips_image_get_string(image, "exif-ifd0-Orientation", &exif)
) {
orientation = atoi(&exif[0]);
}
return orientation;
}
/*
Returns the window size for the named interpolator. For example,
a window size of 3 means a 3x3 pixel grid is used for the calculation.
*/
int InterpolatorWindowSize(char const *name) {
VipsInterpolate *interpolator = vips_interpolate_new(name);
int window_size = vips_interpolate_get_window_size(interpolator);
g_object_unref(interpolator);
return window_size;
}
} // namespace

View File

@ -1,53 +1,66 @@
#ifndef SHARP_COMMON_H
#define SHARP_COMMON_H
typedef enum {
UNKNOWN,
JPEG,
PNG,
WEBP,
TIFF,
MAGICK
} ImageType;
namespace sharp {
// Filename extension checkers
bool is_jpeg(std::string const &str);
bool is_png(std::string const &str);
bool is_webp(std::string const &str);
bool is_tiff(std::string const &str);
enum class ImageType {
UNKNOWN,
JPEG,
PNG,
WEBP,
TIFF,
MAGICK
};
// How many tasks are in the queue?
extern volatile int counter_queue;
// How many tasks are in the queue?
extern volatile int counterQueue;
// How many tasks are being processed?
extern volatile int counter_process;
// How many tasks are being processed?
extern volatile int counterProcess;
/*
Initialise a VipsImage from a buffer. Supports JPEG, PNG and WebP.
Returns the ImageType detected, if any.
*/
ImageType
sharp_init_image_from_buffer(VipsImage **image, void *buffer, size_t const length, VipsAccess const access);
// Filename extension checkers
bool IsJpeg(std::string const &str);
bool IsPng(std::string const &str);
bool IsWebp(std::string const &str);
bool IsTiff(std::string const &str);
/*
Initialise a VipsImage from a file.
Returns the ImageType detected, if any.
*/
ImageType
sharp_init_image_from_file(VipsImage **image, char const *file, VipsAccess const access);
/*
Determine image format of a buffer.
*/
ImageType DetermineImageType(void *buffer, size_t const length);
/*
Does this image have an alpha channel?
Uses colour space interpretation with number of channels to guess this.
*/
bool
sharp_image_has_alpha(VipsImage *image);
/*
Determine image format of a file.
*/
ImageType DetermineImageType(char const *file);
/*
Returns the window size for the named interpolator. For example,
a window size of 3 means a 3x3 pixel grid is used for the calculation.
*/
int
sharp_interpolator_window_size(char const *name);
/*
Initialise and return a VipsImage from a buffer. Supports JPEG, PNG, WebP and TIFF.
*/
VipsImage* InitImage(ImageType imageType, void *buffer, size_t const length, VipsAccess const access);
/*
Initialise and return a VipsImage from a file.
*/
VipsImage* InitImage(ImageType imageType, char const *file, VipsAccess const access);
/*
Does this image have an alpha channel?
Uses colour space interpretation with number of channels to guess this.
*/
bool HasAlpha(VipsImage *image);
/*
Get EXIF Orientation of image, if any.
*/
int ExifOrientation(VipsImage const *image);
/*
Returns the window size for the named interpolator. For example,
a window size of 3 means a 3x3 pixel grid is used for the calculation.
*/
int InterpolatorWindowSize(char const *name);
} // namespace
#endif

View File

@ -7,6 +7,7 @@
#include "metadata.h"
using namespace v8;
using namespace sharp;
struct MetadataBaton {
// Input
@ -36,49 +37,49 @@ class MetadataWorker : public NanAsyncWorker {
void Execute() {
// Decrement queued task counter
g_atomic_int_dec_and_test(&counter_queue);
g_atomic_int_dec_and_test(&counterQueue);
ImageType imageType = UNKNOWN;
VipsImage *image;
ImageType imageType = ImageType::UNKNOWN;
VipsImage *image = NULL;
if (baton->bufferInLength > 1) {
// From buffer
imageType = sharp_init_image_from_buffer(&image, baton->bufferIn, baton->bufferInLength, VIPS_ACCESS_RANDOM);
if (imageType == UNKNOWN) {
imageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
if (imageType != ImageType::UNKNOWN) {
image = InitImage(imageType, baton->bufferIn, baton->bufferInLength, VIPS_ACCESS_RANDOM);
} else {
(baton->err).append("Input buffer contains unsupported image format");
}
} else {
// From file
imageType = sharp_init_image_from_file(&image, baton->fileIn.c_str(), VIPS_ACCESS_RANDOM);
if (imageType == UNKNOWN) {
imageType = DetermineImageType(baton->fileIn.c_str());
if (imageType != ImageType::UNKNOWN) {
image = InitImage(imageType, baton->fileIn.c_str(), VIPS_ACCESS_RANDOM);
} else {
(baton->err).append("File is of an unsupported image format");
}
}
if (imageType != UNKNOWN) {
if (image != NULL && imageType != ImageType::UNKNOWN) {
// Image type
switch (imageType) {
case JPEG: baton->format = "jpeg"; break;
case PNG: baton->format = "png"; break;
case WEBP: baton->format = "webp"; break;
case TIFF: baton->format = "tiff"; break;
case MAGICK: baton->format = "magick"; break;
case UNKNOWN: default: baton->format = "";
case ImageType::JPEG: baton->format = "jpeg"; break;
case ImageType::PNG: baton->format = "png"; break;
case ImageType::WEBP: baton->format = "webp"; break;
case ImageType::TIFF: baton->format = "tiff"; break;
case ImageType::MAGICK: baton->format = "magick"; break;
case ImageType::UNKNOWN: break;
}
// VipsImage attributes
baton->width = image->Xsize;
baton->height = image->Ysize;
baton->space = vips_enum_nick(VIPS_TYPE_INTERPRETATION, image->Type);
baton->channels = image->Bands;
baton->hasAlpha = sharp_image_has_alpha(image);
// EXIF Orientation
const char *exif;
if (!vips_image_get_string(image, "exif-ifd0-Orientation", &exif)) {
baton->orientation = atoi(&exif[0]);
}
}
// Clean up
if (imageType != UNKNOWN) {
// Derived attributes
baton->hasAlpha = HasAlpha(image);
baton->orientation = ExifOrientation(image);
// Drop image reference
g_object_unref(image);
}
// Clean up
vips_error_clear();
vips_thread_shutdown();
}
@ -138,7 +139,7 @@ NAN_METHOD(metadata) {
NanAsyncQueueWorker(new MetadataWorker(callback, baton));
// Increment queued task counter
g_atomic_int_inc(&counter_queue);
g_atomic_int_inc(&counterQueue);
NanReturnUndefined();
}

View File

@ -11,20 +11,21 @@
#include "resize.h"
using namespace v8;
using namespace sharp;
typedef enum {
enum class Canvas {
CROP,
MAX,
EMBED
} Canvas;
};
typedef enum {
ANGLE_0,
ANGLE_90,
ANGLE_180,
ANGLE_270,
ANGLE_LAST
} Angle;
enum class Angle {
D0,
D90,
D180,
D270,
DLAST
};
struct ResizeBaton {
std::string fileIn;
@ -74,7 +75,7 @@ struct ResizeBaton {
bufferOutLength(0),
topOffsetPre(-1),
topOffsetPost(-1),
canvas(CROP),
canvas(Canvas::CROP),
gravity(0),
flatten(false),
blurRadius(0),
@ -83,10 +84,14 @@ struct ResizeBaton {
sharpenJagged(2.0),
gamma(0.0),
greyscale(false),
angle(0),
flip(false),
flop(false),
progressive(false),
withoutEnlargement(false),
quality(80),
compressionLevel(6),
withoutAdaptiveFiltering(false),
withMetadata(false) {
background[0] = 0.0;
background[1] = 0.0;
@ -106,43 +111,45 @@ class ResizeWorker : public NanAsyncWorker {
*/
void Execute() {
// Decrement queued task counter
g_atomic_int_dec_and_test(&counter_queue);
g_atomic_int_dec_and_test(&counterQueue);
// Increment processing task counter
g_atomic_int_inc(&counter_process);
g_atomic_int_inc(&counterProcess);
// Hang image references from this hook object
VipsObject *hook = reinterpret_cast<VipsObject*>(vips_image_new());
// Input
ImageType inputImageType = UNKNOWN;
VipsImage *image = vips_image_new();
vips_object_local(hook, image);
ImageType inputImageType = ImageType::UNKNOWN;
VipsImage *image;
if (baton->bufferInLength > 1) {
// From buffer
inputImageType = sharp_init_image_from_buffer(&image, baton->bufferIn, baton->bufferInLength, baton->accessMethod);
if (inputImageType == UNKNOWN) {
inputImageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
if (inputImageType != ImageType::UNKNOWN) {
image = InitImage(inputImageType, baton->bufferIn, baton->bufferInLength, baton->accessMethod);
} else {
(baton->err).append("Input buffer contains unsupported image format");
}
} else {
// From file
inputImageType = sharp_init_image_from_file(&image, baton->fileIn.c_str(), baton->accessMethod);
if (inputImageType == UNKNOWN) {
inputImageType = DetermineImageType(baton->fileIn.c_str());
if (inputImageType != ImageType::UNKNOWN) {
image = InitImage(inputImageType, baton->fileIn.c_str(), baton->accessMethod);
} else {
(baton->err).append("File is of an unsupported image format");
}
}
if (inputImageType == UNKNOWN) {
if (inputImageType == ImageType::UNKNOWN) {
return Error(baton, hook);
}
vips_object_local(hook, image);
// Pre extraction
if (baton->topOffsetPre != -1) {
VipsImage *extractedPre = vips_image_new();
vips_object_local(hook, extractedPre);
VipsImage *extractedPre;
if (vips_extract_area(image, &extractedPre, baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
vips_object_local(hook, extractedPre);
image = extractedPre;
}
@ -154,7 +161,7 @@ class ResizeWorker : public NanAsyncWorker {
Angle rotation;
bool flip;
std::tie(rotation, flip) = CalculateRotationAndFlip(baton->angle, image);
if (rotation == ANGLE_90 || rotation == ANGLE_270) {
if (rotation == Angle::D90 || rotation == Angle::D270) {
// Swap input output width and height when rotating by 90 or 270 degrees
int swap = inputWidth;
inputWidth = inputHeight;
@ -166,7 +173,7 @@ class ResizeWorker : public NanAsyncWorker {
}
// Get window size of interpolator, used for determining shrink vs affine
int interpolatorWindowSize = sharp_interpolator_window_size(baton->interpolator.c_str());
int interpolatorWindowSize = InterpolatorWindowSize(baton->interpolator.c_str());
// Scaling calculations
double factor;
@ -174,9 +181,9 @@ class ResizeWorker : public NanAsyncWorker {
// Fixed width and height
double xfactor = static_cast<double>(inputWidth) / static_cast<double>(baton->width);
double yfactor = static_cast<double>(inputHeight) / static_cast<double>(baton->height);
factor = (baton->canvas == CROP) ? std::min(xfactor, yfactor) : std::max(xfactor, yfactor);
factor = (baton->canvas == Canvas::CROP) ? std::min(xfactor, yfactor) : std::max(xfactor, yfactor);
// if max is set, we need to compute the real size of the thumb image
if (baton->canvas == MAX) {
if (baton->canvas == Canvas::MAX) {
if (xfactor > yfactor) {
baton->height = round(static_cast<double>(inputHeight) / xfactor);
} else {
@ -226,7 +233,7 @@ class ResizeWorker : public NanAsyncWorker {
// Try to use libjpeg shrink-on-load, but not when applying gamma correction or pre-resize extract
int shrink_on_load = 1;
if (inputImageType == JPEG && shrink >= 2 && baton->gamma == 0 && baton->topOffsetPre == -1) {
if (inputImageType == ImageType::JPEG && shrink >= 2 && baton->gamma == 0 && baton->topOffsetPre == -1) {
if (shrink >= 8) {
factor = factor / 8;
shrink_on_load = 8;
@ -248,52 +255,51 @@ class ResizeWorker : public NanAsyncWorker {
}
residual = static_cast<double>(shrink) / factor;
// Reload input using shrink-on-load
g_object_unref(image);
VipsImage *shrunkOnLoad;
if (baton->bufferInLength > 1) {
if (vips_jpegload_buffer(baton->bufferIn, baton->bufferInLength, &image, "shrink", shrink_on_load, NULL)) {
if (vips_jpegload_buffer(baton->bufferIn, baton->bufferInLength, &shrunkOnLoad, "shrink", shrink_on_load, NULL)) {
return Error(baton, hook);
}
} else {
if (vips_jpegload((baton->fileIn).c_str(), &image, "shrink", shrink_on_load, NULL)) {
if (vips_jpegload((baton->fileIn).c_str(), &shrunkOnLoad, "shrink", shrink_on_load, NULL)) {
return Error(baton, hook);
}
}
vips_object_local(hook, shrunkOnLoad);
image = shrunkOnLoad;
}
// Handle colour profile, if any, for non sRGB images
if (image->Type != VIPS_INTERPRETATION_sRGB) {
// Get the input colour profile
if (vips_image_get_typeof(image, VIPS_META_ICC_NAME)) {
VipsImage *profile;
// Use embedded profile
VipsImage *profile = vips_image_new();
vips_object_local(hook, profile);
if (vips_icc_import(image, &profile, "pcs", VIPS_PCS_XYZ, "embedded", TRUE, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
vips_object_local(hook, profile);
image = profile;
} else if (image->Type == VIPS_INTERPRETATION_CMYK) {
VipsImage *profile;
// CMYK with no embedded profile
VipsImage *profile = vips_image_new();
vips_object_local(hook, profile);
if (vips_icc_import(image, &profile, "pcs", VIPS_PCS_XYZ, "input_profile", (baton->iccProfileCmyk).c_str(), NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
vips_object_local(hook, profile);
image = profile;
}
// Attempt to convert to sRGB colour space
VipsImage *colourspaced = vips_image_new();
vips_object_local(hook, colourspaced);
VipsImage *colourspaced;
if (vips_colourspace(image, &colourspaced, VIPS_INTERPRETATION_sRGB, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
vips_object_local(hook, colourspaced);
image = colourspaced;
}
// Flatten image to remove alpha channel
if (baton->flatten && sharp_image_has_alpha(image)) {
if (baton->flatten && HasAlpha(image)) {
// Background colour
VipsArrayDouble *background = vips_array_double_newv(
3, // Ignore alpha channel as we're about to remove it
@ -301,52 +307,48 @@ class ResizeWorker : public NanAsyncWorker {
baton->background[1],
baton->background[2]
);
VipsImage *flattened = vips_image_new();
vips_object_local(hook, flattened);
VipsImage *flattened;
if (vips_flatten(image, &flattened, "background", background, NULL)) {
vips_area_unref(reinterpret_cast<VipsArea*>(background));
return Error(baton, hook);
};
vips_area_unref(reinterpret_cast<VipsArea*>(background));
g_object_unref(image);
vips_object_local(hook, flattened);
image = flattened;
}
// Gamma encoding (darken)
if (baton->gamma >= 1 && baton->gamma <= 3) {
VipsImage *gammaEncoded = vips_image_new();
vips_object_local(hook, gammaEncoded);
VipsImage *gammaEncoded;
if (vips_gamma(image, &gammaEncoded, "exponent", 1.0 / baton->gamma, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
vips_object_local(hook, gammaEncoded);
image = gammaEncoded;
}
// Convert to greyscale (linear, therefore after gamma encoding, if any)
if (baton->greyscale) {
VipsImage *greyscale = vips_image_new();
vips_object_local(hook, greyscale);
VipsImage *greyscale;
if (vips_colourspace(image, &greyscale, VIPS_INTERPRETATION_B_W, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
vips_object_local(hook, greyscale);
image = greyscale;
}
if (shrink > 1) {
VipsImage *shrunk = vips_image_new();
vips_object_local(hook, shrunk);
VipsImage *shrunk;
// Use vips_shrink with the integral reduction
if (vips_shrink(image, &shrunk, shrink, shrink, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
vips_object_local(hook, shrunk);
image = shrunk;
// Recalculate residual float based on dimensions of required vs shrunk images
double shrunkWidth = shrunk->Xsize;
double shrunkHeight = shrunk->Ysize;
if (rotation == ANGLE_90 || rotation == ANGLE_270) {
if (rotation == Angle::D90 || rotation == Angle::D270) {
// Swap input output width and height when rotating by 90 or 270 degrees
int swap = shrunkWidth;
shrunkWidth = shrunkHeight;
@ -354,7 +356,7 @@ class ResizeWorker : public NanAsyncWorker {
}
double residualx = static_cast<double>(baton->width) / static_cast<double>(shrunkWidth);
double residualy = static_cast<double>(baton->height) / static_cast<double>(shrunkHeight);
if (baton->canvas == EMBED) {
if (baton->canvas == Canvas::EMBED) {
residual = std::min(residualx, residualy);
} else {
residual = std::max(residualx, residualy);
@ -365,98 +367,88 @@ class ResizeWorker : public NanAsyncWorker {
if (residual != 0) {
// Apply variable blur radius of floor(residual) before large affine reductions
if (residual >= 1) {
VipsImage *blurred = vips_image_new();
vips_object_local(hook, blurred);
VipsImage *blurred;
if (vips_gaussblur(image, &blurred, floor(residual), NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
vips_object_local(hook, blurred);
image = blurred;
}
// Create interpolator - "bilinear" (default), "bicubic" or "nohalo"
VipsInterpolate *interpolator = vips_interpolate_new(baton->interpolator.c_str());
vips_object_local(hook, interpolator);
// Perform affine transformation
VipsImage *affined = vips_image_new();
vips_object_local(hook, affined);
VipsImage *affined;
if (vips_affine(image, &affined, residual, 0, 0, residual, "interpolate", interpolator, NULL)) {
g_object_unref(interpolator);
return Error(baton, hook);
}
g_object_unref(interpolator);
g_object_unref(image);
vips_object_local(hook, affined);
image = affined;
}
// Rotate
if (rotation != ANGLE_0) {
VipsImage *rotated = vips_image_new();
vips_object_local(hook, rotated);
if (rotation != Angle::D0) {
VipsImage *rotated;
if (vips_rot(image, &rotated, static_cast<VipsAngle>(rotation), NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
vips_object_local(hook, rotated);
image = rotated;
}
// Flip (mirror about Y axis)
if (baton->flip) {
VipsImage *flipped = vips_image_new();
vips_object_local(hook, flipped);
VipsImage *flipped;
if (vips_flip(image, &flipped, VIPS_DIRECTION_VERTICAL, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
vips_object_local(hook, flipped);
image = flipped;
}
// Flop (mirror about X axis)
if (baton->flop) {
VipsImage *flopped = vips_image_new();
vips_object_local(hook, flopped);
VipsImage *flopped;
if (vips_flip(image, &flopped, VIPS_DIRECTION_HORIZONTAL, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
vips_object_local(hook, flopped);
image = flopped;
}
// Crop/embed
if (image->Xsize != baton->width || image->Ysize != baton->height) {
if (baton->canvas == EMBED) {
if (baton->canvas == Canvas::EMBED) {
// Match background colour space, namely sRGB
if (image->Type != VIPS_INTERPRETATION_sRGB) {
// Convert to sRGB colour space
VipsImage *colourspaced = vips_image_new();
vips_object_local(hook, colourspaced);
VipsImage *colourspaced;
if (vips_colourspace(image, &colourspaced, VIPS_INTERPRETATION_sRGB, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
vips_object_local(hook, colourspaced);
image = colourspaced;
}
// Add non-transparent alpha channel, if required
if (baton->background[3] < 255.0 && !sharp_image_has_alpha(image)) {
if (baton->background[3] < 255.0 && !HasAlpha(image)) {
// Create single-channel transparency
VipsImage *black = vips_image_new();
vips_object_local(hook, black);
VipsImage *black;
if (vips_black(&black, image->Xsize, image->Ysize, "bands", 1, NULL)) {
return Error(baton, hook);
}
vips_object_local(hook, black);
// Invert to become non-transparent
VipsImage *alpha = vips_image_new();
vips_object_local(hook, alpha);
VipsImage *alpha;
if (vips_invert(black, &alpha, NULL)) {
return Error(baton, hook);
}
g_object_unref(black);
vips_object_local(hook, alpha);
// Append alpha channel to existing image
VipsImage *joined = vips_image_new();
vips_object_local(hook, joined);
VipsImage *joined;
if (vips_bandjoin2(image, alpha, &joined, NULL)) {
return Error(baton, hook);
}
g_object_unref(alpha);
g_object_unref(image);
vips_object_local(hook, joined);
image = joined;
}
// Create background
@ -473,8 +465,7 @@ class ResizeWorker : public NanAsyncWorker {
// Embed
int left = (baton->width - image->Xsize) / 2;
int top = (baton->height - image->Ysize) / 2;
VipsImage *embedded = vips_image_new();
vips_object_local(hook, embedded);
VipsImage *embedded;
if (vips_embed(image, &embedded, left, top, baton->width, baton->height,
"extend", VIPS_EXTEND_BACKGROUND, "background", background, NULL
)) {
@ -482,7 +473,7 @@ class ResizeWorker : public NanAsyncWorker {
return Error(baton, hook);
}
vips_area_unref(reinterpret_cast<VipsArea*>(background));
g_object_unref(image);
vips_object_local(hook, embedded);
image = embedded;
} else {
// Crop/max
@ -491,31 +482,28 @@ class ResizeWorker : public NanAsyncWorker {
std::tie(left, top) = CalculateCrop(image->Xsize, image->Ysize, baton->width, baton->height, baton->gravity);
int width = std::min(image->Xsize, baton->width);
int height = std::min(image->Ysize, baton->height);
VipsImage *extracted = vips_image_new();
vips_object_local(hook, extracted);
VipsImage *extracted;
if (vips_extract_area(image, &extracted, left, top, width, height, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
vips_object_local(hook, extracted);
image = extracted;
}
}
// Post extraction
if (baton->topOffsetPost != -1) {
VipsImage *extractedPost = vips_image_new();
vips_object_local(hook, extractedPost);
VipsImage *extractedPost;
if (vips_extract_area(image, &extractedPost, baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
vips_object_local(hook, extractedPost);
image = extractedPost;
}
// Blur
if (baton->blurRadius != 0) {
VipsImage *blurred = vips_image_new();
vips_object_local(hook, blurred);
VipsImage *blurred;
if (baton->blurRadius == -1) {
// Fast, mild blur
VipsImage *blur = vips_image_new_matrixv(3, 3,
@ -533,14 +521,13 @@ class ResizeWorker : public NanAsyncWorker {
return Error(baton, hook);
}
}
g_object_unref(image);
vips_object_local(hook, blurred);
image = blurred;
}
// Sharpen
if (baton->sharpenRadius != 0) {
VipsImage *sharpened = vips_image_new();
vips_object_local(hook, sharpened);
VipsImage *sharpened;
if (baton->sharpenRadius == -1) {
// Fast, mild sharpen
VipsImage *sharpen = vips_image_new_matrixv(3, 3,
@ -558,54 +545,51 @@ class ResizeWorker : public NanAsyncWorker {
return Error(baton, hook);
}
}
g_object_unref(image);
vips_object_local(hook, sharpened);
image = sharpened;
}
// Gamma decoding (brighten)
if (baton->gamma >= 1 && baton->gamma <= 3) {
VipsImage *gammaDecoded = vips_image_new();
vips_object_local(hook, gammaDecoded);
VipsImage *gammaDecoded;
if (vips_gamma(image, &gammaDecoded, "exponent", baton->gamma, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
vips_object_local(hook, gammaDecoded);
image = gammaDecoded;
}
// Convert to sRGB colour space, if not already
if (image->Type != VIPS_INTERPRETATION_sRGB) {
VipsImage *colourspaced = vips_image_new();
vips_object_local(hook, colourspaced);
VipsImage *colourspaced;
if (vips_colourspace(image, &colourspaced, VIPS_INTERPRETATION_sRGB, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
vips_object_local(hook, colourspaced);
image = colourspaced;
}
#if !(VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 40 && VIPS_MINOR_VERSION >= 5)
// Generate image tile cache when interlace output is required - no longer required as of libvips 7.40.5+
if (baton->progressive) {
VipsImage *cached = vips_image_new();
vips_object_local(hook, cached);
VipsImage *cached;
if (vips_tilecache(image, &cached, "threaded", TRUE, "persistent", TRUE, "max_tiles", -1, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
vips_object_local(hook, cached);
image = cached;
}
#endif
// Output
if (baton->output == "__jpeg" || (baton->output == "__input" && inputImageType == JPEG)) {
if (baton->output == "__jpeg" || (baton->output == "__input" && inputImageType == ImageType::JPEG)) {
// Write JPEG to buffer
if (vips_jpegsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata,
"Q", baton->quality, "optimize_coding", TRUE, "interlace", baton->progressive, NULL)) {
return Error(baton, hook);
}
baton->outputFormat = "jpeg";
} else if (baton->output == "__png" || (baton->output == "__input" && inputImageType == PNG)) {
} else if (baton->output == "__png" || (baton->output == "__input" && inputImageType == ImageType::PNG)) {
#if (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 41)
// Select PNG row filter
int filter = baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL;
@ -622,7 +606,7 @@ class ResizeWorker : public NanAsyncWorker {
}
#endif
baton->outputFormat = "png";
} else if (baton->output == "__webp" || (baton->output == "__input" && inputImageType == WEBP)) {
} else if (baton->output == "__webp" || (baton->output == "__input" && inputImageType == ImageType::WEBP)) {
// Write WEBP to buffer
if (vips_webpsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata,
"Q", baton->quality, NULL)) {
@ -630,19 +614,19 @@ class ResizeWorker : public NanAsyncWorker {
}
baton->outputFormat = "webp";
} else {
bool output_jpeg = is_jpeg(baton->output);
bool output_png = is_png(baton->output);
bool output_webp = is_webp(baton->output);
bool output_tiff = is_tiff(baton->output);
bool match_input = !(output_jpeg || output_png || output_webp || output_tiff);
if (output_jpeg || (match_input && inputImageType == JPEG)) {
bool outputJpeg = IsJpeg(baton->output);
bool outputPng = IsPng(baton->output);
bool outputWebp = IsWebp(baton->output);
bool outputTiff = IsTiff(baton->output);
bool matchInput = !(outputJpeg || outputPng || outputWebp || outputTiff);
if (outputJpeg || (matchInput && inputImageType == ImageType::JPEG)) {
// Write JPEG to file
if (vips_jpegsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
"Q", baton->quality, "optimize_coding", TRUE, "interlace", baton->progressive, NULL)) {
return Error(baton, hook);
}
baton->outputFormat = "jpeg";
} else if (output_png || (match_input && inputImageType == PNG)) {
} else if (outputPng || (matchInput && inputImageType == ImageType::PNG)) {
#if (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 41)
// Select PNG row filter
int filter = baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL;
@ -659,14 +643,14 @@ class ResizeWorker : public NanAsyncWorker {
}
#endif
baton->outputFormat = "png";
} else if (output_webp || (match_input && inputImageType == WEBP)) {
} else if (outputWebp || (matchInput && inputImageType == ImageType::WEBP)) {
// Write WEBP to file
if (vips_webpsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
"Q", baton->quality, NULL)) {
return Error(baton, hook);
}
baton->outputFormat = "webp";
} else if (output_tiff || (match_input && inputImageType == TIFF)) {
} else if (outputTiff || (matchInput && inputImageType == ImageType::TIFF)) {
// Write TIFF to file
if (vips_tiffsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
"compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG, "Q", baton->quality, NULL)) {
@ -675,12 +659,10 @@ class ResizeWorker : public NanAsyncWorker {
baton->outputFormat = "tiff";
} else {
(baton->err).append("Unsupported output " + baton->output);
g_object_unref(image);
return Error(baton, hook);
}
}
// Clean up any dangling image references
g_object_unref(image);
g_object_unref(hook);
// Clean up libvips' per-request data and threads
vips_error_clear();
@ -724,7 +706,7 @@ class ResizeWorker : public NanAsyncWorker {
delete baton;
// Decrement processing task counter
g_atomic_int_dec_and_test(&counter_process);
g_atomic_int_dec_and_test(&counterProcess);
// Return to JavaScript
callback->Call(3, argv);
@ -742,40 +724,25 @@ class ResizeWorker : public NanAsyncWorker {
*/
std::tuple<Angle, bool>
CalculateRotationAndFlip(int const angle, VipsImage const *input) {
Angle rotate = ANGLE_0;
Angle rotate = Angle::D0;
bool flip = FALSE;
if (angle == -1) {
const char *exif;
if (
vips_image_get_typeof(input, "exif-ifd0-Orientation") != 0 &&
!vips_image_get_string(input, "exif-ifd0-Orientation", &exif)
) {
if (exif[0] == 0x36) { // "6"
rotate = ANGLE_90;
} else if (exif[0] == 0x33) { // "3"
rotate = ANGLE_180;
} else if (exif[0] == 0x38) { // "8"
rotate = ANGLE_270;
} else if (exif[0] == 0x32) { // "2" (flip 1)
flip = TRUE;
} else if (exif[0] == 0x37) { // "7" (flip 6)
rotate = ANGLE_90;
flip = TRUE;
} else if (exif[0] == 0x34) { // "4" (flip 3)
rotate = ANGLE_180;
flip = TRUE;
} else if (exif[0] == 0x35) { // "5" (flip 8)
rotate = ANGLE_270;
flip = TRUE;
}
switch(ExifOrientation(input)) {
case 6: rotate = Angle::D90; break;
case 3: rotate = Angle::D180; break;
case 8: rotate = Angle::D270; break;
case 2: flip = TRUE; break; // flip 1
case 7: flip = TRUE; rotate = Angle::D90; break; // flip 6
case 4: flip = TRUE; rotate = Angle::D180; break; // flip 3
case 5: flip = TRUE; rotate = Angle::D270; break; // flip 8
}
} else {
if (angle == 90) {
rotate = ANGLE_90;
rotate = Angle::D90;
} else if (angle == 180) {
rotate = ANGLE_180;
rotate = Angle::D180;
} else if (angle == 270) {
rotate = ANGLE_270;
rotate = Angle::D270;
}
}
return std::make_tuple(rotate, flip);
@ -817,9 +784,12 @@ class ResizeWorker : public NanAsyncWorker {
Clear all thread-local data.
*/
void Error(ResizeBaton *baton, VipsObject *hook) {
// Get libvips' error message
(baton->err).append(vips_error_buffer());
vips_error_clear();
// Clean up any dangling image references
g_object_unref(hook);
// Clean up libvips' per-request data and threads
vips_error_clear();
vips_thread_shutdown();
}
};
@ -860,11 +830,11 @@ NAN_METHOD(resize) {
// Canvas option
Local<String> canvas = options->Get(NanNew<String>("canvas"))->ToString();
if (canvas->Equals(NanNew<String>("c"))) {
baton->canvas = CROP;
baton->canvas = Canvas::CROP;
} else if (canvas->Equals(NanNew<String>("m"))) {
baton->canvas = MAX;
baton->canvas = Canvas::MAX;
} else if (canvas->Equals(NanNew<String>("e"))) {
baton->canvas = EMBED;
baton->canvas = Canvas::EMBED;
}
// Background colour
Local<Array> background = Local<Array>::Cast(options->Get(NanNew<String>("background")));
@ -900,7 +870,7 @@ NAN_METHOD(resize) {
NanAsyncQueueWorker(new ResizeWorker(callback, baton));
// Increment queued task counter
g_atomic_int_inc(&counter_queue);
g_atomic_int_inc(&counterQueue);
NanReturnUndefined();
}

View File

@ -7,6 +7,7 @@
#include "utilities.h"
using namespace v8;
using namespace sharp;
/*
Get and set cache memory and item limits
@ -58,8 +59,8 @@ NAN_METHOD(concurrency) {
NAN_METHOD(counters) {
NanScope();
Local<Object> counters = NanNew<Object>();
counters->Set(NanNew<String>("queue"), NanNew<Number>(counter_queue));
counters->Set(NanNew<String>("process"), NanNew<Number>(counter_process));
counters->Set(NanNew<String>("queue"), NanNew<Number>(counterQueue));
counters->Set(NanNew<String>("process"), NanNew<Number>(counterProcess));
NanReturnValue(counters);
}