Switch default interpolator to bicubic #289

Only use gaussian blur for non-linear interpolators

Improves performance of bilinear by ~15%

Add liborc to the packaged build to improve bicubic perf

Add examples of the various interpolation methods

Add bilinear vs bicubic to perf tests
This commit is contained in:
Lovell Fuller 2015-11-15 22:04:31 +00:00
parent 2678d761ba
commit 84fd1caa46
21 changed files with 935 additions and 442 deletions

View File

@ -201,14 +201,18 @@ Ignoring the aspect ratio of the input, stretch the image to the exact `width` a
Use the given interpolator for image resizing, where `interpolator` is an attribute of the `sharp.interpolator` Object e.g. `sharp.interpolator.bicubic`. Use the given interpolator for image resizing, where `interpolator` is an attribute of the `sharp.interpolator` Object e.g. `sharp.interpolator.bicubic`.
The default interpolator is `bicubic`, providing a general-purpose interpolator that is both fast and of good quality.
Possible interpolators, in order of performance, are: Possible interpolators, in order of performance, are:
* `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation), suitable for image enlargement only. * `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation), suitable for image enlargement only.
* `bilinear`: Use [bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation), the default and fastest image reduction interpolation. * `bilinear`: Use [bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation), faster than bicubic but with less smooth results.
* `bicubic`: Use [bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation), which typically reduces performance by 5%. * `vertexSplitQuadraticBasisSpline`: Use the smoother [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48) to prevent "staircasing" when enlarging.
* `vertexSplitQuadraticBasisSpline`: Use [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48), which prevents "staircasing" and typically reduces performance by 5%. * `bicubic`: Use [bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default).
* `locallyBoundedBicubic`: Use [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100), which prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" and typically reduces performance by a factor of 2. * `locallyBoundedBicubic`: Use [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100), which prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2.
* `nohalo`: Use [Nohalo interpolation](http://eprints.soton.ac.uk/268086/), which prevents acutance and typically reduces performance by a factor of 3. * `nohalo`: Use [Nohalo interpolation](http://eprints.soton.ac.uk/268086/), which prevents acutance but typically reduces performance by a factor of 3.
[Compare the output of these interpolators](https://github.com/lovell/sharp/tree/master/test/interpolators)
```javascript ```javascript
sharp(inputBuffer) sharp(inputBuffer)

2
index.js Executable file → Normal file
View File

@ -54,7 +54,7 @@ var Sharp = function(input) {
flip: false, flip: false,
flop: false, flop: false,
withoutEnlargement: false, withoutEnlargement: false,
interpolator: 'bilinear', interpolator: 'bicubic',
// operations // operations
background: [0, 0, 0, 255], background: [0, 0, 0, 255],
flatten: false, flatten: false,

View File

@ -51,7 +51,7 @@ WORKDIR ${DEPS}/jpeg
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-jpeg8 --without-turbojpeg && make install-strip RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-jpeg8 --without-turbojpeg && make install-strip
RUN mkdir ${DEPS}/png RUN mkdir ${DEPS}/png
RUN curl -Ls http://kent.dl.sourceforge.net/project/libpng/libpng16/1.6.18/libpng-1.6.18.tar.xz | tar xJC ${DEPS}/png --strip-components=1 RUN curl -Ls http://kent.dl.sourceforge.net/project/libpng/libpng16/1.6.19/libpng-1.6.19.tar.xz | tar xJC ${DEPS}/png --strip-components=1
WORKDIR ${DEPS}/png WORKDIR ${DEPS}/png
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
@ -76,6 +76,11 @@ RUN curl -Ls http://www.imagemagick.org/download/releases/ImageMagick-6.9.2-5.ta
WORKDIR ${DEPS}/magick WORKDIR ${DEPS}/magick
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --without-magick-plus-plus && make install-strip RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --without-magick-plus-plus && make install-strip
RUN mkdir ${DEPS}/orc
RUN curl -Ls http://gstreamer.freedesktop.org/data/src/orc/orc-0.4.24.tar.xz | tar xJC ${DEPS}/orc --strip-components=1
WORKDIR ${DEPS}/orc
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
RUN mkdir ${DEPS}/vips RUN mkdir ${DEPS}/vips
RUN curl -Ls http://www.vips.ecs.soton.ac.uk/supported/8.1/vips-8.1.1.tar.gz | tar xzC ${DEPS}/vips --strip-components=1 RUN curl -Ls http://www.vips.ecs.soton.ac.uk/supported/8.1/vips-8.1.1.tar.gz | tar xzC ${DEPS}/vips --strip-components=1
WORKDIR ${DEPS}/vips WORKDIR ${DEPS}/vips

View File

@ -78,7 +78,7 @@ namespace sharp {
Initialise and return a VipsImage from a buffer. Supports JPEG, PNG, WebP and TIFF. Initialise and return a VipsImage from a buffer. Supports JPEG, PNG, WebP and TIFF.
*/ */
VipsImage* InitImage(void *buffer, size_t const length, VipsAccess const access) { VipsImage* InitImage(void *buffer, size_t const length, VipsAccess const access) {
return vips_image_new_from_buffer(buffer, length, NULL, "access", access, NULL); return vips_image_new_from_buffer(buffer, length, nullptr, "access", access, nullptr);
} }
/* /*
@ -87,7 +87,7 @@ namespace sharp {
ImageType DetermineImageType(char const *file) { ImageType DetermineImageType(char const *file) {
ImageType imageType = ImageType::UNKNOWN; ImageType imageType = ImageType::UNKNOWN;
char const *load = vips_foreign_find_load(file); char const *load = vips_foreign_find_load(file);
if (load != NULL) { if (load != nullptr) {
std::string loader = load; std::string loader = load;
if (EndsWith(loader, "JpegFile")) { if (EndsWith(loader, "JpegFile")) {
imageType = ImageType::JPEG; imageType = ImageType::JPEG;
@ -110,7 +110,7 @@ namespace sharp {
Initialise and return a VipsImage from a file. Initialise and return a VipsImage from a file.
*/ */
VipsImage* InitImage(char const *file, VipsAccess const access) { VipsImage* InitImage(char const *file, VipsAccess const access) {
return vips_image_new_from_file(file, "access", access, NULL); return vips_image_new_from_file(file, "access", access, nullptr);
} }
/* /*
@ -169,7 +169,7 @@ namespace sharp {
*/ */
int InterpolatorWindowSize(char const *name) { int InterpolatorWindowSize(char const *name) {
VipsInterpolate *interpolator = vips_interpolate_new(name); VipsInterpolate *interpolator = vips_interpolate_new(name);
if (interpolator == NULL) { if (interpolator == nullptr) {
return -1; return -1;
} }
int window_size = vips_interpolate_get_window_size(interpolator); int window_size = vips_interpolate_get_window_size(interpolator);
@ -181,7 +181,7 @@ namespace sharp {
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
*/ */
void FreeCallback(char* data, void* hint) { void FreeCallback(char* data, void* hint) {
if (data != NULL) { if (data != nullptr) {
g_free(data); g_free(data);
} }
} }

View File

@ -81,13 +81,13 @@ class MetadataWorker : public AsyncWorker {
g_atomic_int_dec_and_test(&counterQueue); g_atomic_int_dec_and_test(&counterQueue);
ImageType imageType = ImageType::UNKNOWN; ImageType imageType = ImageType::UNKNOWN;
VipsImage *image = NULL; VipsImage *image = nullptr;
if (baton->bufferInLength > 0) { if (baton->bufferInLength > 0) {
// From buffer // From buffer
imageType = DetermineImageType(baton->bufferIn, baton->bufferInLength); imageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
if (imageType != ImageType::UNKNOWN) { if (imageType != ImageType::UNKNOWN) {
image = InitImage(baton->bufferIn, baton->bufferInLength, VIPS_ACCESS_RANDOM); image = InitImage(baton->bufferIn, baton->bufferInLength, VIPS_ACCESS_RANDOM);
if (image == NULL) { if (image == nullptr) {
(baton->err).append("Input buffer has corrupt header"); (baton->err).append("Input buffer has corrupt header");
imageType = ImageType::UNKNOWN; imageType = ImageType::UNKNOWN;
} }
@ -96,10 +96,10 @@ class MetadataWorker : public AsyncWorker {
} }
} else { } else {
// From file // From file
imageType = DetermineImageType(baton->fileIn.c_str()); imageType = DetermineImageType(baton->fileIn.data());
if (imageType != ImageType::UNKNOWN) { if (imageType != ImageType::UNKNOWN) {
image = InitImage(baton->fileIn.c_str(), VIPS_ACCESS_RANDOM); image = InitImage(baton->fileIn.data(), VIPS_ACCESS_RANDOM);
if (image == NULL) { if (image == nullptr) {
(baton->err).append("Input file has corrupt header"); (baton->err).append("Input file has corrupt header");
imageType = ImageType::UNKNOWN; imageType = ImageType::UNKNOWN;
} }
@ -107,7 +107,7 @@ class MetadataWorker : public AsyncWorker {
(baton->err).append("Input file is of an unsupported image format"); (baton->err).append("Input file is of an unsupported image format");
} }
} }
if (image != NULL && imageType != ImageType::UNKNOWN) { if (image != nullptr && imageType != ImageType::UNKNOWN) {
// Image type // Image type
switch (imageType) { switch (imageType) {
case ImageType::JPEG: baton->format = "jpeg"; break; case ImageType::JPEG: baton->format = "jpeg"; break;
@ -161,7 +161,7 @@ class MetadataWorker : public AsyncWorker {
Local<Value> argv[2] = { Null(), Null() }; Local<Value> argv[2] = { Null(), Null() };
if (!baton->err.empty()) { if (!baton->err.empty()) {
// Error // Error
argv[0] = Error(baton->err.c_str()); argv[0] = Error(baton->err.data());
} else { } else {
// Metadata Object // Metadata Object
Local<Object> info = New<Object>(); Local<Object> info = New<Object>();

58
src/operations.cc Executable file → Normal file
View File

@ -14,11 +14,11 @@ namespace sharp {
// Split src into non-alpha and alpha // Split src into non-alpha and alpha
VipsImage *srcWithoutAlpha; VipsImage *srcWithoutAlpha;
if (vips_extract_band(src, &srcWithoutAlpha, 0, "n", src->Bands - 1, NULL)) if (vips_extract_band(src, &srcWithoutAlpha, 0, "n", src->Bands - 1, nullptr))
return -1; return -1;
vips_object_local(context, srcWithoutAlpha); vips_object_local(context, srcWithoutAlpha);
VipsImage *srcAlpha; VipsImage *srcAlpha;
if (vips_extract_band(src, &srcAlpha, src->Bands - 1, "n", 1, NULL)) if (vips_extract_band(src, &srcAlpha, src->Bands - 1, "n", 1, nullptr))
return -1; return -1;
vips_object_local(context, srcAlpha); vips_object_local(context, srcAlpha);
@ -27,12 +27,12 @@ namespace sharp {
VipsImage *dstAlpha; VipsImage *dstAlpha;
if (HasAlpha(dst)) { if (HasAlpha(dst)) {
// Non-alpha: extract all-but-last channel // Non-alpha: extract all-but-last channel
if (vips_extract_band(dst, &dstWithoutAlpha, 0, "n", dst->Bands - 1, NULL)) { if (vips_extract_band(dst, &dstWithoutAlpha, 0, "n", dst->Bands - 1, nullptr)) {
return -1; return -1;
} }
vips_object_local(context, dstWithoutAlpha); vips_object_local(context, dstWithoutAlpha);
// Alpha: Extract last channel // Alpha: Extract last channel
if (vips_extract_band(dst, &dstAlpha, dst->Bands - 1, "n", 1, NULL)) { if (vips_extract_band(dst, &dstAlpha, dst->Bands - 1, "n", 1, nullptr)) {
return -1; return -1;
} }
vips_object_local(context, dstAlpha); vips_object_local(context, dstAlpha);
@ -41,11 +41,11 @@ namespace sharp {
dstWithoutAlpha = dst; dstWithoutAlpha = dst;
// Alpha: Use blank, opaque (0xFF) image // Alpha: Use blank, opaque (0xFF) image
VipsImage *black; VipsImage *black;
if (vips_black(&black, dst->Xsize, dst->Ysize, NULL)) { if (vips_black(&black, dst->Xsize, dst->Ysize, nullptr)) {
return -1; return -1;
} }
vips_object_local(context, black); vips_object_local(context, black);
if (vips_invert(black, &dstAlpha, NULL)) { if (vips_invert(black, &dstAlpha, nullptr)) {
return -1; return -1;
} }
vips_object_local(context, dstAlpha); vips_object_local(context, dstAlpha);
@ -53,12 +53,12 @@ namespace sharp {
// Compute normalized input alpha channels: // Compute normalized input alpha channels:
VipsImage *srcAlphaNormalized; VipsImage *srcAlphaNormalized;
if (vips_linear1(srcAlpha, &srcAlphaNormalized, 1.0 / 255.0, 0.0, NULL)) if (vips_linear1(srcAlpha, &srcAlphaNormalized, 1.0 / 255.0, 0.0, nullptr))
return -1; return -1;
vips_object_local(context, srcAlphaNormalized); vips_object_local(context, srcAlphaNormalized);
VipsImage *dstAlphaNormalized; VipsImage *dstAlphaNormalized;
if (vips_linear1(dstAlpha, &dstAlphaNormalized, 1.0 / 255.0, 0.0, NULL)) if (vips_linear1(dstAlpha, &dstAlphaNormalized, 1.0 / 255.0, 0.0, nullptr))
return -1; return -1;
vips_object_local(context, dstAlphaNormalized); vips_object_local(context, dstAlphaNormalized);
@ -75,17 +75,17 @@ namespace sharp {
// ^^^^^^^^^^^^^^^^^^^ // ^^^^^^^^^^^^^^^^^^^
// t1 // t1
VipsImage *t0; VipsImage *t0;
if (vips_linear1(srcAlphaNormalized, &t0, -1.0, 1.0, NULL)) if (vips_linear1(srcAlphaNormalized, &t0, -1.0, 1.0, nullptr))
return -1; return -1;
vips_object_local(context, t0); vips_object_local(context, t0);
VipsImage *t1; VipsImage *t1;
if (vips_multiply(dstAlphaNormalized, t0, &t1, NULL)) if (vips_multiply(dstAlphaNormalized, t0, &t1, nullptr))
return -1; return -1;
vips_object_local(context, t1); vips_object_local(context, t1);
VipsImage *outAlphaNormalized; VipsImage *outAlphaNormalized;
if (vips_add(srcAlphaNormalized, t1, &outAlphaNormalized, NULL)) if (vips_add(srcAlphaNormalized, t1, &outAlphaNormalized, nullptr))
return -1; return -1;
vips_object_local(context, outAlphaNormalized); vips_object_local(context, outAlphaNormalized);
@ -102,23 +102,23 @@ namespace sharp {
// externally. // externally.
// //
VipsImage *t2; VipsImage *t2;
if (vips_multiply(dstWithoutAlpha, t0, &t2, NULL)) if (vips_multiply(dstWithoutAlpha, t0, &t2, nullptr))
return -1; return -1;
vips_object_local(context, t2); vips_object_local(context, t2);
VipsImage *outRGBPremultiplied; VipsImage *outRGBPremultiplied;
if (vips_add(srcWithoutAlpha, t2, &outRGBPremultiplied, NULL)) if (vips_add(srcWithoutAlpha, t2, &outRGBPremultiplied, nullptr))
return -1; return -1;
vips_object_local(context, outRGBPremultiplied); vips_object_local(context, outRGBPremultiplied);
// Denormalize output alpha channel: // Denormalize output alpha channel:
VipsImage *outAlpha; VipsImage *outAlpha;
if (vips_linear1(outAlphaNormalized, &outAlpha, 255.0, 0.0, NULL)) if (vips_linear1(outAlphaNormalized, &outAlpha, 255.0, 0.0, nullptr))
return -1; return -1;
vips_object_local(context, outAlpha); vips_object_local(context, outAlpha);
// Combine RGB and alpha channel into output image: // Combine RGB and alpha channel into output image:
return vips_bandjoin2(outRGBPremultiplied, outAlpha, out, NULL); return vips_bandjoin2(outRGBPremultiplied, outAlpha, out, nullptr);
} }
/* /*
@ -132,25 +132,25 @@ namespace sharp {
} }
// Convert to LAB colourspace // Convert to LAB colourspace
VipsImage *lab; VipsImage *lab;
if (vips_colourspace(image, &lab, VIPS_INTERPRETATION_LAB, NULL)) { if (vips_colourspace(image, &lab, VIPS_INTERPRETATION_LAB, nullptr)) {
return -1; return -1;
} }
vips_object_local(context, lab); vips_object_local(context, lab);
// Extract luminance // Extract luminance
VipsImage *luminance; VipsImage *luminance;
if (vips_extract_band(lab, &luminance, 0, "n", 1, NULL)) { if (vips_extract_band(lab, &luminance, 0, "n", 1, nullptr)) {
return -1; return -1;
} }
vips_object_local(context, luminance); vips_object_local(context, luminance);
// Extract chroma // Extract chroma
VipsImage *chroma; VipsImage *chroma;
if (vips_extract_band(lab, &chroma, 1, "n", 2, NULL)) { if (vips_extract_band(lab, &chroma, 1, "n", 2, nullptr)) {
return -1; return -1;
} }
vips_object_local(context, chroma); vips_object_local(context, chroma);
// Find luminance range // Find luminance range
VipsImage *stats; VipsImage *stats;
if (vips_stats(luminance, &stats, NULL)) { if (vips_stats(luminance, &stats, nullptr)) {
return -1; return -1;
} }
vips_object_local(context, stats); vips_object_local(context, stats);
@ -161,19 +161,19 @@ namespace sharp {
double a = -(min * f); double a = -(min * f);
// Scale luminance // Scale luminance
VipsImage *luminance100; VipsImage *luminance100;
if (vips_linear1(luminance, &luminance100, f, a, NULL)) { if (vips_linear1(luminance, &luminance100, f, a, nullptr)) {
return -1; return -1;
} }
vips_object_local(context, luminance100); vips_object_local(context, luminance100);
// Join scaled luminance to chroma // Join scaled luminance to chroma
VipsImage *normalizedLab; VipsImage *normalizedLab;
if (vips_bandjoin2(luminance100, chroma, &normalizedLab, NULL)) { if (vips_bandjoin2(luminance100, chroma, &normalizedLab, nullptr)) {
return -1; return -1;
} }
vips_object_local(context, normalizedLab); vips_object_local(context, normalizedLab);
// Convert to original colourspace // Convert to original colourspace
VipsImage *normalized; VipsImage *normalized;
if (vips_colourspace(normalizedLab, &normalized, typeBeforeNormalize, NULL)) { if (vips_colourspace(normalizedLab, &normalized, typeBeforeNormalize, nullptr)) {
return -1; return -1;
} }
vips_object_local(context, normalized); vips_object_local(context, normalized);
@ -181,13 +181,13 @@ namespace sharp {
if (HasAlpha(image)) { if (HasAlpha(image)) {
// Extract original alpha channel // Extract original alpha channel
VipsImage *alpha; VipsImage *alpha;
if (vips_extract_band(image, &alpha, image->Bands - 1, "n", 1, NULL)) { if (vips_extract_band(image, &alpha, image->Bands - 1, "n", 1, nullptr)) {
return -1; return -1;
} }
vips_object_local(context, alpha); vips_object_local(context, alpha);
// Join alpha channel to normalised image // Join alpha channel to normalised image
VipsImage *normalizedAlpha; VipsImage *normalizedAlpha;
if (vips_bandjoin2(normalized, alpha, &normalizedAlpha, NULL)) { if (vips_bandjoin2(normalized, alpha, &normalizedAlpha, nullptr)) {
return -1; return -1;
} }
vips_object_local(context, normalizedAlpha); vips_object_local(context, normalizedAlpha);
@ -215,19 +215,19 @@ namespace sharp {
1.0, 1.0, 1.0); 1.0, 1.0, 1.0);
vips_image_set_double(blur, "scale", 9); vips_image_set_double(blur, "scale", 9);
vips_object_local(context, blur); vips_object_local(context, blur);
if (vips_conv(image, &blurred, blur, NULL)) { if (vips_conv(image, &blurred, blur, nullptr)) {
return -1; return -1;
} }
} else { } else {
// Slower, accurate Gaussian blur // Slower, accurate Gaussian blur
// Create Gaussian function for standard deviation // Create Gaussian function for standard deviation
VipsImage *gaussian; VipsImage *gaussian;
if (vips_gaussmat(&gaussian, sigma, 0.2, "separable", TRUE, "integer", TRUE, NULL)) { if (vips_gaussmat(&gaussian, sigma, 0.2, "separable", TRUE, "integer", TRUE, nullptr)) {
return -1; return -1;
} }
vips_object_local(context, gaussian); vips_object_local(context, gaussian);
// Apply Gaussian function // Apply Gaussian function
if (vips_convsep(image, &blurred, gaussian, "precision", VIPS_PRECISION_INTEGER, NULL)) { if (vips_convsep(image, &blurred, gaussian, "precision", VIPS_PRECISION_INTEGER, nullptr)) {
return -1; return -1;
} }
} }
@ -249,12 +249,12 @@ namespace sharp {
-1.0, -1.0, -1.0); -1.0, -1.0, -1.0);
vips_image_set_double(sharpen, "scale", 24); vips_image_set_double(sharpen, "scale", 24);
vips_object_local(context, sharpen); vips_object_local(context, sharpen);
if (vips_conv(image, &sharpened, sharpen, NULL)) { if (vips_conv(image, &sharpened, sharpen, nullptr)) {
return -1; return -1;
} }
} else { } else {
// Slow, accurate sharpen in LAB colour space, with control over flat vs jagged areas // Slow, accurate sharpen in LAB colour space, with control over flat vs jagged areas
if (vips_sharpen(image, &sharpened, "radius", radius, "m1", flat, "m2", jagged, NULL)) { if (vips_sharpen(image, &sharpened, "radius", radius, "m1", flat, "m2", jagged, nullptr)) {
return -1; return -1;
} }
} }

View File

@ -197,13 +197,13 @@ class PipelineWorker : public AsyncWorker {
// Input // Input
ImageType inputImageType = ImageType::UNKNOWN; ImageType inputImageType = ImageType::UNKNOWN;
VipsImage *image = NULL; VipsImage *image = nullptr;
if (baton->bufferInLength > 0) { if (baton->bufferInLength > 0) {
// From buffer // From buffer
inputImageType = DetermineImageType(baton->bufferIn, baton->bufferInLength); inputImageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
if (inputImageType != ImageType::UNKNOWN) { if (inputImageType != ImageType::UNKNOWN) {
image = InitImage(baton->bufferIn, baton->bufferInLength, baton->accessMethod); image = InitImage(baton->bufferIn, baton->bufferInLength, baton->accessMethod);
if (image == NULL) { if (image == nullptr) {
// Could not read header data // Could not read header data
(baton->err).append("Input buffer has corrupt header"); (baton->err).append("Input buffer has corrupt header");
inputImageType = ImageType::UNKNOWN; inputImageType = ImageType::UNKNOWN;
@ -213,10 +213,10 @@ class PipelineWorker : public AsyncWorker {
} }
} else { } else {
// From file // From file
inputImageType = DetermineImageType(baton->fileIn.c_str()); inputImageType = DetermineImageType(baton->fileIn.data());
if (inputImageType != ImageType::UNKNOWN) { if (inputImageType != ImageType::UNKNOWN) {
image = InitImage(baton->fileIn.c_str(), baton->accessMethod); image = InitImage(baton->fileIn.data(), baton->accessMethod);
if (image == NULL) { if (image == nullptr) {
(baton->err).append("Input file has corrupt header"); (baton->err).append("Input file has corrupt header");
inputImageType = ImageType::UNKNOWN; inputImageType = ImageType::UNKNOWN;
} }
@ -224,7 +224,7 @@ class PipelineWorker : public AsyncWorker {
(baton->err).append("Input file is of an unsupported image format"); (baton->err).append("Input file is of an unsupported image format");
} }
} }
if (image == NULL || inputImageType == ImageType::UNKNOWN) { if (image == nullptr || inputImageType == ImageType::UNKNOWN) {
return Error(); return Error();
} }
vips_object_local(hook, image); vips_object_local(hook, image);
@ -252,7 +252,7 @@ class PipelineWorker : public AsyncWorker {
// Rotate pre-extract // Rotate pre-extract
if (baton->rotateBeforePreExtract && rotation != Angle::D0) { if (baton->rotateBeforePreExtract && rotation != Angle::D0) {
VipsImage *rotated; VipsImage *rotated;
if (vips_rot(image, &rotated, static_cast<VipsAngle>(rotation), NULL)) { if (vips_rot(image, &rotated, static_cast<VipsAngle>(rotation), nullptr)) {
return Error(); return Error();
} }
vips_object_local(hook, rotated); vips_object_local(hook, rotated);
@ -263,7 +263,7 @@ class PipelineWorker : public AsyncWorker {
// Pre extraction // Pre extraction
if (baton->topOffsetPre != -1) { if (baton->topOffsetPre != -1) {
VipsImage *extractedPre; VipsImage *extractedPre;
if (vips_extract_area(image, &extractedPre, baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre, NULL)) { if (vips_extract_area(image, &extractedPre, baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre, nullptr)) {
return Error(); return Error();
} }
vips_object_local(hook, extractedPre); vips_object_local(hook, extractedPre);
@ -281,7 +281,7 @@ class PipelineWorker : public AsyncWorker {
} }
// Get window size of interpolator, used for determining shrink vs affine // Get window size of interpolator, used for determining shrink vs affine
int interpolatorWindowSize = InterpolatorWindowSize(baton->interpolator.c_str()); int interpolatorWindowSize = InterpolatorWindowSize(baton->interpolator.data());
if (interpolatorWindowSize < 0) { if (interpolatorWindowSize < 0) {
return Error(); return Error();
} }
@ -400,11 +400,11 @@ class PipelineWorker : public AsyncWorker {
// Reload input using shrink-on-load // Reload input using shrink-on-load
VipsImage *shrunkOnLoad; VipsImage *shrunkOnLoad;
if (baton->bufferInLength > 1) { if (baton->bufferInLength > 1) {
if (vips_jpegload_buffer(baton->bufferIn, baton->bufferInLength, &shrunkOnLoad, "shrink", shrink_on_load, NULL)) { if (vips_jpegload_buffer(baton->bufferIn, baton->bufferInLength, &shrunkOnLoad, "shrink", shrink_on_load, nullptr)) {
return Error(); return Error();
} }
} else { } else {
if (vips_jpegload((baton->fileIn).c_str(), &shrunkOnLoad, "shrink", shrink_on_load, NULL)) { if (vips_jpegload((baton->fileIn).data(), &shrunkOnLoad, "shrink", shrink_on_load, nullptr)) {
return Error(); return Error();
} }
} }
@ -416,7 +416,7 @@ class PipelineWorker : public AsyncWorker {
if (HasProfile(image)) { if (HasProfile(image)) {
// Convert to sRGB using embedded profile // Convert to sRGB using embedded profile
VipsImage *transformed; VipsImage *transformed;
if (!vips_icc_transform(image, &transformed, srgbProfile.c_str(), "embedded", TRUE, NULL)) { if (!vips_icc_transform(image, &transformed, srgbProfile.data(), "embedded", TRUE, nullptr)) {
// Embedded profile can fail, so only update references on success // Embedded profile can fail, so only update references on success
vips_object_local(hook, transformed); vips_object_local(hook, transformed);
image = transformed; image = transformed;
@ -425,7 +425,7 @@ class PipelineWorker : public AsyncWorker {
// Convert to sRGB using default "USWebCoatedSWOP" CMYK profile // Convert to sRGB using default "USWebCoatedSWOP" CMYK profile
std::string cmykProfile = baton->iccProfilePath + "USWebCoatedSWOP.icc"; std::string cmykProfile = baton->iccProfilePath + "USWebCoatedSWOP.icc";
VipsImage *transformed; VipsImage *transformed;
if (vips_icc_transform(image, &transformed, srgbProfile.c_str(), "input_profile", cmykProfile.c_str(), NULL)) { if (vips_icc_transform(image, &transformed, srgbProfile.data(), "input_profile", cmykProfile.data(), nullptr)) {
return Error(); return Error();
} }
vips_object_local(hook, transformed); vips_object_local(hook, transformed);
@ -442,7 +442,7 @@ class PipelineWorker : public AsyncWorker {
baton->background[2] baton->background[2]
); );
VipsImage *flattened; VipsImage *flattened;
if (vips_flatten(image, &flattened, "background", background, NULL)) { if (vips_flatten(image, &flattened, "background", background, nullptr)) {
vips_area_unref(reinterpret_cast<VipsArea*>(background)); vips_area_unref(reinterpret_cast<VipsArea*>(background));
return Error(); return Error();
} }
@ -454,7 +454,7 @@ class PipelineWorker : public AsyncWorker {
// Gamma encoding (darken) // Gamma encoding (darken)
if (baton->gamma >= 1 && baton->gamma <= 3 && !HasAlpha(image)) { if (baton->gamma >= 1 && baton->gamma <= 3 && !HasAlpha(image)) {
VipsImage *gammaEncoded; VipsImage *gammaEncoded;
if (vips_gamma(image, &gammaEncoded, "exponent", 1.0 / baton->gamma, NULL)) { if (vips_gamma(image, &gammaEncoded, "exponent", 1.0 / baton->gamma, nullptr)) {
return Error(); return Error();
} }
vips_object_local(hook, gammaEncoded); vips_object_local(hook, gammaEncoded);
@ -464,7 +464,7 @@ class PipelineWorker : public AsyncWorker {
// Convert to greyscale (linear, therefore after gamma encoding, if any) // Convert to greyscale (linear, therefore after gamma encoding, if any)
if (baton->greyscale) { if (baton->greyscale) {
VipsImage *greyscale; VipsImage *greyscale;
if (vips_colourspace(image, &greyscale, VIPS_INTERPRETATION_B_W, NULL)) { if (vips_colourspace(image, &greyscale, VIPS_INTERPRETATION_B_W, nullptr)) {
return Error(); return Error();
} }
vips_object_local(hook, greyscale); vips_object_local(hook, greyscale);
@ -474,7 +474,7 @@ class PipelineWorker : public AsyncWorker {
if (xshrink > 1 || yshrink > 1) { if (xshrink > 1 || yshrink > 1) {
VipsImage *shrunk; VipsImage *shrunk;
// Use vips_shrink with the integral reduction // Use vips_shrink with the integral reduction
if (vips_shrink(image, &shrunk, xshrink, yshrink, NULL)) { if (vips_shrink(image, &shrunk, xshrink, yshrink, nullptr)) {
return Error(); return Error();
} }
vips_object_local(hook, shrunk); vips_object_local(hook, shrunk);
@ -510,7 +510,7 @@ class PipelineWorker : public AsyncWorker {
// See: http://entropymine.com/imageworsener/resizealpha/ // See: http://entropymine.com/imageworsener/resizealpha/
if (shouldPremultiplyAlpha) { if (shouldPremultiplyAlpha) {
VipsImage *imagePremultiplied; VipsImage *imagePremultiplied;
if (vips_premultiply(image, &imagePremultiplied, NULL)) { if (vips_premultiply(image, &imagePremultiplied, nullptr)) {
(baton->err).append("Failed to premultiply alpha channel."); (baton->err).append("Failed to premultiply alpha channel.");
return Error(); return Error();
} }
@ -520,46 +520,48 @@ class PipelineWorker : public AsyncWorker {
// Use vips_affine with the remaining float part // Use vips_affine with the remaining float part
if (shouldAffineTransform) { if (shouldAffineTransform) {
// Create interpolator
VipsInterpolate *interpolator = vips_interpolate_new(baton->interpolator.data());
if (interpolator == nullptr) {
return Error();
}
vips_object_local(hook, interpolator);
// Use average of x and y residuals to compute sigma for Gaussian blur // Use average of x and y residuals to compute sigma for Gaussian blur
double residual = (xresidual + yresidual) / 2.0; double residual = (xresidual + yresidual) / 2.0;
// Apply Gaussian blur before large affine reductions // Apply Gaussian blur before large affine reductions with non-linear interpolators
if (residual < 1.0) { if (residual < 1.0 && (
baton->interpolator == "bicubic" ||
baton->interpolator == "locallyBoundedBicubic" ||
baton->interpolator == "nohalo"
)) {
// Calculate standard deviation // Calculate standard deviation
double sigma = ((1.0 / residual) - 0.4) / 3.0; double sigma = ((1.0 / residual) - 0.4) / 3.0;
if (sigma >= 0.3) { if (sigma >= 0.3) {
// Create Gaussian function for standard deviation
VipsImage *gaussian;
if (vips_gaussmat(&gaussian, sigma, 0.2, "separable", TRUE, "integer", TRUE, NULL)) {
return Error();
}
vips_object_local(hook, gaussian);
// Sequential input requires a small linecache before use of convolution // Sequential input requires a small linecache before use of convolution
if (baton->accessMethod == VIPS_ACCESS_SEQUENTIAL) { if (baton->accessMethod == VIPS_ACCESS_SEQUENTIAL) {
VipsImage *lineCached; VipsImage *lineCached;
if (vips_linecache(image, &lineCached, "access", VIPS_ACCESS_SEQUENTIAL, "tile_height", 1, "threaded", TRUE, NULL)) { if (vips_linecache(image, &lineCached, "access", VIPS_ACCESS_SEQUENTIAL,
"tile_height", 1, "threaded", TRUE, nullptr)
) {
return Error(); return Error();
} }
vips_object_local(hook, lineCached); vips_object_local(hook, lineCached);
image = lineCached; image = lineCached;
} }
// Apply Gaussian function // Apply Gaussian blur
VipsImage *blurred; VipsImage *blurred;
if (vips_convsep(image, &blurred, gaussian, "precision", VIPS_PRECISION_INTEGER, NULL)) { if (vips_gaussblur(image, &blurred, sigma, nullptr)) {
return Error(); return Error();
} }
vips_object_local(hook, blurred); vips_object_local(hook, blurred);
image = blurred; image = blurred;
} }
} }
// Create interpolator - "bilinear" (default), "bicubic" or "nohalo"
VipsInterpolate *interpolator = vips_interpolate_new(baton->interpolator.c_str());
if (interpolator == NULL) {
return Error();
}
vips_object_local(hook, interpolator);
// Perform affine transformation // Perform affine transformation
VipsImage *affined; VipsImage *affined;
if (vips_affine(image, &affined, xresidual, 0.0, 0.0, yresidual, "interpolate", interpolator, NULL)) { if (vips_affine(image, &affined, xresidual, 0.0, 0.0, yresidual,
"interpolate", interpolator, nullptr)
) {
return Error(); return Error();
} }
vips_object_local(hook, affined); vips_object_local(hook, affined);
@ -569,7 +571,7 @@ class PipelineWorker : public AsyncWorker {
// Rotate // Rotate
if (!baton->rotateBeforePreExtract && rotation != Angle::D0) { if (!baton->rotateBeforePreExtract && rotation != Angle::D0) {
VipsImage *rotated; VipsImage *rotated;
if (vips_rot(image, &rotated, static_cast<VipsAngle>(rotation), NULL)) { if (vips_rot(image, &rotated, static_cast<VipsAngle>(rotation), nullptr)) {
return Error(); return Error();
} }
vips_object_local(hook, rotated); vips_object_local(hook, rotated);
@ -580,7 +582,7 @@ class PipelineWorker : public AsyncWorker {
// Flip (mirror about Y axis) // Flip (mirror about Y axis)
if (baton->flip) { if (baton->flip) {
VipsImage *flipped; VipsImage *flipped;
if (vips_flip(image, &flipped, VIPS_DIRECTION_VERTICAL, NULL)) { if (vips_flip(image, &flipped, VIPS_DIRECTION_VERTICAL, nullptr)) {
return Error(); return Error();
} }
vips_object_local(hook, flipped); vips_object_local(hook, flipped);
@ -591,7 +593,7 @@ class PipelineWorker : public AsyncWorker {
// Flop (mirror about X axis) // Flop (mirror about X axis)
if (baton->flop) { if (baton->flop) {
VipsImage *flopped; VipsImage *flopped;
if (vips_flip(image, &flopped, VIPS_DIRECTION_HORIZONTAL, NULL)) { if (vips_flip(image, &flopped, VIPS_DIRECTION_HORIZONTAL, nullptr)) {
return Error(); return Error();
} }
vips_object_local(hook, flopped); vips_object_local(hook, flopped);
@ -606,7 +608,7 @@ class PipelineWorker : public AsyncWorker {
if (image->Type != VIPS_INTERPRETATION_sRGB) { if (image->Type != VIPS_INTERPRETATION_sRGB) {
// Convert to sRGB colour space // Convert to sRGB colour space
VipsImage *colourspaced; VipsImage *colourspaced;
if (vips_colourspace(image, &colourspaced, VIPS_INTERPRETATION_sRGB, NULL)) { if (vips_colourspace(image, &colourspaced, VIPS_INTERPRETATION_sRGB, nullptr)) {
return Error(); return Error();
} }
vips_object_local(hook, colourspaced); vips_object_local(hook, colourspaced);
@ -616,19 +618,19 @@ class PipelineWorker : public AsyncWorker {
if (baton->background[3] < 255.0 && !HasAlpha(image)) { if (baton->background[3] < 255.0 && !HasAlpha(image)) {
// Create single-channel transparency // Create single-channel transparency
VipsImage *black; VipsImage *black;
if (vips_black(&black, image->Xsize, image->Ysize, "bands", 1, NULL)) { if (vips_black(&black, image->Xsize, image->Ysize, "bands", 1, nullptr)) {
return Error(); return Error();
} }
vips_object_local(hook, black); vips_object_local(hook, black);
// Invert to become non-transparent // Invert to become non-transparent
VipsImage *alpha; VipsImage *alpha;
if (vips_invert(black, &alpha, NULL)) { if (vips_invert(black, &alpha, nullptr)) {
return Error(); return Error();
} }
vips_object_local(hook, alpha); vips_object_local(hook, alpha);
// Append alpha channel to existing image // Append alpha channel to existing image
VipsImage *joined; VipsImage *joined;
if (vips_bandjoin2(image, alpha, &joined, NULL)) { if (vips_bandjoin2(image, alpha, &joined, nullptr)) {
return Error(); return Error();
} }
vips_object_local(hook, joined); vips_object_local(hook, joined);
@ -650,7 +652,7 @@ class PipelineWorker : public AsyncWorker {
int top = (baton->height - image->Ysize) / 2; int top = (baton->height - image->Ysize) / 2;
VipsImage *embedded; VipsImage *embedded;
if (vips_embed(image, &embedded, left, top, baton->width, baton->height, if (vips_embed(image, &embedded, left, top, baton->width, baton->height,
"extend", VIPS_EXTEND_BACKGROUND, "background", background, NULL "extend", VIPS_EXTEND_BACKGROUND, "background", background, nullptr
)) { )) {
vips_area_unref(reinterpret_cast<VipsArea*>(background)); vips_area_unref(reinterpret_cast<VipsArea*>(background));
return Error(); return Error();
@ -666,7 +668,7 @@ class PipelineWorker : public AsyncWorker {
int width = std::min(image->Xsize, baton->width); int width = std::min(image->Xsize, baton->width);
int height = std::min(image->Ysize, baton->height); int height = std::min(image->Ysize, baton->height);
VipsImage *extracted; VipsImage *extracted;
if (vips_extract_area(image, &extracted, left, top, width, height, NULL)) { if (vips_extract_area(image, &extracted, left, top, width, height, nullptr)) {
return Error(); return Error();
} }
vips_object_local(hook, extracted); vips_object_local(hook, extracted);
@ -678,7 +680,7 @@ class PipelineWorker : public AsyncWorker {
if (baton->topOffsetPost != -1) { if (baton->topOffsetPost != -1) {
VipsImage *extractedPost; VipsImage *extractedPost;
if (vips_extract_area(image, &extractedPost, if (vips_extract_area(image, &extractedPost,
baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost, NULL baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost, nullptr
)) { )) {
return Error(); return Error();
} }
@ -706,12 +708,12 @@ class PipelineWorker : public AsyncWorker {
// Composite with overlay, if present // Composite with overlay, if present
if (hasOverlay) { if (hasOverlay) {
VipsImage *overlayImage = NULL; VipsImage *overlayImage = nullptr;
ImageType overlayImageType = ImageType::UNKNOWN; ImageType overlayImageType = ImageType::UNKNOWN;
overlayImageType = DetermineImageType(baton->overlayPath.c_str()); overlayImageType = DetermineImageType(baton->overlayPath.data());
if (overlayImageType != ImageType::UNKNOWN) { if (overlayImageType != ImageType::UNKNOWN) {
overlayImage = InitImage(baton->overlayPath.c_str(), baton->accessMethod); overlayImage = InitImage(baton->overlayPath.data(), baton->accessMethod);
if (overlayImage == NULL) { if (overlayImage == nullptr) {
(baton->err).append("Overlay image has corrupt header"); (baton->err).append("Overlay image has corrupt header");
return Error(); return Error();
} else { } else {
@ -742,15 +744,15 @@ class PipelineWorker : public AsyncWorker {
// Ensure overlay is sRGB // Ensure overlay is sRGB
VipsImage *overlayImageRGB; VipsImage *overlayImageRGB;
if (vips_colourspace(overlayImage, &overlayImageRGB, VIPS_INTERPRETATION_sRGB, NULL)) { if (vips_colourspace(overlayImage, &overlayImageRGB, VIPS_INTERPRETATION_sRGB, nullptr)) {
return Error(); return Error();
} }
vips_object_local(hook, overlayImageRGB); vips_object_local(hook, overlayImageRGB);
// Premultiply overlay // Premultiply overlay
VipsImage *overlayImagePremultiplied; VipsImage *overlayImagePremultiplied;
if (vips_premultiply(overlayImageRGB, &overlayImagePremultiplied, NULL)) { if (vips_premultiply(overlayImageRGB, &overlayImagePremultiplied, nullptr)) {
(baton->err).append("Failed to premultiply alpha channel of overlay image."); (baton->err).append("Failed to premultiply alpha channel of overlay image");
return Error(); return Error();
} }
vips_object_local(hook, overlayImagePremultiplied); vips_object_local(hook, overlayImagePremultiplied);
@ -767,8 +769,8 @@ class PipelineWorker : public AsyncWorker {
// Reverse premultiplication after all transformations: // Reverse premultiplication after all transformations:
if (shouldPremultiplyAlpha) { if (shouldPremultiplyAlpha) {
VipsImage *imageUnpremultiplied; VipsImage *imageUnpremultiplied;
if (vips_unpremultiply(image, &imageUnpremultiplied, NULL)) { if (vips_unpremultiply(image, &imageUnpremultiplied, nullptr)) {
(baton->err).append("Failed to unpremultiply alpha channel."); (baton->err).append("Failed to unpremultiply alpha channel");
return Error(); return Error();
} }
vips_object_local(hook, imageUnpremultiplied); vips_object_local(hook, imageUnpremultiplied);
@ -778,7 +780,7 @@ class PipelineWorker : public AsyncWorker {
// Gamma decoding (brighten) // Gamma decoding (brighten)
if (baton->gamma >= 1 && baton->gamma <= 3 && !HasAlpha(image)) { if (baton->gamma >= 1 && baton->gamma <= 3 && !HasAlpha(image)) {
VipsImage *gammaDecoded; VipsImage *gammaDecoded;
if (vips_gamma(image, &gammaDecoded, "exponent", baton->gamma, NULL)) { if (vips_gamma(image, &gammaDecoded, "exponent", baton->gamma, nullptr)) {
return Error(); return Error();
} }
vips_object_local(hook, gammaDecoded); vips_object_local(hook, gammaDecoded);
@ -798,7 +800,7 @@ class PipelineWorker : public AsyncWorker {
if (image->Type != VIPS_INTERPRETATION_sRGB) { if (image->Type != VIPS_INTERPRETATION_sRGB) {
// Switch interpretation to sRGB // Switch interpretation to sRGB
VipsImage *rgb; VipsImage *rgb;
if (vips_colourspace(image, &rgb, VIPS_INTERPRETATION_sRGB, NULL)) { if (vips_colourspace(image, &rgb, VIPS_INTERPRETATION_sRGB, nullptr)) {
return Error(); return Error();
} }
vips_object_local(hook, rgb); vips_object_local(hook, rgb);
@ -806,7 +808,7 @@ class PipelineWorker : public AsyncWorker {
// Transform colours from embedded profile to sRGB profile // Transform colours from embedded profile to sRGB profile
if (baton->withMetadata && HasProfile(image)) { if (baton->withMetadata && HasProfile(image)) {
VipsImage *profiled; VipsImage *profiled;
if (vips_icc_transform(image, &profiled, srgbProfile.c_str(), "embedded", TRUE, NULL)) { if (vips_icc_transform(image, &profiled, srgbProfile.data(), "embedded", TRUE, nullptr)) {
return Error(); return Error();
} }
vips_object_local(hook, profiled); vips_object_local(hook, profiled);
@ -822,28 +824,42 @@ class PipelineWorker : public AsyncWorker {
// Output // Output
if (baton->output == "__jpeg" || (baton->output == "__input" && inputImageType == ImageType::JPEG)) { if (baton->output == "__jpeg" || (baton->output == "__input" && inputImageType == ImageType::JPEG)) {
// Write JPEG to buffer // Write JPEG to buffer
if (vips_jpegsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata, if (vips_jpegsave_buffer(
"Q", baton->quality, "optimize_coding", TRUE, "no_subsample", baton->withoutChromaSubsampling, image, &baton->bufferOut, &baton->bufferOutLength,
"strip", !baton->withMetadata,
"Q", baton->quality,
"optimize_coding", TRUE,
"no_subsample", baton->withoutChromaSubsampling,
"trellis_quant", baton->trellisQuantisation, "trellis_quant", baton->trellisQuantisation,
"overshoot_deringing", baton->overshootDeringing, "overshoot_deringing", baton->overshootDeringing,
"optimize_scans", baton->optimiseScans, "optimize_scans", baton->optimiseScans,
"interlace", baton->progressive, NULL)) { "interlace", baton->progressive,
nullptr
)) {
return Error(); return Error();
} }
baton->outputFormat = "jpeg"; baton->outputFormat = "jpeg";
} else if (baton->output == "__png" || (baton->output == "__input" && inputImageType == ImageType::PNG)) { } else if (baton->output == "__png" || (baton->output == "__input" && inputImageType == ImageType::PNG)) {
// Select PNG row filter
int filter = baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL;
// Write PNG to buffer // Write PNG to buffer
if (vips_pngsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata, if (vips_pngsave_buffer(
"compression", baton->compressionLevel, "interlace", baton->progressive, "filter", filter, NULL)) { image, &baton->bufferOut, &baton->bufferOutLength,
"strip", !baton->withMetadata,
"compression", baton->compressionLevel,
"interlace", baton->progressive,
"filter", baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL,
nullptr
)) {
return Error(); return Error();
} }
baton->outputFormat = "png"; baton->outputFormat = "png";
} else if (baton->output == "__webp" || (baton->output == "__input" && inputImageType == ImageType::WEBP)) { } else if (baton->output == "__webp" || (baton->output == "__input" && inputImageType == ImageType::WEBP)) {
// Write WEBP to buffer // Write WEBP to buffer
if (vips_webpsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata, if (vips_webpsave_buffer(
"Q", baton->quality, NULL)) { image, &baton->bufferOut, &baton->bufferOutLength,
"strip", !baton->withMetadata,
"Q", baton->quality,
nullptr
)) {
return Error(); return Error();
} }
baton->outputFormat = "webp"; baton->outputFormat = "webp";
@ -852,7 +868,7 @@ class PipelineWorker : public AsyncWorker {
if (baton->greyscale || image->Type == VIPS_INTERPRETATION_B_W) { if (baton->greyscale || image->Type == VIPS_INTERPRETATION_B_W) {
// Extract first band for greyscale image // Extract first band for greyscale image
VipsImage *grey; VipsImage *grey;
if (vips_extract_band(image, &grey, 0, NULL)) { if (vips_extract_band(image, &grey, 0, nullptr)) {
return Error(); return Error();
} }
vips_object_local(hook, grey); vips_object_local(hook, grey);
@ -861,7 +877,7 @@ class PipelineWorker : public AsyncWorker {
if (image->BandFmt != VIPS_FORMAT_UCHAR) { if (image->BandFmt != VIPS_FORMAT_UCHAR) {
// Cast pixels to uint8 (unsigned char) // Cast pixels to uint8 (unsigned char)
VipsImage *uchar; VipsImage *uchar;
if (vips_cast(image, &uchar, VIPS_FORMAT_UCHAR, NULL)) { if (vips_cast(image, &uchar, VIPS_FORMAT_UCHAR, nullptr)) {
return Error(); return Error();
} }
vips_object_local(hook, uchar); vips_object_local(hook, uchar);
@ -869,7 +885,7 @@ class PipelineWorker : public AsyncWorker {
} }
// Get raw image data // Get raw image data
baton->bufferOut = vips_image_write_to_memory(image, &baton->bufferOutLength); baton->bufferOut = vips_image_write_to_memory(image, &baton->bufferOutLength);
if (baton->bufferOut == NULL) { if (baton->bufferOut == nullptr) {
(baton->err).append("Could not allocate enough memory for raw output"); (baton->err).append("Could not allocate enough memory for raw output");
return Error(); return Error();
} }
@ -883,42 +899,66 @@ class PipelineWorker : public AsyncWorker {
bool matchInput = !(outputJpeg || outputPng || outputWebp || outputTiff || outputDz); bool matchInput = !(outputJpeg || outputPng || outputWebp || outputTiff || outputDz);
if (outputJpeg || (matchInput && inputImageType == ImageType::JPEG)) { if (outputJpeg || (matchInput && inputImageType == ImageType::JPEG)) {
// Write JPEG to file // Write JPEG to file
if (vips_jpegsave(image, baton->output.c_str(), "strip", !baton->withMetadata, if (vips_jpegsave(
"Q", baton->quality, "optimize_coding", TRUE, "no_subsample", baton->withoutChromaSubsampling, image, baton->output.data(),
"strip", !baton->withMetadata,
"Q", baton->quality,
"optimize_coding", TRUE,
"no_subsample", baton->withoutChromaSubsampling,
"trellis_quant", baton->trellisQuantisation, "trellis_quant", baton->trellisQuantisation,
"overshoot_deringing", baton->overshootDeringing, "overshoot_deringing", baton->overshootDeringing,
"optimize_scans", baton->optimiseScans, "optimize_scans", baton->optimiseScans,
"interlace", baton->progressive, NULL)) { "interlace", baton->progressive,
nullptr
)) {
return Error(); return Error();
} }
baton->outputFormat = "jpeg"; baton->outputFormat = "jpeg";
} else if (outputPng || (matchInput && inputImageType == ImageType::PNG)) { } else if (outputPng || (matchInput && inputImageType == ImageType::PNG)) {
// Select PNG row filter
int filter = baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL;
// Write PNG to file // Write PNG to file
if (vips_pngsave(image, baton->output.c_str(), "strip", !baton->withMetadata, if (vips_pngsave(
"compression", baton->compressionLevel, "interlace", baton->progressive, "filter", filter, NULL)) { image, baton->output.data(),
"strip", !baton->withMetadata,
"compression", baton->compressionLevel,
"interlace", baton->progressive,
"filter", baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL,
nullptr
)) {
return Error(); return Error();
} }
baton->outputFormat = "png"; baton->outputFormat = "png";
} else if (outputWebp || (matchInput && inputImageType == ImageType::WEBP)) { } else if (outputWebp || (matchInput && inputImageType == ImageType::WEBP)) {
// Write WEBP to file // Write WEBP to file
if (vips_webpsave(image, baton->output.c_str(), "strip", !baton->withMetadata, if (vips_webpsave(
"Q", baton->quality, NULL)) { image, baton->output.data(),
"strip", !baton->withMetadata,
"Q", baton->quality,
nullptr
)) {
return Error(); return Error();
} }
baton->outputFormat = "webp"; baton->outputFormat = "webp";
} else if (outputTiff || (matchInput && inputImageType == ImageType::TIFF)) { } else if (outputTiff || (matchInput && inputImageType == ImageType::TIFF)) {
// Write TIFF to file // Write TIFF to file
if (vips_tiffsave(image, baton->output.c_str(), "strip", !baton->withMetadata, if (vips_tiffsave(
"compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG, "Q", baton->quality, NULL)) { image, baton->output.data(),
"strip", !baton->withMetadata,
"compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG,
"Q", baton->quality,
nullptr
)) {
return Error(); return Error();
} }
baton->outputFormat = "tiff"; baton->outputFormat = "tiff";
} else if (outputDz) { } else if (outputDz) {
// Write DZ to file // Write DZ to file
if (vips_dzsave(image, baton->output.c_str(), "strip", !baton->withMetadata, if (vips_dzsave(
"tile_size", baton->tileSize, "overlap", baton->tileOverlap, NULL)) { image, baton->output.data(),
"strip", !baton->withMetadata,
"tile_size", baton->tileSize,
"overlap", baton->tileOverlap,
nullptr
)) {
return Error(); return Error();
} }
baton->outputFormat = "dz"; baton->outputFormat = "dz";
@ -937,10 +977,10 @@ class PipelineWorker : public AsyncWorker {
void HandleOKCallback () { void HandleOKCallback () {
HandleScope(); HandleScope();
Local<Value> argv[3] = { Null(), Null(), Null() }; Local<Value> argv[3] = { Null(), Null(), Null() };
if (!baton->err.empty()) { if (!baton->err.empty()) {
// Error // Error
argv[0] = Nan::Error(baton->err.c_str()); argv[0] = Nan::Error(baton->err.data());
} else { } else {
int width = baton->width; int width = baton->width;
int height = baton->height; int height = baton->height;
@ -969,7 +1009,7 @@ class PipelineWorker : public AsyncWorker {
} else { } else {
// Add file size to info // Add file size to info
GStatBuf st; GStatBuf st;
g_stat(baton->output.c_str(), &st); g_stat(baton->output.data(), &st);
Set(info, New("size").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(st.st_size))); Set(info, New("size").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(st.st_size)));
argv[1] = info; argv[1] = info;
} }

20
src/utilities.cc Executable file → Normal file
View File

@ -168,11 +168,11 @@ NAN_METHOD(_maxColourDistance) {
VipsObject *hook = reinterpret_cast<VipsObject*>(vips_image_new()); VipsObject *hook = reinterpret_cast<VipsObject*>(vips_image_new());
// Open input files // Open input files
VipsImage *image1 = NULL; VipsImage *image1 = nullptr;
ImageType imageType1 = DetermineImageType(*Utf8String(info[0])); ImageType imageType1 = DetermineImageType(*Utf8String(info[0]));
if (imageType1 != ImageType::UNKNOWN) { if (imageType1 != ImageType::UNKNOWN) {
image1 = InitImage(*Utf8String(info[0]), VIPS_ACCESS_SEQUENTIAL); image1 = InitImage(*Utf8String(info[0]), VIPS_ACCESS_SEQUENTIAL);
if (image1 == NULL) { if (image1 == nullptr) {
g_object_unref(hook); g_object_unref(hook);
return ThrowError("Input file 1 has corrupt header"); return ThrowError("Input file 1 has corrupt header");
} else { } else {
@ -182,11 +182,11 @@ NAN_METHOD(_maxColourDistance) {
g_object_unref(hook); g_object_unref(hook);
return ThrowError("Input file 1 is of an unsupported image format"); return ThrowError("Input file 1 is of an unsupported image format");
} }
VipsImage *image2 = NULL; VipsImage *image2 = nullptr;
ImageType imageType2 = DetermineImageType(*Utf8String(info[1])); ImageType imageType2 = DetermineImageType(*Utf8String(info[1]));
if (imageType2 != ImageType::UNKNOWN) { if (imageType2 != ImageType::UNKNOWN) {
image2 = InitImage(*Utf8String(info[1]), VIPS_ACCESS_SEQUENTIAL); image2 = InitImage(*Utf8String(info[1]), VIPS_ACCESS_SEQUENTIAL);
if (image2 == NULL) { if (image2 == nullptr) {
g_object_unref(hook); g_object_unref(hook);
return ThrowError("Input file 2 has corrupt header"); return ThrowError("Input file 2 has corrupt header");
} else { } else {
@ -211,13 +211,13 @@ NAN_METHOD(_maxColourDistance) {
// Premultiply and remove alpha // Premultiply and remove alpha
if (HasAlpha(image1)) { if (HasAlpha(image1)) {
VipsImage *imagePremultiplied1; VipsImage *imagePremultiplied1;
if (vips_premultiply(image1, &imagePremultiplied1, NULL)) { if (vips_premultiply(image1, &imagePremultiplied1, nullptr)) {
g_object_unref(hook); g_object_unref(hook);
return ThrowError(vips_error_buffer()); return ThrowError(vips_error_buffer());
} }
vips_object_local(hook, imagePremultiplied1); vips_object_local(hook, imagePremultiplied1);
VipsImage *imagePremultipliedNoAlpha1; VipsImage *imagePremultipliedNoAlpha1;
if (vips_extract_band(image1, &imagePremultipliedNoAlpha1, 1, "n", image1->Bands - 1, NULL)) { if (vips_extract_band(image1, &imagePremultipliedNoAlpha1, 1, "n", image1->Bands - 1, nullptr)) {
g_object_unref(hook); g_object_unref(hook);
return ThrowError(vips_error_buffer()); return ThrowError(vips_error_buffer());
} }
@ -226,13 +226,13 @@ NAN_METHOD(_maxColourDistance) {
} }
if (HasAlpha(image2)) { if (HasAlpha(image2)) {
VipsImage *imagePremultiplied2; VipsImage *imagePremultiplied2;
if (vips_premultiply(image2, &imagePremultiplied2, NULL)) { if (vips_premultiply(image2, &imagePremultiplied2, nullptr)) {
g_object_unref(hook); g_object_unref(hook);
return ThrowError(vips_error_buffer()); return ThrowError(vips_error_buffer());
} }
vips_object_local(hook, imagePremultiplied2); vips_object_local(hook, imagePremultiplied2);
VipsImage *imagePremultipliedNoAlpha2; VipsImage *imagePremultipliedNoAlpha2;
if (vips_extract_band(image2, &imagePremultipliedNoAlpha2, 1, "n", image2->Bands - 1, NULL)) { if (vips_extract_band(image2, &imagePremultipliedNoAlpha2, 1, "n", image2->Bands - 1, nullptr)) {
g_object_unref(hook); g_object_unref(hook);
return ThrowError(vips_error_buffer()); return ThrowError(vips_error_buffer());
} }
@ -241,14 +241,14 @@ NAN_METHOD(_maxColourDistance) {
} }
// Calculate colour distance // Calculate colour distance
VipsImage *difference; VipsImage *difference;
if (vips_dE00(image1, image2, &difference, NULL)) { if (vips_dE00(image1, image2, &difference, nullptr)) {
g_object_unref(hook); g_object_unref(hook);
return ThrowError(vips_error_buffer()); return ThrowError(vips_error_buffer());
} }
vips_object_local(hook, difference); vips_object_local(hook, difference);
// Extract maximum distance // Extract maximum distance
double maxColourDistance; double maxColourDistance;
if (vips_max(difference, &maxColourDistance, NULL)) { if (vips_max(difference, &maxColourDistance, nullptr)) {
g_object_unref(hook); g_object_unref(hook);
return ThrowError(vips_error_buffer()); return ThrowError(vips_error_buffer());
} }

979
test/bench/perf.js Executable file → Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 506 B

After

Width:  |  Height:  |  Size: 519 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 377 B

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,48 @@
# Interpolators
[Photo](https://www.flickr.com/photos/aotaro/21978966091) by
[aotaro](https://www.flickr.com/photos/aotaro/) is licensed under
[CC BY 2.0](https://creativecommons.org/licenses/by/2.0/).
The following examples take the 4608x3072px original image
and resize to 480x320px using various interpolators.
To fetch the original 4608x3072px image and
generate the interpolator sample images:
```sh
curl -O https://farm6.staticflickr.com/5682/21978966091_b421afe866_o.jpg
node generate.js
```
## Nearest neighbour
![Nearest neighbour interpolation](nearest.jpg)
## Bilinear
![Bilinear interpolation](bilinear.jpg)
## Bicubic
![Bicubic interpolation](bicubic.jpg)
## Locally bounded bicubic
![Locally bounded bicubic interpolation](lbb.jpg)
## Vertex-split quadratic b-splines (VSQBS)
![Vertex-split quadratic b-splines interpolation](vsqbs.jpg)
## Nohalo
![Nohalo interpolation](nohalo.jpg)
## GraphicsMagick
![GraphicsMagick](gm.jpg)
```sh
gm convert 21978966091_b421afe866_o.jpg -resize 480x320^ -gravity center -extent 480x320 -quality 95 -strip -define jpeg:optimize-coding=true gm.jpg
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

@ -0,0 +1,11 @@
'use strict';
['nearest', 'bilinear', 'bicubic', 'vsqbs', 'lbb', 'nohalo'].forEach(function(interpolator) {
require('../../')('21978966091_b421afe866_o.jpg')
.resize(480, 320)
.interpolateWith(interpolator)
.quality(95)
.toFile(interpolator + '.jpg', function(err) {
if (err) throw err;
});
});

BIN
test/interpolators/gm.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

BIN
test/interpolators/lbb.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

2
test/unit/gamma.js Executable file → Normal file
View File

@ -16,7 +16,7 @@ describe('Gamma correction', function() {
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
assert.strictEqual(129, info.width); assert.strictEqual(129, info.width);
assert.strictEqual(111, info.height); assert.strictEqual(111, info.height);
fixtures.assertSimilar(fixtures.expected('gamma-0.0.jpg'), data, {threshold: 12}, done); fixtures.assertSimilar(fixtures.expected('gamma-0.0.jpg'), data, done);
}); });
}); });