From 85f20c6e1b337db88d71adb7533f982ec7fcaba4 Mon Sep 17 00:00:00 2001 From: Matt Hirsch Date: Sun, 3 Jul 2016 14:32:07 -0400 Subject: [PATCH] Add greyscale option to threshold operation (#480) --- docs/api.md | 4 ++- index.js | 11 +++++++- src/operations.cc | 7 +++++ src/operations.h | 5 ++++ src/pipeline.cc | 4 ++- src/pipeline.h | 2 ++ .../fixtures/expected/threshold-color-128.jpg | Bin 0 -> 22502 bytes test/unit/threshold.js | 25 ++++++++++++++++++ 8 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 test/fixtures/expected/threshold-color-128.jpg diff --git a/docs/api.md b/docs/api.md index 2249c213..f4bd9915 100644 --- a/docs/api.md +++ b/docs/api.md @@ -391,12 +391,14 @@ When a `sigma` is provided, performs a slower, more accurate sharpen of the L ch * `flat`, if present, is a Number representing the level of sharpening to apply to "flat" areas, defaulting to a value of 1.0. * `jagged`, if present, is a Number representing the level of sharpening to apply to "jagged" areas, defaulting to a value of 2.0. -#### threshold([threshold]) +#### threshold([threshold], [options]) Converts all pixels in the image to greyscale white or black. Any pixel greather-than-or-equal-to the threshold (0..255) will be white. All others will be black. * `threshold`, if present, is a Number, representing the level above which pixels will be forced to white. +* `options`, an options object that may contain a boolean `grayscale` or `greyscale`. When `grayscale` is `true`, `threshold` returns a black and white image, and when `false` a color image with each channel thresholded independently. The default is `grayscale: true` when omitted. + #### gamma([gamma]) Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of `1/gamma` then increasing the encoding (brighten) post-resize at a factor of `gamma`. diff --git a/index.js b/index.js index bd3f9555..a19c9475 100644 --- a/index.js +++ b/index.js @@ -85,6 +85,7 @@ var Sharp = function(input, options) { sharpenFlat: 1, sharpenJagged: 2, threshold: 0, + thresholdGrayscale: true, gamma: 0, greyscale: false, normalize: 0, @@ -487,7 +488,7 @@ Sharp.prototype.sharpen = function(sigma, flat, jagged) { return this; }; -Sharp.prototype.threshold = function(threshold) { +Sharp.prototype.threshold = function(threshold, options) { if (typeof threshold === 'undefined') { this.options.threshold = 128; } else if (typeof threshold === 'boolean') { @@ -497,6 +498,14 @@ Sharp.prototype.threshold = function(threshold) { } else { throw new Error('Invalid threshold (0 to 255) ' + threshold); } + + if(typeof options === 'undefined' || + options.greyscale === true || options.grayscale === true) { + this.options.thresholdGrayscale = true; + } else { + this.options.thresholdGrayscale = false; + } + return this; }; diff --git a/src/operations.cc b/src/operations.cc index 8e146a5f..2cae8a88 100644 --- a/src/operations.cc +++ b/src/operations.cc @@ -327,4 +327,11 @@ namespace sharp { ); } + VImage Threshold(VImage image, double const threshold, bool const thresholdGrayscale) { + if(!thresholdGrayscale) { + return image >= threshold; + } + return image.colourspace(VIPS_INTERPRETATION_B_W) >= threshold; + } + } // namespace sharp diff --git a/src/operations.h b/src/operations.h index fa10b7d2..044807b4 100644 --- a/src/operations.h +++ b/src/operations.h @@ -54,6 +54,11 @@ namespace sharp { */ VImage TileCache(VImage image, double const factor); + /* + Threshold an image + */ + VImage Threshold(VImage image, double const threshold, bool const thresholdColor); + } // namespace sharp #endif // SRC_OPERATIONS_H_ diff --git a/src/pipeline.cc b/src/pipeline.cc index 07464540..85662f1e 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -52,6 +52,7 @@ using sharp::Blur; using sharp::Sharpen; using sharp::EntropyCrop; using sharp::TileCache; +using sharp::Threshold; using sharp::ImageType; using sharp::ImageTypeId; @@ -625,7 +626,7 @@ class PipelineWorker : public AsyncWorker { // Threshold - must happen before blurring, due to the utility of blurring after thresholding if (shouldThreshold) { - image = image.colourspace(VIPS_INTERPRETATION_B_W) >= baton->threshold; + image = Threshold(image, baton->threshold, baton->thresholdGrayscale); } // Blur @@ -1107,6 +1108,7 @@ NAN_METHOD(pipeline) { baton->sharpenFlat = attrAs(options, "sharpenFlat"); baton->sharpenJagged = attrAs(options, "sharpenJagged"); baton->threshold = attrAs(options, "threshold"); + baton->thresholdGrayscale = attrAs(options, "thresholdGrayscale"); baton->gamma = attrAs(options, "gamma"); baton->greyscale = attrAs(options, "greyscale"); baton->normalize = attrAs(options, "normalize"); diff --git a/src/pipeline.h b/src/pipeline.h index 5a6b9a64..8581eedb 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -58,6 +58,7 @@ struct PipelineBaton { double sharpenFlat; double sharpenJagged; int threshold; + bool thresholdGrayscale; double gamma; bool greyscale; bool normalize; @@ -113,6 +114,7 @@ struct PipelineBaton { sharpenFlat(1.0), sharpenJagged(2.0), threshold(0), + thresholdGrayscale(true), gamma(0.0), greyscale(false), normalize(false), diff --git a/test/fixtures/expected/threshold-color-128.jpg b/test/fixtures/expected/threshold-color-128.jpg new file mode 100644 index 0000000000000000000000000000000000000000..54703d2e283c78a5a6d69011fa98c648ebd7bb13 GIT binary patch literal 22502 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