mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 10:30:15 +02:00
Add lightness option to modulate operation
This commit is contained in:
parent
104464c2e0
commit
4a9267ce12
@ -400,7 +400,9 @@ Returns **Sharp**
|
|||||||
|
|
||||||
## modulate
|
## modulate
|
||||||
|
|
||||||
Transforms the image using brightness, saturation and hue rotation.
|
Transforms the image using brightness, saturation, hue rotation, and lightness.
|
||||||
|
Brightness and lightness both operate on luminance, with the difference being that
|
||||||
|
brightness is multiplicative whereas lightness is additive.
|
||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|
||||||
@ -409,13 +411,14 @@ Transforms the image using brightness, saturation and hue rotation.
|
|||||||
* `options.brightness` **[number][1]?** Brightness multiplier
|
* `options.brightness` **[number][1]?** Brightness multiplier
|
||||||
* `options.saturation` **[number][1]?** Saturation multiplier
|
* `options.saturation` **[number][1]?** Saturation multiplier
|
||||||
* `options.hue` **[number][1]?** Degrees for hue rotation
|
* `options.hue` **[number][1]?** Degrees for hue rotation
|
||||||
|
* `options.lightness` **[number][1]?** Lightness addend
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
sharp(input)
|
sharp(input)
|
||||||
.modulate({
|
.modulate({
|
||||||
brightness: 2 // increase lightness by a factor of 2
|
brightness: 2 // increase brightness by a factor of 2
|
||||||
});
|
});
|
||||||
|
|
||||||
sharp(input)
|
sharp(input)
|
||||||
@ -423,6 +426,11 @@ sharp(input)
|
|||||||
hue: 180 // hue-rotate by 180 degrees
|
hue: 180 // hue-rotate by 180 degrees
|
||||||
});
|
});
|
||||||
|
|
||||||
|
sharp(input)
|
||||||
|
.modulate({
|
||||||
|
lightness: 50 // increase lightness by +50
|
||||||
|
});
|
||||||
|
|
||||||
// decreate brightness and saturation while also hue-rotating by 90 degrees
|
// decreate brightness and saturation while also hue-rotating by 90 degrees
|
||||||
sharp(input)
|
sharp(input)
|
||||||
.modulate({
|
.modulate({
|
||||||
|
File diff suppressed because one or more lines are too long
@ -199,6 +199,7 @@ const Sharp = function (input, options) {
|
|||||||
brightness: 1,
|
brightness: 1,
|
||||||
saturation: 1,
|
saturation: 1,
|
||||||
hue: 0,
|
hue: 0,
|
||||||
|
lightness: 0,
|
||||||
booleanBufferIn: null,
|
booleanBufferIn: null,
|
||||||
booleanFileIn: '',
|
booleanFileIn: '',
|
||||||
joinChannelIn: [],
|
joinChannelIn: [],
|
||||||
|
@ -570,14 +570,16 @@ function recomb (inputMatrix) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms the image using brightness, saturation and hue rotation.
|
* Transforms the image using brightness, saturation, hue rotation, and lightness.
|
||||||
|
* Brightness and lightness both operate on luminance, with the difference being that
|
||||||
|
* brightness is multiplicative whereas lightness is additive.
|
||||||
*
|
*
|
||||||
* @since 0.22.1
|
* @since 0.22.1
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* sharp(input)
|
* sharp(input)
|
||||||
* .modulate({
|
* .modulate({
|
||||||
* brightness: 2 // increase lightness by a factor of 2
|
* brightness: 2 // increase brightness by a factor of 2
|
||||||
* });
|
* });
|
||||||
*
|
*
|
||||||
* sharp(input)
|
* sharp(input)
|
||||||
@ -585,6 +587,11 @@ function recomb (inputMatrix) {
|
|||||||
* hue: 180 // hue-rotate by 180 degrees
|
* hue: 180 // hue-rotate by 180 degrees
|
||||||
* });
|
* });
|
||||||
*
|
*
|
||||||
|
* sharp(input)
|
||||||
|
* .modulate({
|
||||||
|
* lightness: 50 // increase lightness by +50
|
||||||
|
* });
|
||||||
|
*
|
||||||
* // decreate brightness and saturation while also hue-rotating by 90 degrees
|
* // decreate brightness and saturation while also hue-rotating by 90 degrees
|
||||||
* sharp(input)
|
* sharp(input)
|
||||||
* .modulate({
|
* .modulate({
|
||||||
@ -597,6 +604,7 @@ function recomb (inputMatrix) {
|
|||||||
* @param {number} [options.brightness] Brightness multiplier
|
* @param {number} [options.brightness] Brightness multiplier
|
||||||
* @param {number} [options.saturation] Saturation multiplier
|
* @param {number} [options.saturation] Saturation multiplier
|
||||||
* @param {number} [options.hue] Degrees for hue rotation
|
* @param {number} [options.hue] Degrees for hue rotation
|
||||||
|
* @param {number} [options.lightness] Lightness addend
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
*/
|
*/
|
||||||
function modulate (options) {
|
function modulate (options) {
|
||||||
@ -624,6 +632,13 @@ function modulate (options) {
|
|||||||
throw is.invalidParameterError('hue', 'number', options.hue);
|
throw is.invalidParameterError('hue', 'number', options.hue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ('lightness' in options) {
|
||||||
|
if (is.number(options.lightness)) {
|
||||||
|
this.options.lightness = options.lightness;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('lightness', 'number', options.lightness);
|
||||||
|
}
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -833,4 +833,5 @@ namespace sharp {
|
|||||||
}
|
}
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace sharp
|
} // namespace sharp
|
||||||
|
@ -182,7 +182,8 @@ namespace sharp {
|
|||||||
0.0, 0.0, 0.0, 1.0));
|
0.0, 0.0, 0.0, 1.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
VImage Modulate(VImage image, double const brightness, double const saturation, int const hue) {
|
VImage Modulate(VImage image, double const brightness, double const saturation,
|
||||||
|
int const hue, double const lightness) {
|
||||||
if (HasAlpha(image)) {
|
if (HasAlpha(image)) {
|
||||||
// Separate alpha channel
|
// Separate alpha channel
|
||||||
VImage alpha = image[image.bands() - 1];
|
VImage alpha = image[image.bands() - 1];
|
||||||
@ -190,7 +191,7 @@ namespace sharp {
|
|||||||
.colourspace(VIPS_INTERPRETATION_LCH)
|
.colourspace(VIPS_INTERPRETATION_LCH)
|
||||||
.linear(
|
.linear(
|
||||||
{ brightness, saturation, 1},
|
{ brightness, saturation, 1},
|
||||||
{ 0.0, 0.0, static_cast<double>(hue) }
|
{ lightness, 0.0, static_cast<double>(hue) }
|
||||||
)
|
)
|
||||||
.colourspace(VIPS_INTERPRETATION_sRGB)
|
.colourspace(VIPS_INTERPRETATION_sRGB)
|
||||||
.bandjoin(alpha);
|
.bandjoin(alpha);
|
||||||
@ -199,7 +200,7 @@ namespace sharp {
|
|||||||
.colourspace(VIPS_INTERPRETATION_LCH)
|
.colourspace(VIPS_INTERPRETATION_LCH)
|
||||||
.linear(
|
.linear(
|
||||||
{ brightness, saturation, 1 },
|
{ brightness, saturation, 1 },
|
||||||
{ 0.0, 0.0, static_cast<double>(hue) }
|
{ lightness, 0.0, static_cast<double>(hue) }
|
||||||
)
|
)
|
||||||
.colourspace(VIPS_INTERPRETATION_sRGB);
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
}
|
}
|
||||||
|
@ -98,9 +98,10 @@ namespace sharp {
|
|||||||
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix);
|
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Modulate brightness, saturation and hue
|
* Modulate brightness, saturation, hue and lightness
|
||||||
*/
|
*/
|
||||||
VImage Modulate(VImage image, double const brightness, double const saturation, int const hue);
|
VImage Modulate(VImage image, double const brightness, double const saturation,
|
||||||
|
int const hue, double const lightness);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Ensure the image is in a given colourspace
|
* Ensure the image is in a given colourspace
|
||||||
|
@ -347,7 +347,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
bool const shouldSharpen = baton->sharpenSigma != 0.0;
|
bool const shouldSharpen = baton->sharpenSigma != 0.0;
|
||||||
bool const shouldApplyMedian = baton->medianSize > 0;
|
bool const shouldApplyMedian = baton->medianSize > 0;
|
||||||
bool const shouldComposite = !baton->composite.empty();
|
bool const shouldComposite = !baton->composite.empty();
|
||||||
bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0;
|
bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 ||
|
||||||
|
baton->hue != 0.0 || baton->lightness != 0.0;
|
||||||
bool const shouldApplyClahe = baton->claheWidth != 0 && baton->claheHeight != 0;
|
bool const shouldApplyClahe = baton->claheWidth != 0 && baton->claheHeight != 0;
|
||||||
|
|
||||||
if (shouldComposite && !sharp::HasAlpha(image)) {
|
if (shouldComposite && !sharp::HasAlpha(image)) {
|
||||||
@ -543,7 +544,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (shouldModulate) {
|
if (shouldModulate) {
|
||||||
image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue);
|
image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue, baton->lightness);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sharpen
|
// Sharpen
|
||||||
@ -1331,6 +1332,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
baton->brightness = sharp::AttrAsDouble(options, "brightness");
|
baton->brightness = sharp::AttrAsDouble(options, "brightness");
|
||||||
baton->saturation = sharp::AttrAsDouble(options, "saturation");
|
baton->saturation = sharp::AttrAsDouble(options, "saturation");
|
||||||
baton->hue = sharp::AttrAsInt32(options, "hue");
|
baton->hue = sharp::AttrAsInt32(options, "hue");
|
||||||
|
baton->lightness = sharp::AttrAsDouble(options, "lightness");
|
||||||
baton->medianSize = sharp::AttrAsUint32(options, "medianSize");
|
baton->medianSize = sharp::AttrAsUint32(options, "medianSize");
|
||||||
baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma");
|
baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma");
|
||||||
baton->sharpenFlat = sharp::AttrAsDouble(options, "sharpenFlat");
|
baton->sharpenFlat = sharp::AttrAsDouble(options, "sharpenFlat");
|
||||||
|
@ -95,6 +95,7 @@ struct PipelineBaton {
|
|||||||
double brightness;
|
double brightness;
|
||||||
double saturation;
|
double saturation;
|
||||||
int hue;
|
int hue;
|
||||||
|
double lightness;
|
||||||
int medianSize;
|
int medianSize;
|
||||||
double sharpenSigma;
|
double sharpenSigma;
|
||||||
double sharpenFlat;
|
double sharpenFlat;
|
||||||
@ -227,6 +228,7 @@ struct PipelineBaton {
|
|||||||
brightness(1.0),
|
brightness(1.0),
|
||||||
saturation(1.0),
|
saturation(1.0),
|
||||||
hue(0),
|
hue(0),
|
||||||
|
lightness(0),
|
||||||
medianSize(0),
|
medianSize(0),
|
||||||
sharpenSigma(0.0),
|
sharpenSigma(0.0),
|
||||||
sharpenFlat(1.0),
|
sharpenFlat(1.0),
|
||||||
|
@ -18,7 +18,9 @@ describe('Modulate', function () {
|
|||||||
{ saturation: null },
|
{ saturation: null },
|
||||||
{ hue: '50deg' },
|
{ hue: '50deg' },
|
||||||
{ hue: 1.5 },
|
{ hue: 1.5 },
|
||||||
{ hue: null }
|
{ hue: null },
|
||||||
|
{ lightness: '+50' },
|
||||||
|
{ lightness: null }
|
||||||
].forEach(function (options) {
|
].forEach(function (options) {
|
||||||
it('should throw', function () {
|
it('should throw', function () {
|
||||||
assert.throws(function () {
|
assert.throws(function () {
|
||||||
@ -108,6 +110,22 @@ describe('Modulate', function () {
|
|||||||
assert.deepStrictEqual({ r: 127, g: 83, b: 81 }, { r, g, b });
|
assert.deepStrictEqual({ r: 127, g: 83, b: 81 }, { r, g, b });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be able to lighten', async () => {
|
||||||
|
const [r, g, b] = await sharp({
|
||||||
|
create: {
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
channels: 3,
|
||||||
|
background: { r: 153, g: 68, b: 68 }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.modulate({ lightness: 10 })
|
||||||
|
.raw()
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
assert.deepStrictEqual({ r: 182, g: 93, b: 92 }, { r, g, b });
|
||||||
|
});
|
||||||
|
|
||||||
it('should be able to modulate all channels', async () => {
|
it('should be able to modulate all channels', async () => {
|
||||||
const [r, g, b] = await sharp({
|
const [r, g, b] = await sharp({
|
||||||
create: {
|
create: {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user