Add ensureAlpha op, adds alpha channel if missing #1153

This commit is contained in:
Lovell Fuller 2019-01-05 21:12:33 +00:00
parent 8e70579e47
commit 4c01a099ea
9 changed files with 84 additions and 0 deletions

View File

@ -16,6 +16,22 @@ sharp('rgba.png')
Returns **Sharp** Returns **Sharp**
## 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.
### Examples
```javascript
sharp('rgb.jpg')
.ensureAlpha()
.toFile('rgba.png', function(err, info) {
// rgba.png is a 4 channel image with a fully opaque alpha channel
});
```
Returns **Sharp**
## extractChannel ## extractChannel
Extract a single channel from a multi-channel image. Extract a single channel from a multi-channel image.

View File

@ -9,6 +9,9 @@ Requires libvips v8.7.0.
* Ensure shortest edge is at least one pixel after resizing. * Ensure shortest edge is at least one pixel after resizing.
[#1003](https://github.com/lovell/sharp/issues/1003) [#1003](https://github.com/lovell/sharp/issues/1003)
* Add `ensureAlpha` operation to add an alpha channel, if missing.
[#1153](https://github.com/lovell/sharp/issues/1153)
* Expose `pages` and `pageHeight` metadata for multi-page input images. * Expose `pages` and `pageHeight` metadata for multi-page input images.
[#1205](https://github.com/lovell/sharp/issues/1205) [#1205](https://github.com/lovell/sharp/issues/1205)

View File

@ -29,6 +29,23 @@ function removeAlpha () {
return this; return this;
} }
/**
* 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.
*
* @example
* sharp('rgb.jpg')
* .ensureAlpha()
* .toFile('rgba.png', function(err, info) {
* // rgba.png is a 4 channel image with a fully opaque alpha channel
* });
*
* @returns {Sharp}
*/
function ensureAlpha () {
this.options.ensureAlpha = true;
return this;
}
/** /**
* Extract a single channel from a multi-channel image. * Extract a single channel from a multi-channel image.
* *
@ -120,6 +137,7 @@ module.exports = function (Sharp) {
Object.assign(Sharp.prototype, { Object.assign(Sharp.prototype, {
// Public instance functions // Public instance functions
removeAlpha, removeAlpha,
ensureAlpha,
extractChannel, extractChannel,
joinChannel, joinChannel,
bandbool bandbool

View File

@ -144,6 +144,7 @@ const Sharp = function (input, options) {
joinChannelIn: [], joinChannelIn: [],
extractChannel: -1, extractChannel: -1,
removeAlpha: false, removeAlpha: false,
ensureAlpha: false,
colourspace: 'srgb', colourspace: 'srgb',
// overlay // overlay
overlayGravity: 0, overlayGravity: 0,

View File

@ -38,6 +38,18 @@ namespace sharp {
return image; return image;
} }
/*
Ensures alpha channel, if missing.
*/
VImage EnsureAlpha(VImage image) {
if (!HasAlpha(image)) {
std::vector<double> alpha;
alpha.push_back(sharp::MaximumImageAlpha(image.interpretation()));
image = image.bandjoin_const(alpha);
}
return image;
}
/* /*
Composite overlayImage over image at given position Composite overlayImage over image at given position
Assumes alpha channels are already premultiplied and will be unpremultiplied after Assumes alpha channels are already premultiplied and will be unpremultiplied after

View File

@ -30,6 +30,11 @@ namespace sharp {
*/ */
VImage RemoveAlpha(VImage image); VImage RemoveAlpha(VImage image);
/*
Ensures alpha channel, if missing.
*/
VImage EnsureAlpha(VImage image);
/* /*
Alpha composite src over dst with given gravity. Alpha composite src over dst with given gravity.
Assumes alpha channels are already premultiplied and will be unpremultiplied after. Assumes alpha channels are already premultiplied and will be unpremultiplied after.

View File

@ -677,6 +677,11 @@ class PipelineWorker : public Nan::AsyncWorker {
image = sharp::RemoveAlpha(image); image = sharp::RemoveAlpha(image);
} }
// Ensure alpha channel, if missing
if (baton->ensureAlpha) {
image = sharp::EnsureAlpha(image);
}
// Convert image to sRGB, if not already // Convert image to sRGB, if not already
if (sharp::Is16Bit(image.interpretation())) { if (sharp::Is16Bit(image.interpretation())) {
image = image.cast(VIPS_FORMAT_USHORT); image = image.cast(VIPS_FORMAT_USHORT);
@ -1236,6 +1241,7 @@ NAN_METHOD(pipeline) {
baton->extractChannel = AttrTo<int32_t>(options, "extractChannel"); baton->extractChannel = AttrTo<int32_t>(options, "extractChannel");
baton->removeAlpha = AttrTo<bool>(options, "removeAlpha"); baton->removeAlpha = AttrTo<bool>(options, "removeAlpha");
baton->ensureAlpha = AttrTo<bool>(options, "ensureAlpha");
if (HasAttr(options, "boolean")) { if (HasAttr(options, "boolean")) {
baton->boolean = CreateInputDescriptor(AttrAs<v8::Object>(options, "boolean"), buffersToPersist); baton->boolean = CreateInputDescriptor(AttrAs<v8::Object>(options, "boolean"), buffersToPersist);
baton->booleanOp = sharp::GetBooleanOperation(AttrAsStr(options, "booleanOp")); baton->booleanOp = sharp::GetBooleanOperation(AttrAsStr(options, "booleanOp"));

View File

@ -142,6 +142,7 @@ struct PipelineBaton {
VipsOperationBoolean bandBoolOp; VipsOperationBoolean bandBoolOp;
int extractChannel; int extractChannel;
bool removeAlpha; bool removeAlpha;
bool ensureAlpha;
VipsInterpretation colourspace; VipsInterpretation colourspace;
int tileSize; int tileSize;
int tileOverlap; int tileOverlap;
@ -237,6 +238,7 @@ struct PipelineBaton {
bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST), bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST),
extractChannel(-1), extractChannel(-1),
removeAlpha(false), removeAlpha(false),
ensureAlpha(false),
colourspace(VIPS_INTERPRETATION_LAST), colourspace(VIPS_INTERPRETATION_LAST),
tileSize(256), tileSize(256),
tileOverlap(0), tileOverlap(0),

View File

@ -115,6 +115,7 @@ describe('Alpha transparency', function () {
fixtures.inputWebP fixtures.inputWebP
].map(function (input) { ].map(function (input) {
return sharp(input) return sharp(input)
.resize(10)
.removeAlpha() .removeAlpha()
.toBuffer({ resolveWithObject: true }) .toBuffer({ resolveWithObject: true })
.then(function (result) { .then(function (result) {
@ -122,4 +123,24 @@ describe('Alpha transparency', function () {
}); });
})); }));
}); });
it('Ensures alpha from fixtures without transparency, ignores those with', function () {
return Promise.all([
fixtures.inputPngWithTransparency,
fixtures.inputPngWithTransparency16bit,
fixtures.inputWebPWithTransparency,
fixtures.inputJpg,
fixtures.inputPng,
fixtures.inputWebP
].map(function (input) {
return sharp(input)
.resize(10)
.ensureAlpha()
.png()
.toBuffer({ resolveWithObject: true })
.then(function (result) {
assert.strictEqual(4, result.info.channels);
});
}));
});
}); });