Add tint operation to set image chroma

This commit is contained in:
Rik Heywood 2018-04-11 20:05:48 +01:00 committed by Lovell Fuller
parent bdac5b5807
commit dbac4b9a63
15 changed files with 172 additions and 23 deletions

View File

@ -3,10 +3,11 @@
### Table of Contents
- [background][1]
- [greyscale][2]
- [grayscale][3]
- [toColourspace][4]
- [toColorspace][5]
- [tint][2]
- [greyscale][3]
- [grayscale][4]
- [toColourspace][5]
- [toColorspace][6]
## background
@ -19,10 +20,24 @@ The alpha value is a float between `0` (transparent) and `1` (opaque).
**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**
@ -37,7 +52,7 @@ An alpha channel may be present, and will be unchanged by the operation.
**Parameters**
- `greyscale` **[Boolean][10]** (optional, default `true`)
- `greyscale` **[Boolean][11]** (optional, default `true`)
Returns **Sharp**
@ -47,7 +62,7 @@ Alternative spelling of `greyscale`.
**Parameters**
- `grayscale` **[Boolean][10]** (optional, default `true`)
- `grayscale` **[Boolean][11]** (optional, default `true`)
Returns **Sharp**
@ -58,10 +73,10 @@ By default output image will be web-friendly sRGB, with additional channels inte
**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**
@ -71,31 +86,33 @@ Alternative spelling of `toColourspace`.
**Parameters**
- `colorspace` **[String][6]?** output colorspace.
- `colorspace` **[String][7]?** output colorspace.
- Throws **[Error][9]** Invalid parameters
- Throws **[Error][10]** Invalid parameters
Returns **Sharp**
[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

View File

@ -4,6 +4,12 @@
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
* Improve installation experience when a globally-installed libvips below the minimum required version is found.

View File

@ -38,6 +38,21 @@ function background (rgba) {
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.
* 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
[
background,
tint,
greyscale,
grayscale,
toColourspace,

View File

@ -151,6 +151,8 @@ const Sharp = function (input, options) {
fastShrinkOnLoad: true,
// operations
background: [0, 0, 0, 255],
tintA: 0,
tintB: 0,
flatten: false,
negate: false,
medianSize: 0,

View File

@ -45,7 +45,8 @@
"Kenric D'Souza <kenric.dsouza@gmail.com>",
"Oleh Aleinyk <oleg.aleynik@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": {
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",

View File

@ -152,6 +152,32 @@ namespace sharp {
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.
*/

View File

@ -46,6 +46,11 @@ namespace sharp {
*/
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.
*/

View File

@ -682,6 +682,11 @@ class PipelineWorker : public Nan::AsyncWorker {
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)
if (baton->extractChannel > -1) {
if (baton->extractChannel >= image.bands()) {
@ -1167,6 +1172,9 @@ NAN_METHOD(pipeline) {
for (unsigned int i = 0; i < 4; i++) {
baton->background[i] = AttrTo<double>(background, i);
}
// Tint chroma
baton->tintA = AttrTo<double>(options, "tintA");
baton->tintB = AttrTo<double>(options, "tintB");
// Overlay options
if (HasAttr(options, "overlay")) {
baton->overlay = CreateInputDescriptor(AttrAs<v8::Object>(options, "overlay"), buffersToPersist);

View File

@ -70,6 +70,8 @@ struct PipelineBaton {
std::string kernel;
bool fastShrinkOnLoad;
double background[4];
double tintA;
double tintB;
bool flatten;
bool negate;
double blurSigma;
@ -155,6 +157,8 @@ struct PipelineBaton {
cropOffsetLeft(0),
cropOffsetTop(0),
premultiplied(false),
tintA(0.0),
tintB(0.0),
flatten(false),
negate(false),
blurSigma(0.0),

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 MiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
test/fixtures/expected/tint-sepia.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -89,6 +89,7 @@ module.exports = {
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
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
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp

63
test/unit/tint.js Normal file
View 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();
});
});
});