Skip to content

Commit

Permalink
Implement brightness, saturation and hue modulation lovell#609
Browse files Browse the repository at this point in the history
  • Loading branch information
Goues committed Mar 5, 2019
1 parent 5afe02b commit 246abdd
Show file tree
Hide file tree
Showing 13 changed files with 108 additions and 1 deletion.
3 changes: 3 additions & 0 deletions lib/constructor.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ const Sharp = function (input, options) {
gammaOut: 0,
greyscale: false,
normalise: 0,
brightness: 1,
saturation: 1,
hue: 0,
booleanBufferIn: null,
booleanFileIn: '',
joinChannelIn: [],
Expand Down
16 changes: 15 additions & 1 deletion lib/operation.js
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,19 @@ function recomb (inputMatrix) {
return this;
}

function modulate (input) {
if (is.number(input.brightness) && input.brightness >= 0) {
this.options.brightness = input.brightness;
}
if (is.number(input.saturation) && input.saturation >= 0) {
this.options.saturation = input.saturation;
}
if (is.number(input.hue)) {
this.options.hue = input.hue % 360;
}
return this;
}

/**
* Decorate the Sharp prototype with operation-related functions.
* @private
Expand All @@ -436,6 +449,7 @@ module.exports = function (Sharp) {
threshold,
boolean,
linear,
recomb
recomb,
modulate
});
};
6 changes: 6 additions & 0 deletions src/operations.cc
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,12 @@ namespace sharp {
0.0, 0.0, 0.0, 1.0));
}

VImage Modulate(VImage image, double const brightness, double const saturation, double const hue) {
return image
.colourspace(VIPS_INTERPRETATION_LCH)
.linear({brightness, saturation, 1}, {0, 0, hue});
}

/*
* Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
*/
Expand Down
5 changes: 5 additions & 0 deletions src/operations.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ namespace sharp {
*/
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix);

/*
* Modulate brightness, saturation and hue
*/
VImage Modulate(VImage image, double const brightness, double const saturation, double const hue);

} // namespace sharp

#endif // SRC_OPERATIONS_H_
8 changes: 8 additions & 0 deletions src/pipeline.cc
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ class PipelineWorker : public Nan::AsyncWorker {
bool const shouldConv = baton->convKernelWidth * baton->convKernelHeight > 0;
bool const shouldSharpen = baton->sharpenSigma != 0.0;
bool const shouldApplyMedian = baton->medianSize > 0;
bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0;

bool const shouldPremultiplyAlpha = HasAlpha(image) &&
(shouldResize || shouldBlur || shouldConv || shouldSharpen || shouldOverlayWithAlpha);
Expand Down Expand Up @@ -539,6 +540,10 @@ class PipelineWorker : public Nan::AsyncWorker {
image = sharp::Recomb(image, baton->recombMatrix);
}

if (shouldModulate) {
image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue);
}

// Sharpen
if (shouldSharpen) {
image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenFlat, baton->sharpenJagged);
Expand Down Expand Up @@ -1215,6 +1220,9 @@ NAN_METHOD(pipeline) {
baton->flattenBackground = AttrAsRgba(options, "flattenBackground");
baton->negate = AttrTo<bool>(options, "negate");
baton->blurSigma = AttrTo<double>(options, "blurSigma");
baton->brightness = AttrTo<double>(options, "brightness");
baton->saturation = AttrTo<double>(options, "saturation");
baton->hue = AttrTo<double>(options, "hue");
baton->medianSize = AttrTo<uint32_t>(options, "medianSize");
baton->sharpenSigma = AttrTo<double>(options, "sharpenSigma");
baton->sharpenFlat = AttrTo<double>(options, "sharpenFlat");
Expand Down
6 changes: 6 additions & 0 deletions src/pipeline.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ struct PipelineBaton {
std::vector<double> flattenBackground;
bool negate;
double blurSigma;
double brightness;
double saturation;
double hue;
int medianSize;
double sharpenSigma;
double sharpenFlat;
Expand Down Expand Up @@ -183,6 +186,9 @@ struct PipelineBaton {
flattenBackground{ 0.0, 0.0, 0.0 },
negate(false),
blurSigma(0.0),
brightness(1.0),
saturation(1.0),
hue(0.0),
medianSize(0),
sharpenSigma(0.0),
sharpenFlat(1.0),
Expand Down
Binary file added test/fixtures/expected/modulate-all.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/fixtures/expected/modulate-hue-120.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
65 changes: 65 additions & 0 deletions test/unit/modulate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
'use strict';

const sharp = require('../../');
const fixtures = require('../fixtures');

describe.only('Modulate', function () {
it('should be able to hue-rotate', function (done) {
sharp(fixtures.inputJpg)
.modulate({ hue: 120 })
.toBuffer(function (err, data, info) {
if (err) throw err;
fixtures.assertSimilar(fixtures.expected('modulate-hue-120.jpg'), data, { threshold: 1 }, done);
});
});

it('should be able to brighten', function (done) {
sharp(fixtures.inputJpg)
.modulate({ brightness: 2 })
.toBuffer(function (err, data, info) {
if (err) throw err;
require('fs').writeFileSync('./modulate-brightness-2.jpg.jpg', data);
fixtures.assertSimilar(fixtures.expected('modulate-brightness-2.jpg'), data, { threshold: 1 }, done);
});
});

it('should be able to unbrighten', function (done) {
sharp(fixtures.inputJpg)
.modulate({ brightness: 0.5 })
.toBuffer(function (err, data, info) {
if (err) throw err;
require('fs').writeFileSync('./modulate-brightness-0-5.jpg', data);
fixtures.assertSimilar(fixtures.expected('modulate-brightness-0-5.jpg'), data, { threshold: 1 }, done);
});
});

it('should be able to saturate', function (done) {
sharp(fixtures.inputJpg)
.modulate({ saturation: 2 })
.toBuffer(function (err, data, info) {
if (err) throw err;
require('fs').writeFileSync('./modulate-saturation-2.jpg.jpg', data);
fixtures.assertSimilar(fixtures.expected('modulate-saturation-2.jpg'), data, { threshold: 1 }, done);
});
});

it('should be able to desaturate', function (done) {
sharp(fixtures.inputJpg)
.modulate({ saturation: 0.5 })
.toBuffer(function (err, data, info) {
if (err) throw err;
require('fs').writeFileSync('./modulate-saturation-0-5.jpg', data);
fixtures.assertSimilar(fixtures.expected('modulate-saturation-0-5.jpg'), data, { threshold: 1 }, done);
});
});

it('should be able to modulate all channels', function (done) {
sharp(fixtures.inputJpg)
.modulate({ brightness: 2, saturation: 0.5, hue: 180 })
.toBuffer(function (err, data, info) {
if (err) throw err;
require('fs').writeFileSync('./modulate-all.jpg', data);
fixtures.assertSimilar(fixtures.expected('modulate-all.jpg'), data, { threshold: 1 }, done);
});
});
});

0 comments on commit 246abdd

Please sign in to comment.