Allow ensureAlpha to set alpha transparency level #2634

This commit is contained in:
Lovell Fuller 2021-04-01 21:14:06 +01:00
parent fe0767df13
commit 8c33d0aa56
10 changed files with 91 additions and 32 deletions

View File

@ -18,18 +18,33 @@ Returns **Sharp**
## ensureAlpha ## ensureAlpha
Ensure alpha channel, if missing. The added alpha channel will be fully opaque. This is a no-op if the image already has an alpha channel. Ensure the output image has an alpha transparency channel.
If missing, the added alpha channel will have the specified
transparency level, defaulting to fully-opaque (1).
This is a no-op if the image already has an alpha channel.
### Parameters
- `alpha` **[number][1]** alpha transparency level (0=fully-transparent, 1=fully-opaque) (optional, default `1`)
### Examples ### Examples
```javascript ```javascript
sharp('rgb.jpg') // rgba.png will be a 4 channel image with a fully-opaque alpha channel
await sharp('rgb.jpg')
.ensureAlpha() .ensureAlpha()
.toFile('rgba.png', function(err, info) { .toFile('rgba.png')
// rgba.png is a 4 channel image with a fully opaque alpha channel
});
``` ```
```javascript
// rgba is a 4 channel image with a fully-transparent alpha channel
const rgba = await sharp(rgb)
.ensureAlpha(0)
.toBuffer();
```
- Throws **[Error][2]** Invalid alpha transparency level
Returns **Sharp** Returns **Sharp**
**Meta** **Meta**
@ -42,7 +57,7 @@ Extract a single channel from a multi-channel image.
### Parameters ### Parameters
- `channel` **([number][1] \| [string][2])** zero-indexed channel/band number to extract, or `red`, `green`, `blue` or `alpha`. - `channel` **([number][1] \| [string][3])** zero-indexed channel/band number to extract, or `red`, `green`, `blue` or `alpha`.
### Examples ### Examples
@ -56,7 +71,7 @@ sharp(input)
}); });
``` ```
- Throws **[Error][3]** Invalid channel - Throws **[Error][2]** Invalid channel
Returns **Sharp** Returns **Sharp**
@ -75,11 +90,11 @@ For raw pixel input, the `options` object should contain a `raw` attribute, whic
### Parameters ### Parameters
- `images` **([Array][4]<([string][2] \| [Buffer][5])> | [string][2] \| [Buffer][5])** one or more images (file paths, Buffers). - `images` **([Array][4]<([string][3] \| [Buffer][5])> | [string][3] \| [Buffer][5])** one or more images (file paths, Buffers).
- `options` **[Object][6]** image options, see `sharp()` constructor. - `options` **[Object][6]** image options, see `sharp()` constructor.
- Throws **[Error][3]** Invalid parameters - Throws **[Error][2]** Invalid parameters
Returns **Sharp** Returns **Sharp**
@ -89,7 +104,7 @@ Perform a bitwise boolean operation on all input image channels (bands) to produ
### Parameters ### Parameters
- `boolOp` **[string][2]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively. - `boolOp` **[string][3]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
### Examples ### Examples
@ -103,15 +118,15 @@ sharp('3-channel-rgb-input.png')
}); });
``` ```
- Throws **[Error][3]** Invalid parameters - Throws **[Error][2]** Invalid parameters
Returns **Sharp** Returns **Sharp**
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number [1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String [2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error [3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array [4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array

View File

@ -8,6 +8,9 @@ Requires libvips v8.10.6
* Ensure all installation errors are logged with a more obvious prefix. * Ensure all installation errors are logged with a more obvious prefix.
* Allow `ensureAlpha` to set the alpha transparency level.
[#2634](https://github.com/lovell/sharp/issues/2634)
### v0.28.0 - 29th March 2021 ### v0.28.0 - 29th March 2021
* Prebuilt binaries now include mozjpeg and libimagequant (BSD 2-Clause). * Prebuilt binaries now include mozjpeg and libimagequant (BSD 2-Clause).

File diff suppressed because one or more lines are too long

View File

@ -30,21 +30,39 @@ function removeAlpha () {
} }
/** /**
* Ensure alpha channel, if missing. The added alpha channel will be fully opaque. This is a no-op if the image already has an alpha channel. * Ensure the output image has an alpha transparency channel.
* If missing, the added alpha channel will have the specified
* transparency level, defaulting to fully-opaque (1).
* This is a no-op if the image already has an alpha channel.
* *
* @since 0.21.2 * @since 0.21.2
* *
* @example * @example
* sharp('rgb.jpg') * // rgba.png will be a 4 channel image with a fully-opaque alpha channel
* await sharp('rgb.jpg')
* .ensureAlpha() * .ensureAlpha()
* .toFile('rgba.png', function(err, info) { * .toFile('rgba.png')
* // rgba.png is a 4 channel image with a fully opaque alpha channel
* });
* *
* @example
* // rgba is a 4 channel image with a fully-transparent alpha channel
* const rgba = await sharp(rgb)
* .ensureAlpha(0)
* .toBuffer();
*
* @param {number} [alpha=1] - alpha transparency level (0=fully-transparent, 1=fully-opaque)
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid alpha transparency level
*/ */
function ensureAlpha () { function ensureAlpha (alpha) {
this.options.ensureAlpha = true; if (is.defined(alpha)) {
if (is.number(alpha) && is.inRange(alpha, 0, 1)) {
this.options.ensureAlpha = alpha;
} else {
throw is.invalidParameterError('alpha', 'number between 0 and 1', alpha);
}
} else {
this.options.ensureAlpha = 1;
}
return this; return this;
} }

View File

@ -221,7 +221,7 @@ const Sharp = function (input, options) {
joinChannelIn: [], joinChannelIn: [],
extractChannel: -1, extractChannel: -1,
removeAlpha: false, removeAlpha: false,
ensureAlpha: false, ensureAlpha: -1,
colourspace: 'srgb', colourspace: 'srgb',
composite: [], composite: [],
// output // output

View File

@ -799,10 +799,10 @@ namespace sharp {
/* /*
Ensures alpha channel, if missing. Ensures alpha channel, if missing.
*/ */
VImage EnsureAlpha(VImage image) { VImage EnsureAlpha(VImage image, double const value) {
if (!HasAlpha(image)) { if (!HasAlpha(image)) {
std::vector<double> alpha; std::vector<double> alpha;
alpha.push_back(sharp::MaximumImageAlpha(image.interpretation())); alpha.push_back(value * sharp::MaximumImageAlpha(image.interpretation()));
image = image.bandjoin_const(alpha); image = image.bandjoin_const(alpha);
} }
return image; return image;

View File

@ -296,7 +296,7 @@ namespace sharp {
/* /*
Ensures alpha channel, if missing. Ensures alpha channel, if missing.
*/ */
VImage EnsureAlpha(VImage image); VImage EnsureAlpha(VImage image, double const value);
} // namespace sharp } // namespace sharp

View File

@ -346,7 +346,7 @@ class PipelineWorker : public Napi::AsyncWorker {
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;
if (shouldComposite && !sharp::HasAlpha(image)) { if (shouldComposite && !sharp::HasAlpha(image)) {
image = sharp::EnsureAlpha(image); image = sharp::EnsureAlpha(image, 1);
} }
bool const shouldPremultiplyAlpha = sharp::HasAlpha(image) && bool const shouldPremultiplyAlpha = sharp::HasAlpha(image) &&
@ -594,7 +594,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// Ensure image to composite is sRGB with premultiplied alpha // Ensure image to composite is sRGB with premultiplied alpha
compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB); compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB);
if (!sharp::HasAlpha(compositeImage)) { if (!sharp::HasAlpha(compositeImage)) {
compositeImage = sharp::EnsureAlpha(compositeImage); compositeImage = sharp::EnsureAlpha(compositeImage, 1);
} }
if (!composite->premultiplied) compositeImage = compositeImage.premultiply(); if (!composite->premultiplied) compositeImage = compositeImage.premultiply();
// Calculate position // Calculate position
@ -691,8 +691,8 @@ class PipelineWorker : public Napi::AsyncWorker {
} }
// Ensure alpha channel, if missing // Ensure alpha channel, if missing
if (baton->ensureAlpha) { if (baton->ensureAlpha != -1) {
image = sharp::EnsureAlpha(image); image = sharp::EnsureAlpha(image, baton->ensureAlpha);
} }
// Convert image to sRGB, if not already // Convert image to sRGB, if not already
@ -1341,7 +1341,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->affineInterpolator = vips::VInterpolate::new_from_name(sharp::AttrAsStr(options, "affineInterpolator").data()); baton->affineInterpolator = vips::VInterpolate::new_from_name(sharp::AttrAsStr(options, "affineInterpolator").data());
baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha"); baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha");
baton->ensureAlpha = sharp::AttrAsBool(options, "ensureAlpha"); baton->ensureAlpha = sharp::AttrAsDouble(options, "ensureAlpha");
if (options.Has("boolean")) { if (options.Has("boolean")) {
baton->boolean = sharp::CreateInputDescriptor(options.Get("boolean").As<Napi::Object>()); baton->boolean = sharp::CreateInputDescriptor(options.Get("boolean").As<Napi::Object>());
baton->booleanOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "booleanOp")); baton->booleanOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "booleanOp"));

View File

@ -178,7 +178,7 @@ struct PipelineBaton {
VipsOperationBoolean bandBoolOp; VipsOperationBoolean bandBoolOp;
int extractChannel; int extractChannel;
bool removeAlpha; bool removeAlpha;
bool ensureAlpha; double ensureAlpha;
VipsInterpretation colourspace; VipsInterpretation colourspace;
int pageHeight; int pageHeight;
std::vector<int> delay; std::vector<int> delay;
@ -297,7 +297,7 @@ struct PipelineBaton {
bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST), bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST),
extractChannel(-1), extractChannel(-1),
removeAlpha(false), removeAlpha(false),
ensureAlpha(false), ensureAlpha(-1.0),
colourspace(VIPS_INTERPRETATION_LAST), colourspace(VIPS_INTERPRETATION_LAST),
pageHeight(0), pageHeight(0),
delay{-1}, delay{-1},

View File

@ -155,4 +155,27 @@ describe('Alpha transparency', function () {
}); });
})); }));
}); });
it('Valid ensureAlpha value used for alpha channel', async () => {
const background = { r: 255, g: 0, b: 0 };
const [r, g, b, alpha] = await sharp({
create: {
width: 8,
height: 8,
channels: 3,
background
}
})
.ensureAlpha(0.5)
.raw()
.toBuffer();
assert.deepStrictEqual({ r, g, b, alpha }, { ...background, alpha: 127 });
});
it('Invalid ensureAlpha value throws', async () => {
assert.throws(() => {
sharp().ensureAlpha('fail');
});
});
}); });