mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 10:30:15 +02:00
Add tint operation to set image chroma
This commit is contained in:
parent
bdac5b5807
commit
dbac4b9a63
@ -3,10 +3,11 @@
|
|||||||
### Table of Contents
|
### Table of Contents
|
||||||
|
|
||||||
- [background][1]
|
- [background][1]
|
||||||
- [greyscale][2]
|
- [tint][2]
|
||||||
- [grayscale][3]
|
- [greyscale][3]
|
||||||
- [toColourspace][4]
|
- [grayscale][4]
|
||||||
- [toColorspace][5]
|
- [toColourspace][5]
|
||||||
|
- [toColorspace][6]
|
||||||
|
|
||||||
## background
|
## background
|
||||||
|
|
||||||
@ -19,10 +20,24 @@ The alpha value is a float between `0` (transparent) and `1` (opaque).
|
|||||||
|
|
||||||
**Parameters**
|
**Parameters**
|
||||||
|
|
||||||
- `rgba` **([String][6] \| [Object][7])** parsed by the [color][8] module to extract values for red, green, blue and alpha.
|
- `rgba` **([String][7] \| [Object][8])** parsed by the [color][9] module to extract values for red, green, blue and alpha.
|
||||||
|
|
||||||
|
|
||||||
- Throws **[Error][9]** Invalid parameter
|
- Throws **[Error][10]** Invalid parameter
|
||||||
|
|
||||||
|
Returns **Sharp**
|
||||||
|
|
||||||
|
## tint
|
||||||
|
|
||||||
|
Tint the image using the provided chroma while preserving the image luminance.
|
||||||
|
An alpha channel may be present and will be unchanged by the operation.
|
||||||
|
|
||||||
|
**Parameters**
|
||||||
|
|
||||||
|
- `rgb` **([String][7] \| [Object][8])** parsed by the [color][9] module to extract chroma values.
|
||||||
|
|
||||||
|
|
||||||
|
- Throws **[Error][10]** Invalid parameter
|
||||||
|
|
||||||
Returns **Sharp**
|
Returns **Sharp**
|
||||||
|
|
||||||
@ -37,7 +52,7 @@ An alpha channel may be present, and will be unchanged by the operation.
|
|||||||
|
|
||||||
**Parameters**
|
**Parameters**
|
||||||
|
|
||||||
- `greyscale` **[Boolean][10]** (optional, default `true`)
|
- `greyscale` **[Boolean][11]** (optional, default `true`)
|
||||||
|
|
||||||
Returns **Sharp**
|
Returns **Sharp**
|
||||||
|
|
||||||
@ -47,7 +62,7 @@ Alternative spelling of `greyscale`.
|
|||||||
|
|
||||||
**Parameters**
|
**Parameters**
|
||||||
|
|
||||||
- `grayscale` **[Boolean][10]** (optional, default `true`)
|
- `grayscale` **[Boolean][11]** (optional, default `true`)
|
||||||
|
|
||||||
Returns **Sharp**
|
Returns **Sharp**
|
||||||
|
|
||||||
@ -58,10 +73,10 @@ By default output image will be web-friendly sRGB, with additional channels inte
|
|||||||
|
|
||||||
**Parameters**
|
**Parameters**
|
||||||
|
|
||||||
- `colourspace` **[String][6]?** output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...][11]
|
- `colourspace` **[String][7]?** output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...][12]
|
||||||
|
|
||||||
|
|
||||||
- Throws **[Error][9]** Invalid parameters
|
- Throws **[Error][10]** Invalid parameters
|
||||||
|
|
||||||
Returns **Sharp**
|
Returns **Sharp**
|
||||||
|
|
||||||
@ -71,31 +86,33 @@ Alternative spelling of `toColourspace`.
|
|||||||
|
|
||||||
**Parameters**
|
**Parameters**
|
||||||
|
|
||||||
- `colorspace` **[String][6]?** output colorspace.
|
- `colorspace` **[String][7]?** output colorspace.
|
||||||
|
|
||||||
|
|
||||||
- Throws **[Error][9]** Invalid parameters
|
- Throws **[Error][10]** Invalid parameters
|
||||||
|
|
||||||
Returns **Sharp**
|
Returns **Sharp**
|
||||||
|
|
||||||
[1]: #background
|
[1]: #background
|
||||||
|
|
||||||
[2]: #greyscale
|
[2]: #tint
|
||||||
|
|
||||||
[3]: #grayscale
|
[3]: #greyscale
|
||||||
|
|
||||||
[4]: #tocolourspace
|
[4]: #grayscale
|
||||||
|
|
||||||
[5]: #tocolorspace
|
[5]: #tocolourspace
|
||||||
|
|
||||||
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
|
[6]: #tocolorspace
|
||||||
|
|
||||||
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
|
||||||
|
|
||||||
[8]: https://www.npmjs.org/package/color
|
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||||
|
|
||||||
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
|
[9]: https://www.npmjs.org/package/color
|
||||||
|
|
||||||
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
|
||||||
|
|
||||||
[11]: https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568
|
[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||||
|
|
||||||
|
[12]: https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568
|
||||||
|
@ -4,6 +4,12 @@
|
|||||||
|
|
||||||
Requires libvips v8.6.1.
|
Requires libvips v8.6.1.
|
||||||
|
|
||||||
|
#### v0.20.2 - TBD
|
||||||
|
|
||||||
|
* Add tint operation to set image chroma.
|
||||||
|
[#825](https://github.com/lovell/sharp/pull/825)
|
||||||
|
[@rikh42](https://github.com/rikh42)
|
||||||
|
|
||||||
#### v0.20.1 - 17<sup>th</sup> March 2018
|
#### v0.20.1 - 17<sup>th</sup> March 2018
|
||||||
|
|
||||||
* Improve installation experience when a globally-installed libvips below the minimum required version is found.
|
* Improve installation experience when a globally-installed libvips below the minimum required version is found.
|
||||||
|
@ -38,6 +38,21 @@ function background (rgba) {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tint the image using the provided chroma while preserving the image luminance.
|
||||||
|
* An alpha channel may be present and will be unchanged by the operation.
|
||||||
|
*
|
||||||
|
* @param {String|Object} rgb - parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values.
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameter
|
||||||
|
*/
|
||||||
|
function tint (rgb) {
|
||||||
|
const colour = color(rgb);
|
||||||
|
this.options.tintA = colour.a();
|
||||||
|
this.options.tintB = colour.b();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert to 8-bit greyscale; 256 shades of grey.
|
* Convert to 8-bit greyscale; 256 shades of grey.
|
||||||
* This is a linear operation. If the input image is in a non-linear colour space such as sRGB, use `gamma()` with `greyscale()` for the best results.
|
* This is a linear operation. If the input image is in a non-linear colour space such as sRGB, use `gamma()` with `greyscale()` for the best results.
|
||||||
@ -95,6 +110,7 @@ module.exports = function (Sharp) {
|
|||||||
// Public instance functions
|
// Public instance functions
|
||||||
[
|
[
|
||||||
background,
|
background,
|
||||||
|
tint,
|
||||||
greyscale,
|
greyscale,
|
||||||
grayscale,
|
grayscale,
|
||||||
toColourspace,
|
toColourspace,
|
||||||
|
@ -151,6 +151,8 @@ const Sharp = function (input, options) {
|
|||||||
fastShrinkOnLoad: true,
|
fastShrinkOnLoad: true,
|
||||||
// operations
|
// operations
|
||||||
background: [0, 0, 0, 255],
|
background: [0, 0, 0, 255],
|
||||||
|
tintA: 0,
|
||||||
|
tintB: 0,
|
||||||
flatten: false,
|
flatten: false,
|
||||||
negate: false,
|
negate: false,
|
||||||
medianSize: 0,
|
medianSize: 0,
|
||||||
|
@ -45,7 +45,8 @@
|
|||||||
"Kenric D'Souza <kenric.dsouza@gmail.com>",
|
"Kenric D'Souza <kenric.dsouza@gmail.com>",
|
||||||
"Oleh Aleinyk <oleg.aleynik@gmail.com>",
|
"Oleh Aleinyk <oleg.aleynik@gmail.com>",
|
||||||
"Marcel Bretschneider <marcel.bretschneider@gmail.com>",
|
"Marcel Bretschneider <marcel.bretschneider@gmail.com>",
|
||||||
"Andrea Bianco <andrea.bianco@unibas.ch>"
|
"Andrea Bianco <andrea.bianco@unibas.ch>",
|
||||||
|
"Rik Heywood <rik@rik.org>"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
|
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
|
||||||
|
@ -152,6 +152,32 @@ namespace sharp {
|
|||||||
return dst.bandjoin(mask.cast(dst.format()));
|
return dst.bandjoin(mask.cast(dst.format()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tint an image using the specified chroma, preserving the original image luminance
|
||||||
|
*/
|
||||||
|
VImage Tint(VImage image, double const a, double const b) {
|
||||||
|
// Get original colourspace
|
||||||
|
VipsInterpretation typeBeforeTint = image.interpretation();
|
||||||
|
if (typeBeforeTint == VIPS_INTERPRETATION_RGB) {
|
||||||
|
typeBeforeTint = VIPS_INTERPRETATION_sRGB;
|
||||||
|
}
|
||||||
|
// Create 2 band image with every pixel set to the tint chroma
|
||||||
|
std::vector<double> chromaPixel {a, b};
|
||||||
|
VImage chroma = image.new_from_image(chromaPixel);
|
||||||
|
// Extract luminance
|
||||||
|
VImage luminance = image.colourspace(VIPS_INTERPRETATION_LAB)[0];
|
||||||
|
// Create the tinted version by combining the L from the original and the chroma from the tint
|
||||||
|
VImage tinted = luminance.bandjoin(chroma).colourspace(typeBeforeTint);
|
||||||
|
// Attach original alpha channel, if any
|
||||||
|
if (HasAlpha(image)) {
|
||||||
|
// Extract original alpha channel
|
||||||
|
VImage alpha = image[image.bands() - 1];
|
||||||
|
// Join alpha channel to normalised image
|
||||||
|
tinted = tinted.bandjoin(alpha);
|
||||||
|
}
|
||||||
|
return tinted;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Stretch luminance to cover full dynamic range.
|
* Stretch luminance to cover full dynamic range.
|
||||||
*/
|
*/
|
||||||
|
@ -46,6 +46,11 @@ namespace sharp {
|
|||||||
*/
|
*/
|
||||||
VImage Cutout(VImage src, VImage dst, const int gravity);
|
VImage Cutout(VImage src, VImage dst, const int gravity);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tint an image using the specified chroma, preserving the original image luminance
|
||||||
|
*/
|
||||||
|
VImage Tint(VImage image, double const a, double const b);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Stretch luminance to cover full dynamic range.
|
* Stretch luminance to cover full dynamic range.
|
||||||
*/
|
*/
|
||||||
|
@ -682,6 +682,11 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|||||||
image = sharp::Bandbool(image, baton->bandBoolOp);
|
image = sharp::Bandbool(image, baton->bandBoolOp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tint the image
|
||||||
|
if (baton->tintA > 0 || baton->tintB > 0) {
|
||||||
|
image = sharp::Tint(image, baton->tintA, baton->tintB);
|
||||||
|
}
|
||||||
|
|
||||||
// Extract an image channel (aka vips band)
|
// Extract an image channel (aka vips band)
|
||||||
if (baton->extractChannel > -1) {
|
if (baton->extractChannel > -1) {
|
||||||
if (baton->extractChannel >= image.bands()) {
|
if (baton->extractChannel >= image.bands()) {
|
||||||
@ -1167,6 +1172,9 @@ NAN_METHOD(pipeline) {
|
|||||||
for (unsigned int i = 0; i < 4; i++) {
|
for (unsigned int i = 0; i < 4; i++) {
|
||||||
baton->background[i] = AttrTo<double>(background, i);
|
baton->background[i] = AttrTo<double>(background, i);
|
||||||
}
|
}
|
||||||
|
// Tint chroma
|
||||||
|
baton->tintA = AttrTo<double>(options, "tintA");
|
||||||
|
baton->tintB = AttrTo<double>(options, "tintB");
|
||||||
// Overlay options
|
// Overlay options
|
||||||
if (HasAttr(options, "overlay")) {
|
if (HasAttr(options, "overlay")) {
|
||||||
baton->overlay = CreateInputDescriptor(AttrAs<v8::Object>(options, "overlay"), buffersToPersist);
|
baton->overlay = CreateInputDescriptor(AttrAs<v8::Object>(options, "overlay"), buffersToPersist);
|
||||||
|
@ -70,6 +70,8 @@ struct PipelineBaton {
|
|||||||
std::string kernel;
|
std::string kernel;
|
||||||
bool fastShrinkOnLoad;
|
bool fastShrinkOnLoad;
|
||||||
double background[4];
|
double background[4];
|
||||||
|
double tintA;
|
||||||
|
double tintB;
|
||||||
bool flatten;
|
bool flatten;
|
||||||
bool negate;
|
bool negate;
|
||||||
double blurSigma;
|
double blurSigma;
|
||||||
@ -155,6 +157,8 @@ struct PipelineBaton {
|
|||||||
cropOffsetLeft(0),
|
cropOffsetLeft(0),
|
||||||
cropOffsetTop(0),
|
cropOffsetTop(0),
|
||||||
premultiplied(false),
|
premultiplied(false),
|
||||||
|
tintA(0.0),
|
||||||
|
tintB(0.0),
|
||||||
flatten(false),
|
flatten(false),
|
||||||
negate(false),
|
negate(false),
|
||||||
blurSigma(0.0),
|
blurSigma(0.0),
|
||||||
|
BIN
test/fixtures/2569067123_aca715a2ee_o.png
vendored
Normal file
BIN
test/fixtures/2569067123_aca715a2ee_o.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.8 MiB |
BIN
test/fixtures/expected/tint-alpha.png
vendored
Normal file
BIN
test/fixtures/expected/tint-alpha.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 142 KiB |
BIN
test/fixtures/expected/tint-red.jpg
vendored
Normal file
BIN
test/fixtures/expected/tint-red.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
test/fixtures/expected/tint-sepia.jpg
vendored
Normal file
BIN
test/fixtures/expected/tint-sepia.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
1
test/fixtures/index.js
vendored
1
test/fixtures/index.js
vendored
@ -89,6 +89,7 @@ module.exports = {
|
|||||||
inputPngTestJoinChannel: getPath('testJoinChannel.png'),
|
inputPngTestJoinChannel: getPath('testJoinChannel.png'),
|
||||||
inputPngTruncated: getPath('truncated.png'), // gm convert 2569067123_aca715a2ee_o.jpg -resize 320x240 saw.png ; head -c 10000 saw.png > truncated.png
|
inputPngTruncated: getPath('truncated.png'), // gm convert 2569067123_aca715a2ee_o.jpg -resize 320x240 saw.png ; head -c 10000 saw.png > truncated.png
|
||||||
inputPngEmbed: getPath('embedgravitybird.png'), // Released to sharp under a CC BY 4.0
|
inputPngEmbed: getPath('embedgravitybird.png'), // Released to sharp under a CC BY 4.0
|
||||||
|
inputPngRGBWithAlpha: getPath('2569067123_aca715a2ee_o.png'), // http://www.flickr.com/photos/grizdave/2569067123/ (same as inputJpg)
|
||||||
|
|
||||||
inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp
|
inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp
|
||||||
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
|
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
|
||||||
|
63
test/unit/tint.js
Normal file
63
test/unit/tint.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
const sharp = require('../../');
|
||||||
|
const fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
describe('Tint', function () {
|
||||||
|
it('tints rgb image red', function (done) {
|
||||||
|
const output = fixtures.path('output.tint-red.jpg');
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.tint('#FF0000')
|
||||||
|
.toFile(output, function (err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, info.size > 0);
|
||||||
|
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-red.jpg'), 10);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tints rgb image with sepia tone', function (done) {
|
||||||
|
const output = fixtures.path('output.tint-sepia.jpg');
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.tint('#704214')
|
||||||
|
.toFile(output, function (err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-sepia.jpg'), 10);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tints rgb image with sepia tone with rgb colour', function (done) {
|
||||||
|
const output = fixtures.path('output.tint-sepia.jpg');
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.tint([112, 66, 20])
|
||||||
|
.toFile(output, function (err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-sepia.jpg'), 10);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tints rgb image with alpha channel', function (done) {
|
||||||
|
const output = fixtures.path('output.tint-alpha.png');
|
||||||
|
sharp(fixtures.inputPngRGBWithAlpha)
|
||||||
|
.resize(320, 240)
|
||||||
|
.tint('#704214')
|
||||||
|
.toFile(output, function (err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-alpha.png'), 10);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user