diff --git a/docs/api.md b/docs/api.md index e0f06a15..52c6c456 100644 --- a/docs/api.md +++ b/docs/api.md @@ -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`. +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: * `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. -* `bicubic`: Use [bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation), which typically reduces performance by 5%. -* `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%. -* `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. -* `nohalo`: Use [Nohalo interpolation](http://eprints.soton.ac.uk/268086/), which prevents acutance and typically reduces performance by a factor of 3. +* `bilinear`: Use [bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation), faster than bicubic but with less smooth results. +* `vertexSplitQuadraticBasisSpline`: Use the smoother [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48) to prevent "staircasing" when enlarging. +* `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)" but typically reduces performance by a factor of 2. +* `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 sharp(inputBuffer) diff --git a/index.js b/index.js old mode 100755 new mode 100644 index 2cd67d73..c2585f3b --- a/index.js +++ b/index.js @@ -54,7 +54,7 @@ var Sharp = function(input) { flip: false, flop: false, withoutEnlargement: false, - interpolator: 'bilinear', + interpolator: 'bicubic', // operations background: [0, 0, 0, 255], flatten: false, diff --git a/packaging/lin/Dockerfile b/packaging/lin/Dockerfile index d2fa193a..98de39f1 100644 --- a/packaging/lin/Dockerfile +++ b/packaging/lin/Dockerfile @@ -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 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 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 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 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 diff --git a/src/common.cc b/src/common.cc index 9e4205bc..116c7744 100644 --- a/src/common.cc +++ b/src/common.cc @@ -78,7 +78,7 @@ namespace sharp { 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) { - 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 imageType = ImageType::UNKNOWN; char const *load = vips_foreign_find_load(file); - if (load != NULL) { + if (load != nullptr) { std::string loader = load; if (EndsWith(loader, "JpegFile")) { imageType = ImageType::JPEG; @@ -110,7 +110,7 @@ namespace sharp { Initialise and return a VipsImage from a file. */ 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) { VipsInterpolate *interpolator = vips_interpolate_new(name); - if (interpolator == NULL) { + if (interpolator == nullptr) { return -1; } 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 */ void FreeCallback(char* data, void* hint) { - if (data != NULL) { + if (data != nullptr) { g_free(data); } } diff --git a/src/metadata.cc b/src/metadata.cc index 795b3c0a..2f0b2f4c 100644 --- a/src/metadata.cc +++ b/src/metadata.cc @@ -81,13 +81,13 @@ class MetadataWorker : public AsyncWorker { g_atomic_int_dec_and_test(&counterQueue); ImageType imageType = ImageType::UNKNOWN; - VipsImage *image = NULL; + VipsImage *image = nullptr; if (baton->bufferInLength > 0) { // From buffer imageType = DetermineImageType(baton->bufferIn, baton->bufferInLength); if (imageType != ImageType::UNKNOWN) { image = InitImage(baton->bufferIn, baton->bufferInLength, VIPS_ACCESS_RANDOM); - if (image == NULL) { + if (image == nullptr) { (baton->err).append("Input buffer has corrupt header"); imageType = ImageType::UNKNOWN; } @@ -96,10 +96,10 @@ class MetadataWorker : public AsyncWorker { } } else { // From file - imageType = DetermineImageType(baton->fileIn.c_str()); + imageType = DetermineImageType(baton->fileIn.data()); if (imageType != ImageType::UNKNOWN) { - image = InitImage(baton->fileIn.c_str(), VIPS_ACCESS_RANDOM); - if (image == NULL) { + image = InitImage(baton->fileIn.data(), VIPS_ACCESS_RANDOM); + if (image == nullptr) { (baton->err).append("Input file has corrupt header"); imageType = ImageType::UNKNOWN; } @@ -107,7 +107,7 @@ class MetadataWorker : public AsyncWorker { (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 switch (imageType) { case ImageType::JPEG: baton->format = "jpeg"; break; @@ -161,7 +161,7 @@ class MetadataWorker : public AsyncWorker { Local argv[2] = { Null(), Null() }; if (!baton->err.empty()) { // Error - argv[0] = Error(baton->err.c_str()); + argv[0] = Error(baton->err.data()); } else { // Metadata Object Local info = New(); diff --git a/src/operations.cc b/src/operations.cc old mode 100755 new mode 100644 index 2e3c4f46..f2e42549 --- a/src/operations.cc +++ b/src/operations.cc @@ -14,11 +14,11 @@ namespace sharp { // Split src into non-alpha and alpha 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; vips_object_local(context, srcWithoutAlpha); 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; vips_object_local(context, srcAlpha); @@ -27,12 +27,12 @@ namespace sharp { VipsImage *dstAlpha; if (HasAlpha(dst)) { // 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; } vips_object_local(context, dstWithoutAlpha); // 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; } vips_object_local(context, dstAlpha); @@ -41,11 +41,11 @@ namespace sharp { dstWithoutAlpha = dst; // Alpha: Use blank, opaque (0xFF) image VipsImage *black; - if (vips_black(&black, dst->Xsize, dst->Ysize, NULL)) { + if (vips_black(&black, dst->Xsize, dst->Ysize, nullptr)) { return -1; } vips_object_local(context, black); - if (vips_invert(black, &dstAlpha, NULL)) { + if (vips_invert(black, &dstAlpha, nullptr)) { return -1; } vips_object_local(context, dstAlpha); @@ -53,12 +53,12 @@ namespace sharp { // Compute normalized input alpha channels: 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; vips_object_local(context, srcAlphaNormalized); 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; vips_object_local(context, dstAlphaNormalized); @@ -75,17 +75,17 @@ namespace sharp { // ^^^^^^^^^^^^^^^^^^^ // t1 VipsImage *t0; - if (vips_linear1(srcAlphaNormalized, &t0, -1.0, 1.0, NULL)) + if (vips_linear1(srcAlphaNormalized, &t0, -1.0, 1.0, nullptr)) return -1; vips_object_local(context, t0); VipsImage *t1; - if (vips_multiply(dstAlphaNormalized, t0, &t1, NULL)) + if (vips_multiply(dstAlphaNormalized, t0, &t1, nullptr)) return -1; vips_object_local(context, t1); VipsImage *outAlphaNormalized; - if (vips_add(srcAlphaNormalized, t1, &outAlphaNormalized, NULL)) + if (vips_add(srcAlphaNormalized, t1, &outAlphaNormalized, nullptr)) return -1; vips_object_local(context, outAlphaNormalized); @@ -102,23 +102,23 @@ namespace sharp { // externally. // VipsImage *t2; - if (vips_multiply(dstWithoutAlpha, t0, &t2, NULL)) + if (vips_multiply(dstWithoutAlpha, t0, &t2, nullptr)) return -1; vips_object_local(context, t2); VipsImage *outRGBPremultiplied; - if (vips_add(srcWithoutAlpha, t2, &outRGBPremultiplied, NULL)) + if (vips_add(srcWithoutAlpha, t2, &outRGBPremultiplied, nullptr)) return -1; vips_object_local(context, outRGBPremultiplied); // Denormalize output alpha channel: VipsImage *outAlpha; - if (vips_linear1(outAlphaNormalized, &outAlpha, 255.0, 0.0, NULL)) + if (vips_linear1(outAlphaNormalized, &outAlpha, 255.0, 0.0, nullptr)) return -1; vips_object_local(context, outAlpha); // 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 VipsImage *lab; - if (vips_colourspace(image, &lab, VIPS_INTERPRETATION_LAB, NULL)) { + if (vips_colourspace(image, &lab, VIPS_INTERPRETATION_LAB, nullptr)) { return -1; } vips_object_local(context, lab); // Extract 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; } vips_object_local(context, luminance); // Extract 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; } vips_object_local(context, chroma); // Find luminance range VipsImage *stats; - if (vips_stats(luminance, &stats, NULL)) { + if (vips_stats(luminance, &stats, nullptr)) { return -1; } vips_object_local(context, stats); @@ -161,19 +161,19 @@ namespace sharp { double a = -(min * f); // Scale luminance VipsImage *luminance100; - if (vips_linear1(luminance, &luminance100, f, a, NULL)) { + if (vips_linear1(luminance, &luminance100, f, a, nullptr)) { return -1; } vips_object_local(context, luminance100); // Join scaled luminance to chroma VipsImage *normalizedLab; - if (vips_bandjoin2(luminance100, chroma, &normalizedLab, NULL)) { + if (vips_bandjoin2(luminance100, chroma, &normalizedLab, nullptr)) { return -1; } vips_object_local(context, normalizedLab); // Convert to original colourspace VipsImage *normalized; - if (vips_colourspace(normalizedLab, &normalized, typeBeforeNormalize, NULL)) { + if (vips_colourspace(normalizedLab, &normalized, typeBeforeNormalize, nullptr)) { return -1; } vips_object_local(context, normalized); @@ -181,13 +181,13 @@ namespace sharp { if (HasAlpha(image)) { // Extract original alpha channel 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; } vips_object_local(context, alpha); // Join alpha channel to normalised image VipsImage *normalizedAlpha; - if (vips_bandjoin2(normalized, alpha, &normalizedAlpha, NULL)) { + if (vips_bandjoin2(normalized, alpha, &normalizedAlpha, nullptr)) { return -1; } vips_object_local(context, normalizedAlpha); @@ -215,19 +215,19 @@ namespace sharp { 1.0, 1.0, 1.0); vips_image_set_double(blur, "scale", 9); vips_object_local(context, blur); - if (vips_conv(image, &blurred, blur, NULL)) { + if (vips_conv(image, &blurred, blur, nullptr)) { return -1; } } else { // Slower, accurate Gaussian blur // Create Gaussian function for standard deviation 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; } vips_object_local(context, gaussian); // 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; } } @@ -249,12 +249,12 @@ namespace sharp { -1.0, -1.0, -1.0); vips_image_set_double(sharpen, "scale", 24); vips_object_local(context, sharpen); - if (vips_conv(image, &sharpened, sharpen, NULL)) { + if (vips_conv(image, &sharpened, sharpen, nullptr)) { return -1; } } else { // 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; } } diff --git a/src/pipeline.cc b/src/pipeline.cc index c0ae7c8a..a0d05a5a 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -197,13 +197,13 @@ class PipelineWorker : public AsyncWorker { // Input ImageType inputImageType = ImageType::UNKNOWN; - VipsImage *image = NULL; + VipsImage *image = nullptr; if (baton->bufferInLength > 0) { // From buffer inputImageType = DetermineImageType(baton->bufferIn, baton->bufferInLength); if (inputImageType != ImageType::UNKNOWN) { image = InitImage(baton->bufferIn, baton->bufferInLength, baton->accessMethod); - if (image == NULL) { + if (image == nullptr) { // Could not read header data (baton->err).append("Input buffer has corrupt header"); inputImageType = ImageType::UNKNOWN; @@ -213,10 +213,10 @@ class PipelineWorker : public AsyncWorker { } } else { // From file - inputImageType = DetermineImageType(baton->fileIn.c_str()); + inputImageType = DetermineImageType(baton->fileIn.data()); if (inputImageType != ImageType::UNKNOWN) { - image = InitImage(baton->fileIn.c_str(), baton->accessMethod); - if (image == NULL) { + image = InitImage(baton->fileIn.data(), baton->accessMethod); + if (image == nullptr) { (baton->err).append("Input file has corrupt header"); inputImageType = ImageType::UNKNOWN; } @@ -224,7 +224,7 @@ class PipelineWorker : public AsyncWorker { (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(); } vips_object_local(hook, image); @@ -252,7 +252,7 @@ class PipelineWorker : public AsyncWorker { // Rotate pre-extract if (baton->rotateBeforePreExtract && rotation != Angle::D0) { VipsImage *rotated; - if (vips_rot(image, &rotated, static_cast(rotation), NULL)) { + if (vips_rot(image, &rotated, static_cast(rotation), nullptr)) { return Error(); } vips_object_local(hook, rotated); @@ -263,7 +263,7 @@ class PipelineWorker : public AsyncWorker { // Pre extraction if (baton->topOffsetPre != -1) { 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(); } vips_object_local(hook, extractedPre); @@ -281,7 +281,7 @@ class PipelineWorker : public AsyncWorker { } // 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) { return Error(); } @@ -400,11 +400,11 @@ class PipelineWorker : public AsyncWorker { // Reload input using shrink-on-load VipsImage *shrunkOnLoad; 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(); } } 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(); } } @@ -416,7 +416,7 @@ class PipelineWorker : public AsyncWorker { if (HasProfile(image)) { // Convert to sRGB using embedded profile 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 vips_object_local(hook, transformed); image = transformed; @@ -425,7 +425,7 @@ class PipelineWorker : public AsyncWorker { // Convert to sRGB using default "USWebCoatedSWOP" CMYK profile std::string cmykProfile = baton->iccProfilePath + "USWebCoatedSWOP.icc"; 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(); } vips_object_local(hook, transformed); @@ -442,7 +442,7 @@ class PipelineWorker : public AsyncWorker { baton->background[2] ); VipsImage *flattened; - if (vips_flatten(image, &flattened, "background", background, NULL)) { + if (vips_flatten(image, &flattened, "background", background, nullptr)) { vips_area_unref(reinterpret_cast(background)); return Error(); } @@ -454,7 +454,7 @@ class PipelineWorker : public AsyncWorker { // Gamma encoding (darken) if (baton->gamma >= 1 && baton->gamma <= 3 && !HasAlpha(image)) { 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(); } vips_object_local(hook, gammaEncoded); @@ -464,7 +464,7 @@ class PipelineWorker : public AsyncWorker { // Convert to greyscale (linear, therefore after gamma encoding, if any) if (baton->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(); } vips_object_local(hook, greyscale); @@ -474,7 +474,7 @@ class PipelineWorker : public AsyncWorker { if (xshrink > 1 || yshrink > 1) { VipsImage *shrunk; // 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(); } vips_object_local(hook, shrunk); @@ -510,7 +510,7 @@ class PipelineWorker : public AsyncWorker { // See: http://entropymine.com/imageworsener/resizealpha/ if (shouldPremultiplyAlpha) { VipsImage *imagePremultiplied; - if (vips_premultiply(image, &imagePremultiplied, NULL)) { + if (vips_premultiply(image, &imagePremultiplied, nullptr)) { (baton->err).append("Failed to premultiply alpha channel."); return Error(); } @@ -520,46 +520,48 @@ class PipelineWorker : public AsyncWorker { // Use vips_affine with the remaining float part 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 double residual = (xresidual + yresidual) / 2.0; - // Apply Gaussian blur before large affine reductions - if (residual < 1.0) { + // Apply Gaussian blur before large affine reductions with non-linear interpolators + if (residual < 1.0 && ( + baton->interpolator == "bicubic" || + baton->interpolator == "locallyBoundedBicubic" || + baton->interpolator == "nohalo" + )) { // Calculate standard deviation double sigma = ((1.0 / residual) - 0.4) / 3.0; 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 if (baton->accessMethod == VIPS_ACCESS_SEQUENTIAL) { 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(); } vips_object_local(hook, lineCached); image = lineCached; } - // Apply Gaussian function + // Apply Gaussian blur VipsImage *blurred; - if (vips_convsep(image, &blurred, gaussian, "precision", VIPS_PRECISION_INTEGER, NULL)) { + if (vips_gaussblur(image, &blurred, sigma, nullptr)) { return Error(); } vips_object_local(hook, 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 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(); } vips_object_local(hook, affined); @@ -569,7 +571,7 @@ class PipelineWorker : public AsyncWorker { // Rotate if (!baton->rotateBeforePreExtract && rotation != Angle::D0) { VipsImage *rotated; - if (vips_rot(image, &rotated, static_cast(rotation), NULL)) { + if (vips_rot(image, &rotated, static_cast(rotation), nullptr)) { return Error(); } vips_object_local(hook, rotated); @@ -580,7 +582,7 @@ class PipelineWorker : public AsyncWorker { // Flip (mirror about Y axis) if (baton->flip) { VipsImage *flipped; - if (vips_flip(image, &flipped, VIPS_DIRECTION_VERTICAL, NULL)) { + if (vips_flip(image, &flipped, VIPS_DIRECTION_VERTICAL, nullptr)) { return Error(); } vips_object_local(hook, flipped); @@ -591,7 +593,7 @@ class PipelineWorker : public AsyncWorker { // Flop (mirror about X axis) if (baton->flop) { VipsImage *flopped; - if (vips_flip(image, &flopped, VIPS_DIRECTION_HORIZONTAL, NULL)) { + if (vips_flip(image, &flopped, VIPS_DIRECTION_HORIZONTAL, nullptr)) { return Error(); } vips_object_local(hook, flopped); @@ -606,7 +608,7 @@ class PipelineWorker : public AsyncWorker { if (image->Type != VIPS_INTERPRETATION_sRGB) { // Convert to sRGB colour space VipsImage *colourspaced; - if (vips_colourspace(image, &colourspaced, VIPS_INTERPRETATION_sRGB, NULL)) { + if (vips_colourspace(image, &colourspaced, VIPS_INTERPRETATION_sRGB, nullptr)) { return Error(); } vips_object_local(hook, colourspaced); @@ -616,19 +618,19 @@ class PipelineWorker : public AsyncWorker { if (baton->background[3] < 255.0 && !HasAlpha(image)) { // Create single-channel transparency 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(); } vips_object_local(hook, black); // Invert to become non-transparent VipsImage *alpha; - if (vips_invert(black, &alpha, NULL)) { + if (vips_invert(black, &alpha, nullptr)) { return Error(); } vips_object_local(hook, alpha); // Append alpha channel to existing image VipsImage *joined; - if (vips_bandjoin2(image, alpha, &joined, NULL)) { + if (vips_bandjoin2(image, alpha, &joined, nullptr)) { return Error(); } vips_object_local(hook, joined); @@ -650,7 +652,7 @@ class PipelineWorker : public AsyncWorker { int top = (baton->height - image->Ysize) / 2; VipsImage *embedded; 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(background)); return Error(); @@ -666,7 +668,7 @@ class PipelineWorker : public AsyncWorker { int width = std::min(image->Xsize, baton->width); int height = std::min(image->Ysize, baton->height); 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(); } vips_object_local(hook, extracted); @@ -678,7 +680,7 @@ class PipelineWorker : public AsyncWorker { if (baton->topOffsetPost != -1) { VipsImage *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(); } @@ -706,12 +708,12 @@ class PipelineWorker : public AsyncWorker { // Composite with overlay, if present if (hasOverlay) { - VipsImage *overlayImage = NULL; + VipsImage *overlayImage = nullptr; ImageType overlayImageType = ImageType::UNKNOWN; - overlayImageType = DetermineImageType(baton->overlayPath.c_str()); + overlayImageType = DetermineImageType(baton->overlayPath.data()); if (overlayImageType != ImageType::UNKNOWN) { - overlayImage = InitImage(baton->overlayPath.c_str(), baton->accessMethod); - if (overlayImage == NULL) { + overlayImage = InitImage(baton->overlayPath.data(), baton->accessMethod); + if (overlayImage == nullptr) { (baton->err).append("Overlay image has corrupt header"); return Error(); } else { @@ -742,15 +744,15 @@ class PipelineWorker : public AsyncWorker { // Ensure overlay is sRGB VipsImage *overlayImageRGB; - if (vips_colourspace(overlayImage, &overlayImageRGB, VIPS_INTERPRETATION_sRGB, NULL)) { + if (vips_colourspace(overlayImage, &overlayImageRGB, VIPS_INTERPRETATION_sRGB, nullptr)) { return Error(); } vips_object_local(hook, overlayImageRGB); // Premultiply overlay VipsImage *overlayImagePremultiplied; - if (vips_premultiply(overlayImageRGB, &overlayImagePremultiplied, NULL)) { - (baton->err).append("Failed to premultiply alpha channel of overlay image."); + if (vips_premultiply(overlayImageRGB, &overlayImagePremultiplied, nullptr)) { + (baton->err).append("Failed to premultiply alpha channel of overlay image"); return Error(); } vips_object_local(hook, overlayImagePremultiplied); @@ -767,8 +769,8 @@ class PipelineWorker : public AsyncWorker { // Reverse premultiplication after all transformations: if (shouldPremultiplyAlpha) { VipsImage *imageUnpremultiplied; - if (vips_unpremultiply(image, &imageUnpremultiplied, NULL)) { - (baton->err).append("Failed to unpremultiply alpha channel."); + if (vips_unpremultiply(image, &imageUnpremultiplied, nullptr)) { + (baton->err).append("Failed to unpremultiply alpha channel"); return Error(); } vips_object_local(hook, imageUnpremultiplied); @@ -778,7 +780,7 @@ class PipelineWorker : public AsyncWorker { // Gamma decoding (brighten) if (baton->gamma >= 1 && baton->gamma <= 3 && !HasAlpha(image)) { VipsImage *gammaDecoded; - if (vips_gamma(image, &gammaDecoded, "exponent", baton->gamma, NULL)) { + if (vips_gamma(image, &gammaDecoded, "exponent", baton->gamma, nullptr)) { return Error(); } vips_object_local(hook, gammaDecoded); @@ -798,7 +800,7 @@ class PipelineWorker : public AsyncWorker { if (image->Type != VIPS_INTERPRETATION_sRGB) { // Switch interpretation to sRGB VipsImage *rgb; - if (vips_colourspace(image, &rgb, VIPS_INTERPRETATION_sRGB, NULL)) { + if (vips_colourspace(image, &rgb, VIPS_INTERPRETATION_sRGB, nullptr)) { return Error(); } vips_object_local(hook, rgb); @@ -806,7 +808,7 @@ class PipelineWorker : public AsyncWorker { // Transform colours from embedded profile to sRGB profile if (baton->withMetadata && HasProfile(image)) { 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(); } vips_object_local(hook, profiled); @@ -822,28 +824,42 @@ class PipelineWorker : public AsyncWorker { // Output 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, "no_subsample", baton->withoutChromaSubsampling, + if (vips_jpegsave_buffer( + image, &baton->bufferOut, &baton->bufferOutLength, + "strip", !baton->withMetadata, + "Q", baton->quality, + "optimize_coding", TRUE, + "no_subsample", baton->withoutChromaSubsampling, "trellis_quant", baton->trellisQuantisation, "overshoot_deringing", baton->overshootDeringing, "optimize_scans", baton->optimiseScans, - "interlace", baton->progressive, NULL)) { + "interlace", baton->progressive, + nullptr + )) { return Error(); } baton->outputFormat = "jpeg"; } 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 - if (vips_pngsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata, - "compression", baton->compressionLevel, "interlace", baton->progressive, "filter", filter, NULL)) { + if (vips_pngsave_buffer( + 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(); } baton->outputFormat = "png"; } 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)) { + if (vips_webpsave_buffer( + image, &baton->bufferOut, &baton->bufferOutLength, + "strip", !baton->withMetadata, + "Q", baton->quality, + nullptr + )) { return Error(); } baton->outputFormat = "webp"; @@ -852,7 +868,7 @@ class PipelineWorker : public AsyncWorker { if (baton->greyscale || image->Type == VIPS_INTERPRETATION_B_W) { // Extract first band for greyscale image VipsImage *grey; - if (vips_extract_band(image, &grey, 0, NULL)) { + if (vips_extract_band(image, &grey, 0, nullptr)) { return Error(); } vips_object_local(hook, grey); @@ -861,7 +877,7 @@ class PipelineWorker : public AsyncWorker { if (image->BandFmt != VIPS_FORMAT_UCHAR) { // Cast pixels to uint8 (unsigned char) VipsImage *uchar; - if (vips_cast(image, &uchar, VIPS_FORMAT_UCHAR, NULL)) { + if (vips_cast(image, &uchar, VIPS_FORMAT_UCHAR, nullptr)) { return Error(); } vips_object_local(hook, uchar); @@ -869,7 +885,7 @@ class PipelineWorker : public AsyncWorker { } // Get raw image data 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"); return Error(); } @@ -883,42 +899,66 @@ class PipelineWorker : public AsyncWorker { bool matchInput = !(outputJpeg || outputPng || outputWebp || outputTiff || outputDz); 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, "no_subsample", baton->withoutChromaSubsampling, + if (vips_jpegsave( + image, baton->output.data(), + "strip", !baton->withMetadata, + "Q", baton->quality, + "optimize_coding", TRUE, + "no_subsample", baton->withoutChromaSubsampling, "trellis_quant", baton->trellisQuantisation, "overshoot_deringing", baton->overshootDeringing, "optimize_scans", baton->optimiseScans, - "interlace", baton->progressive, NULL)) { + "interlace", baton->progressive, + nullptr + )) { return Error(); } baton->outputFormat = "jpeg"; } 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 - if (vips_pngsave(image, baton->output.c_str(), "strip", !baton->withMetadata, - "compression", baton->compressionLevel, "interlace", baton->progressive, "filter", filter, NULL)) { + if (vips_pngsave( + 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(); } baton->outputFormat = "png"; } 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)) { + if (vips_webpsave( + image, baton->output.data(), + "strip", !baton->withMetadata, + "Q", baton->quality, + nullptr + )) { return Error(); } baton->outputFormat = "webp"; } 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)) { + if (vips_tiffsave( + image, baton->output.data(), + "strip", !baton->withMetadata, + "compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG, + "Q", baton->quality, + nullptr + )) { return Error(); } baton->outputFormat = "tiff"; } else if (outputDz) { // Write DZ to file - if (vips_dzsave(image, baton->output.c_str(), "strip", !baton->withMetadata, - "tile_size", baton->tileSize, "overlap", baton->tileOverlap, NULL)) { + if (vips_dzsave( + image, baton->output.data(), + "strip", !baton->withMetadata, + "tile_size", baton->tileSize, + "overlap", baton->tileOverlap, + nullptr + )) { return Error(); } baton->outputFormat = "dz"; @@ -937,10 +977,10 @@ class PipelineWorker : public AsyncWorker { void HandleOKCallback () { HandleScope(); - Local argv[3] = { Null(), Null(), Null() }; + Local argv[3] = { Null(), Null(), Null() }; if (!baton->err.empty()) { // Error - argv[0] = Nan::Error(baton->err.c_str()); + argv[0] = Nan::Error(baton->err.data()); } else { int width = baton->width; int height = baton->height; @@ -969,7 +1009,7 @@ class PipelineWorker : public AsyncWorker { } else { // Add file size to info GStatBuf st; - g_stat(baton->output.c_str(), &st); + g_stat(baton->output.data(), &st); Set(info, New("size").ToLocalChecked(), New(static_cast(st.st_size))); argv[1] = info; } diff --git a/src/utilities.cc b/src/utilities.cc old mode 100755 new mode 100644 index cd7f42c9..f2c420ee --- a/src/utilities.cc +++ b/src/utilities.cc @@ -168,11 +168,11 @@ NAN_METHOD(_maxColourDistance) { VipsObject *hook = reinterpret_cast(vips_image_new()); // Open input files - VipsImage *image1 = NULL; + VipsImage *image1 = nullptr; ImageType imageType1 = DetermineImageType(*Utf8String(info[0])); if (imageType1 != ImageType::UNKNOWN) { image1 = InitImage(*Utf8String(info[0]), VIPS_ACCESS_SEQUENTIAL); - if (image1 == NULL) { + if (image1 == nullptr) { g_object_unref(hook); return ThrowError("Input file 1 has corrupt header"); } else { @@ -182,11 +182,11 @@ NAN_METHOD(_maxColourDistance) { g_object_unref(hook); return ThrowError("Input file 1 is of an unsupported image format"); } - VipsImage *image2 = NULL; + VipsImage *image2 = nullptr; ImageType imageType2 = DetermineImageType(*Utf8String(info[1])); if (imageType2 != ImageType::UNKNOWN) { image2 = InitImage(*Utf8String(info[1]), VIPS_ACCESS_SEQUENTIAL); - if (image2 == NULL) { + if (image2 == nullptr) { g_object_unref(hook); return ThrowError("Input file 2 has corrupt header"); } else { @@ -211,13 +211,13 @@ NAN_METHOD(_maxColourDistance) { // Premultiply and remove alpha if (HasAlpha(image1)) { VipsImage *imagePremultiplied1; - if (vips_premultiply(image1, &imagePremultiplied1, NULL)) { + if (vips_premultiply(image1, &imagePremultiplied1, nullptr)) { g_object_unref(hook); return ThrowError(vips_error_buffer()); } vips_object_local(hook, imagePremultiplied1); 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); return ThrowError(vips_error_buffer()); } @@ -226,13 +226,13 @@ NAN_METHOD(_maxColourDistance) { } if (HasAlpha(image2)) { VipsImage *imagePremultiplied2; - if (vips_premultiply(image2, &imagePremultiplied2, NULL)) { + if (vips_premultiply(image2, &imagePremultiplied2, nullptr)) { g_object_unref(hook); return ThrowError(vips_error_buffer()); } vips_object_local(hook, imagePremultiplied2); 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); return ThrowError(vips_error_buffer()); } @@ -241,14 +241,14 @@ NAN_METHOD(_maxColourDistance) { } // Calculate colour distance VipsImage *difference; - if (vips_dE00(image1, image2, &difference, NULL)) { + if (vips_dE00(image1, image2, &difference, nullptr)) { g_object_unref(hook); return ThrowError(vips_error_buffer()); } vips_object_local(hook, difference); // Extract maximum distance double maxColourDistance; - if (vips_max(difference, &maxColourDistance, NULL)) { + if (vips_max(difference, &maxColourDistance, nullptr)) { g_object_unref(hook); return ThrowError(vips_error_buffer()); } diff --git a/test/bench/perf.js b/test/bench/perf.js old mode 100755 new mode 100644 index 12d7a5e0..a19e1d67 --- a/test/bench/perf.js +++ b/test/bench/perf.js @@ -11,7 +11,7 @@ var semver = require('semver'); var gm = require('gm'); var imagemagick = require('imagemagick'); var jimp = require('jimp'); -var sharp = require('../../index'); +var sharp = require('../../'); var imagemagickNative; try { imagemagickNative = require('imagemagick-native'); @@ -30,16 +30,16 @@ var fixtures = require('../fixtures'); var width = 720; var height = 480; -// Approximately equivalent to fast bilinear -var magickFilter = 'Triangle'; +var magickFilterBilinear = 'Triangle'; +var magickFilterBicubic = 'Lanczos'; // Disable libvips cache to ensure tests are as fair as they can be sharp.cache(0); async.series({ - jpeg: function(callback) { + 'jpeg-linear': function(callback) { var inputJpgBuffer = fs.readFileSync(fixtures.inputJpg); - var jpegSuite = new Benchmark.Suite('jpeg'); + var jpegSuite = new Benchmark.Suite('jpeg-linear'); // jimp jpegSuite.add('jimp-buffer-buffer', { defer: true, @@ -138,7 +138,7 @@ async.series({ width: width, height: height, format: 'jpg', - filter: magickFilter + filter: magickFilterBilinear }, function(err) { if (err) { throw err; @@ -159,7 +159,7 @@ async.series({ width: width, height: height, format: 'JPEG', - filter: magickFilter + filter: magickFilterBilinear }, function (err, buffer) { if (err) { throw err; @@ -177,7 +177,7 @@ async.series({ fn: function(deferred) { gm(inputJpgBuffer) .resize(width, height) - .filter(magickFilter) + .filter(magickFilterBilinear) .quality(80) .write(fixtures.outputJpg, function (err) { if (err) { @@ -192,7 +192,7 @@ async.series({ fn: function(deferred) { gm(inputJpgBuffer) .resize(width, height) - .filter(magickFilter) + .filter(magickFilterBilinear) .quality(80) .toBuffer(function (err, buffer) { if (err) { @@ -208,7 +208,7 @@ async.series({ fn: function(deferred) { gm(fixtures.inputJpg) .resize(width, height) - .filter(magickFilter) + .filter(magickFilterBilinear) .quality(80) .write(fixtures.outputJpg, function (err) { if (err) { @@ -223,7 +223,7 @@ async.series({ fn: function(deferred) { gm(fixtures.inputJpg) .resize(width, height) - .filter(magickFilter) + .filter(magickFilterBilinear) .quality(80) .toBuffer(function (err, buffer) { if (err) { @@ -239,36 +239,45 @@ async.series({ jpegSuite.add('sharp-buffer-file', { defer: true, fn: function(deferred) { - sharp(inputJpgBuffer).resize(width, height).toFile(fixtures.outputJpg, function(err) { - if (err) { - throw err; - } else { - deferred.resolve(); - } - }); + sharp(inputJpgBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .toFile(fixtures.outputJpg, function(err) { + if (err) { + throw err; + } else { + deferred.resolve(); + } + }); } }).add('sharp-buffer-buffer', { defer: true, fn: function(deferred) { - sharp(inputJpgBuffer).resize(width, height).toBuffer(function(err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); + sharp(inputJpgBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); } }).add('sharp-file-file', { defer: true, fn: function(deferred) { - sharp(fixtures.inputJpg).resize(width, height).toFile(fixtures.outputJpg, function(err) { - if (err) { - throw err; - } else { - deferred.resolve(); - } - }); + sharp(fixtures.inputJpg) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .toFile(fixtures.outputJpg, function(err) { + if (err) { + throw err; + } else { + deferred.resolve(); + } + }); } }).add('sharp-stream-stream', { defer: true, @@ -278,239 +287,585 @@ async.series({ writable.on('finish', function() { deferred.resolve(); }); - var pipeline = sharp().resize(width, height); + var pipeline = sharp() + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear); readable.pipe(pipeline).pipe(writable); } }).add('sharp-file-buffer', { defer: true, fn: function(deferred) { - sharp(fixtures.inputJpg).resize(width, height).toBuffer(function(err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); + sharp(fixtures.inputJpg) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); } }).add('sharp-promise', { defer: true, fn: function(deferred) { - sharp(inputJpgBuffer).resize(width, height).toBuffer().then(function(buffer) { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - }); + sharp(inputJpgBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .toBuffer() + .then(function(buffer) { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + }); } }).add('sharp-sharpen-mild', { defer: true, fn: function(deferred) { - sharp(inputJpgBuffer).resize(width, height).sharpen().toBuffer(function(err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); + sharp(inputJpgBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .sharpen() + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); } }).add('sharp-sharpen-radius', { defer: true, fn: function(deferred) { - sharp(inputJpgBuffer).resize(width, height).sharpen(3, 1, 3).toBuffer(function(err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); + sharp(inputJpgBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .sharpen(3, 1, 3) + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); } }).add('sharp-blur-mild', { defer: true, fn: function(deferred) { - sharp(inputJpgBuffer).resize(width, height).blur().toBuffer(function(err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); + sharp(inputJpgBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .blur() + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); } }).add('sharp-blur-radius', { defer: true, fn: function(deferred) { - sharp(inputJpgBuffer).resize(width, height).blur(3).toBuffer(function(err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); - } - }).add('sharp-nearest-neighbour', { - defer: true, - fn: function(deferred) { - sharp(inputJpgBuffer).resize(width, height).interpolateWith(sharp.interpolator.nearest).toBuffer(function(err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); - } - }).add('sharp-bicubic', { - defer: true, - fn: function(deferred) { - sharp(inputJpgBuffer).resize(width, height).interpolateWith(sharp.interpolator.bicubic).toBuffer(function(err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); - } - }).add('sharp-nohalo', { - defer: true, - fn: function(deferred) { - sharp(inputJpgBuffer).resize(width, height).interpolateWith(sharp.interpolator.nohalo).toBuffer(function(err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); - } - }).add('sharp-locallyBoundedBicubic', { - defer: true, - fn: function(deferred) { - sharp(inputJpgBuffer).resize(width, height).interpolateWith(sharp.interpolator.locallyBoundedBicubic).toBuffer(function(err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); - } - }).add('sharp-vertexSplitQuadraticBasisSpline', { - defer: true, - fn: function(deferred) { - sharp(inputJpgBuffer).resize(width, height).interpolateWith(sharp.interpolator.vertexSplitQuadraticBasisSpline).toBuffer(function(err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); + sharp(inputJpgBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .blur(3) + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); } }).add('sharp-gamma', { defer: true, fn: function(deferred) { - sharp(inputJpgBuffer).resize(width, height).gamma().toBuffer(function(err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); + sharp(inputJpgBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .gamma() + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); } }).add('sharp-normalise', { defer: true, fn: function(deferred) { - sharp(inputJpgBuffer).resize(width, height).normalise().toBuffer(function(err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); + sharp(inputJpgBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .normalise() + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); } }).add('sharp-greyscale', { defer: true, fn: function(deferred) { - sharp(inputJpgBuffer).resize(width, height).greyscale().toBuffer(function(err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); + sharp(inputJpgBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .greyscale() + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); } }).add('sharp-greyscale-gamma', { defer: true, fn: function(deferred) { - sharp(inputJpgBuffer).resize(width, height).gamma().greyscale().toBuffer(function(err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); + sharp(inputJpgBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .gamma() + .greyscale() + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); } }).add('sharp-progressive', { defer: true, fn: function(deferred) { - sharp(inputJpgBuffer).resize(width, height).progressive().toBuffer(function(err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); + sharp(inputJpgBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .progressive() + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); } }).add('sharp-without-chroma-subsampling', { defer: true, fn: function(deferred) { - sharp(inputJpgBuffer).resize(width, height).withoutChromaSubsampling().toBuffer(function(err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); + sharp(inputJpgBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .withoutChromaSubsampling() + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); } }).add('sharp-rotate', { defer: true, fn: function(deferred) { - sharp(inputJpgBuffer).rotate(90).resize(width, height).toBuffer(function(err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); + sharp(inputJpgBuffer) + .rotate(90) + .interpolateWith(sharp.interpolator.bilinear) + .resize(width, height) + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); } }).add('sharp-sequentialRead', { defer: true, fn: function(deferred) { - sharp(inputJpgBuffer).resize(width, height).sequentialRead().toBuffer(function(err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); + sharp(inputJpgBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .sequentialRead() + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); } }).on('cycle', function(event) { - console.log('jpeg ' + String(event.target)); + console.log('jpeg-linear ' + String(event.target)); }).on('complete', function() { callback(null, this.filter('fastest').pluck('name')); }).run(); }, + + 'jpeg-cubic': function(callback) { + var inputJpgBuffer = fs.readFileSync(fixtures.inputJpg); + var jpegSuite = new Benchmark.Suite('jpeg-cubic'); + // lwip + if (typeof lwip !== 'undefined') { + jpegSuite.add('lwip-file-file', { + defer: true, + fn: function(deferred) { + lwip.open(fixtures.inputJpg, function (err, image) { + if (err) { + throw err; + } + image.resize(width, height, 'lanczos', function (err, image) { + if (err) { + throw err; + } + image.writeFile(fixtures.outputJpg, {quality: 80}, function (err) { + if (err) { + throw err; + } + deferred.resolve(); + }); + }); + }); + } + }).add('lwip-buffer-buffer', { + defer: true, + fn: function(deferred) { + lwip.open(inputJpgBuffer, 'jpg', function (err, image) { + if (err) { + throw err; + } + image.resize(width, height, 'lanczos', function (err, image) { + if (err) { + throw err; + } + image.toBuffer('jpg', {quality: 80}, function (err, buffer) { + if (err) { + throw err; + } + assert.notStrictEqual(null, buffer); + deferred.resolve(); + }); + }); + }); + } + }); + } + // imagemagick + jpegSuite.add('imagemagick-file-file', { + defer: true, + fn: function(deferred) { + imagemagick.resize({ + srcPath: fixtures.inputJpg, + dstPath: fixtures.outputJpg, + quality: 0.8, + width: width, + height: height, + format: 'jpg', + filter: magickFilterBicubic + }, function(err) { + if (err) { + throw err; + } else { + deferred.resolve(); + } + }); + } + }); + // imagemagick-native + if (typeof imagemagickNative !== 'undefined') { + jpegSuite.add('imagemagick-native-buffer-buffer', { + defer: true, + fn: function(deferred) { + imagemagickNative.convert({ + srcData: inputJpgBuffer, + quality: 80, + width: width, + height: height, + format: 'JPEG', + filter: magickFilterBicubic + }, function (err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); + } + }); + } + // gm + jpegSuite.add('gm-buffer-file', { + defer: true, + fn: function(deferred) { + gm(inputJpgBuffer) + .resize(width, height) + .filter(magickFilterBicubic) + .quality(80) + .write(fixtures.outputJpg, function (err) { + if (err) { + throw err; + } else { + deferred.resolve(); + } + }); + } + }).add('gm-buffer-buffer', { + defer: true, + fn: function(deferred) { + gm(inputJpgBuffer) + .resize(width, height) + .filter(magickFilterBicubic) + .quality(80) + .toBuffer(function (err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); + } + }).add('gm-file-file', { + defer: true, + fn: function(deferred) { + gm(fixtures.inputJpg) + .resize(width, height) + .filter(magickFilterBicubic) + .quality(80) + .write(fixtures.outputJpg, function (err) { + if (err) { + throw err; + } else { + deferred.resolve(); + } + }); + } + }).add('gm-file-buffer', { + defer: true, + fn: function(deferred) { + gm(fixtures.inputJpg) + .resize(width, height) + .filter(magickFilterBicubic) + .quality(80) + .toBuffer(function (err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); + } + }); + // sharp + jpegSuite.add('sharp-buffer-file', { + defer: true, + fn: function(deferred) { + sharp(inputJpgBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bicubic) + .toFile(fixtures.outputJpg, function(err) { + if (err) { + throw err; + } else { + deferred.resolve(); + } + }); + } + }).add('sharp-buffer-buffer', { + defer: true, + fn: function(deferred) { + sharp(inputJpgBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bicubic) + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); + } + }).add('sharp-file-file', { + defer: true, + fn: function(deferred) { + sharp(fixtures.inputJpg) + .resize(width, height) + .interpolateWith(sharp.interpolator.bicubic) + .toFile(fixtures.outputJpg, function(err) { + if (err) { + throw err; + } else { + deferred.resolve(); + } + }); + } + }).add('sharp-stream-stream', { + defer: true, + fn: function(deferred) { + var readable = fs.createReadStream(fixtures.inputJpg); + var writable = fs.createWriteStream(fixtures.outputJpg); + writable.on('finish', function() { + deferred.resolve(); + }); + var pipeline = sharp() + .resize(width, height) + .interpolateWith(sharp.interpolator.bicubic); + readable.pipe(pipeline).pipe(writable); + } + }).add('sharp-file-buffer', { + defer: true, + fn: function(deferred) { + sharp(fixtures.inputJpg) + .resize(width, height) + .interpolateWith(sharp.interpolator.bicubic) + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); + } + }).add('sharp-promise', { + defer: true, + fn: function(deferred) { + sharp(inputJpgBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bicubic) + .toBuffer() + .then(function(buffer) { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + }); + } + }).on('cycle', function(event) { + console.log('jpeg-cubic ' + String(event.target)); + }).on('complete', function() { + callback(null, this.filter('fastest').pluck('name')); + }).run(); + }, + + // Comparitive speed of pixel interpolators + interpolators: function(callback) { + var inputJpgBuffer = fs.readFileSync(fixtures.inputJpg); + (new Benchmark.Suite('interpolators')).add('sharp-nearest-neighbour', { + defer: true, + fn: function(deferred) { + sharp(inputJpgBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.nearest) + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); + } + }).add('sharp-bilinear', { + defer: true, + fn: function(deferred) { + sharp(inputJpgBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); + } + }).add('sharp-vertexSplitQuadraticBasisSpline', { + defer: true, + fn: function(deferred) { + sharp(inputJpgBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.vertexSplitQuadraticBasisSpline) + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); + } + }).add('sharp-bicubic', { + defer: true, + fn: function(deferred) { + sharp(inputJpgBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bicubic) + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); + } + }).add('sharp-locallyBoundedBicubic', { + defer: true, + fn: function(deferred) { + sharp(inputJpgBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.locallyBoundedBicubic) + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); + } + }).add('sharp-nohalo', { + defer: true, + fn: function(deferred) { + sharp(inputJpgBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.nohalo) + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); + } + }).on('cycle', function(event) { + console.log('interpolators ' + String(event.target)); + }).on('complete', function() { + callback(null, this.filter('fastest').pluck('name')); + }).run(); + }, + png: function(callback) { var inputPngBuffer = fs.readFileSync(fixtures.inputPng); var pngSuite = new Benchmark.Suite('png'); @@ -588,7 +943,7 @@ async.series({ dstPath: fixtures.outputPng, width: width, height: height, - filter: magickFilter + filter: magickFilterBilinear }, function(err) { if (err) { throw err; @@ -608,7 +963,7 @@ async.series({ width: width, height: height, format: 'PNG', - filter: magickFilter + filter: magickFilterBilinear }); deferred.resolve(); } @@ -620,7 +975,7 @@ async.series({ fn: function(deferred) { gm(fixtures.inputPng) .resize(width, height) - .filter(magickFilter) + .filter(magickFilterBilinear) .write(fixtures.outputPng, function (err) { if (err) { throw err; @@ -634,7 +989,7 @@ async.series({ fn: function(deferred) { gm(fixtures.inputPng) .resize(width, height) - .filter(magickFilter) + .filter(magickFilterBilinear) .toBuffer(function (err, buffer) { if (err) { throw err; @@ -649,67 +1004,24 @@ async.series({ pngSuite.add('sharp-buffer-file', { defer: true, fn: function(deferred) { - sharp(inputPngBuffer).resize(width, height).toFile(fixtures.outputPng, function(err) { - if (err) { - throw err; - } else { - deferred.resolve(); - } - }); + sharp(inputPngBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .toFile(fixtures.outputPng, function(err) { + if (err) { + throw err; + } else { + deferred.resolve(); + } + }); } }).add('sharp-buffer-buffer', { defer: true, fn: function(deferred) { - sharp(inputPngBuffer).resize(width, height).toBuffer(function(err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); - } - }).add('sharp-file-file', { - defer: true, - fn: function(deferred) { - sharp(fixtures.inputPng).resize(width, height).toFile(fixtures.outputPng, function(err) { - if (err) { - throw err; - } else { - deferred.resolve(); - } - }); - } - }).add('sharp-file-buffer', { - defer: true, - fn: function(deferred) { - sharp(fixtures.inputPng).resize(width, height).toBuffer(function(err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); - } - }).add('sharp-progressive', { - defer: true, - fn: function(deferred) { - sharp(inputPngBuffer).resize(width, height).progressive().toBuffer(function(err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); - } - }); - if (semver.gte(sharp.libvipsVersion(), '7.41.0')) { - pngSuite.add('sharp-withoutAdaptiveFiltering', { - defer: true, - fn: function(deferred) { - sharp(inputPngBuffer).resize(width, height).withoutAdaptiveFiltering().toBuffer(function(err, buffer) { + sharp(inputPngBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .toBuffer(function(err, buffer) { if (err) { throw err; } else { @@ -717,62 +1029,135 @@ async.series({ deferred.resolve(); } }); - } - }); - } + } + }).add('sharp-file-file', { + defer: true, + fn: function(deferred) { + sharp(fixtures.inputPng) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .toFile(fixtures.outputPng, function(err) { + if (err) { + throw err; + } else { + deferred.resolve(); + } + }); + } + }).add('sharp-file-buffer', { + defer: true, + fn: function(deferred) { + sharp(fixtures.inputPng) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); + } + }).add('sharp-progressive', { + defer: true, + fn: function(deferred) { + sharp(inputPngBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .progressive() + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); + } + }).add('sharp-withoutAdaptiveFiltering', { + defer: true, + fn: function(deferred) { + sharp(inputPngBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .withoutAdaptiveFiltering() + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); + } + }); pngSuite.on('cycle', function(event) { console.log(' png ' + String(event.target)); }).on('complete', function() { callback(null, this.filter('fastest').pluck('name')); }).run(); }, + webp: function(callback) { var inputWebPBuffer = fs.readFileSync(fixtures.inputWebP); (new Benchmark.Suite('webp')).add('sharp-buffer-file', { defer: true, fn: function(deferred) { - sharp(inputWebPBuffer).resize(width, height).toFile(fixtures.outputWebP, function(err) { - if (err) { - throw err; - } else { - deferred.resolve(); - } - }); + sharp(inputWebPBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .toFile(fixtures.outputWebP, function(err) { + if (err) { + throw err; + } else { + deferred.resolve(); + } + }); } }).add('sharp-buffer-buffer', { defer: true, fn: function(deferred) { - sharp(inputWebPBuffer).resize(width, height).toBuffer(function(err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); + sharp(inputWebPBuffer) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); } }).add('sharp-file-file', { defer: true, fn: function(deferred) { - sharp(fixtures.inputWebP).resize(width, height).toFile(fixtures.outputWebP, function(err) { - if (err) { - throw err; - } else { - deferred.resolve(); - } - }); + sharp(fixtures.inputWebP) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .toFile(fixtures.outputWebP, function(err) { + if (err) { + throw err; + } else { + deferred.resolve(); + } + }); } }).add('sharp-file-buffer', { defer: true, fn: function(deferred) { - sharp(fixtures.inputWebp).resize(width, height).toBuffer(function(err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); + sharp(fixtures.inputWebp) + .resize(width, height) + .interpolateWith(sharp.interpolator.bilinear) + .toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); } }).on('cycle', function(event) { console.log('webp ' + String(event.target)); diff --git a/test/fixtures/expected/extract-resize.jpg b/test/fixtures/expected/extract-resize.jpg index 7dd8828a..d4e6d5cd 100644 Binary files a/test/fixtures/expected/extract-resize.jpg and b/test/fixtures/expected/extract-resize.jpg differ diff --git a/test/fixtures/expected/gamma-0.0.jpg b/test/fixtures/expected/gamma-0.0.jpg index 59daf1ac..1734a5cc 100644 Binary files a/test/fixtures/expected/gamma-0.0.jpg and b/test/fixtures/expected/gamma-0.0.jpg differ diff --git a/test/interpolators/README.md b/test/interpolators/README.md new file mode 100644 index 00000000..add6d84a --- /dev/null +++ b/test/interpolators/README.md @@ -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 +``` diff --git a/test/interpolators/bicubic.jpg b/test/interpolators/bicubic.jpg new file mode 100644 index 00000000..95c211c4 Binary files /dev/null and b/test/interpolators/bicubic.jpg differ diff --git a/test/interpolators/bilinear.jpg b/test/interpolators/bilinear.jpg new file mode 100644 index 00000000..880bdbe5 Binary files /dev/null and b/test/interpolators/bilinear.jpg differ diff --git a/test/interpolators/generate.js b/test/interpolators/generate.js new file mode 100644 index 00000000..2ff0e917 --- /dev/null +++ b/test/interpolators/generate.js @@ -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; + }); +}); diff --git a/test/interpolators/gm.jpg b/test/interpolators/gm.jpg new file mode 100644 index 00000000..9bc33c44 Binary files /dev/null and b/test/interpolators/gm.jpg differ diff --git a/test/interpolators/lbb.jpg b/test/interpolators/lbb.jpg new file mode 100644 index 00000000..85582658 Binary files /dev/null and b/test/interpolators/lbb.jpg differ diff --git a/test/interpolators/nearest.jpg b/test/interpolators/nearest.jpg new file mode 100644 index 00000000..844f6c7b Binary files /dev/null and b/test/interpolators/nearest.jpg differ diff --git a/test/interpolators/nohalo.jpg b/test/interpolators/nohalo.jpg new file mode 100644 index 00000000..11247428 Binary files /dev/null and b/test/interpolators/nohalo.jpg differ diff --git a/test/interpolators/vsqbs.jpg b/test/interpolators/vsqbs.jpg new file mode 100644 index 00000000..12908e04 Binary files /dev/null and b/test/interpolators/vsqbs.jpg differ diff --git a/test/unit/gamma.js b/test/unit/gamma.js old mode 100755 new mode 100644 index 5cf36d52..b109e28e --- a/test/unit/gamma.js +++ b/test/unit/gamma.js @@ -16,7 +16,7 @@ describe('Gamma correction', function() { assert.strictEqual('jpeg', info.format); assert.strictEqual(129, info.width); 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); }); });