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
|
||||
|
||||
- [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
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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)",
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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);
|
||||
|
@ -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),
|
||||
|
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'),
|
||||
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
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