Add removeAlpha op, removes alpha channel if any #1248

This commit is contained in:
Lovell Fuller 2018-08-07 20:32:11 +01:00
parent 25bd2cea3e
commit c14434f9e7
10 changed files with 93 additions and 24 deletions

View File

@ -1,5 +1,21 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## removeAlpha
Remove alpha channel, if any. This is a no-op if the image does not have an alpha channel.
### Examples
```javascript
sharp('rgba.png')
.removeAlpha()
.toFile('rgb.png', function(err, info) {
// rgb.png is a 3 channel image without an alpha channel
});
```
Returns **Sharp**
## extractChannel
Extract a single channel from a multi-channel image.

View File

@ -79,6 +79,7 @@ A Promise is returned when `callback` is not provided.
- `maxX` (x-coordinate of one of the pixel where the maximum lies)
- `maxY` (y-coordinate of one of the pixel where the maximum lies)
- `isOpaque`: Value to identify if the image is opaque or transparent, based on the presence and use of alpha channel
- `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
### Parameters

View File

@ -6,6 +6,9 @@ Requires libvips v8.6.1.
#### v0.20.6 - TBD
* Add removeAlpha operation to remove alpha channel, if any.
[#1248](https://github.com/lovell/sharp/issues/1248)
* Expose mozjpeg quant_table flag.
[#1285](https://github.com/lovell/sharp/pull/1285)
[@rexxars](https://github.com/rexxars)

View File

@ -12,6 +12,23 @@ const bool = {
eor: 'eor'
};
/**
* Remove alpha channel, if any. This is a no-op if the image does not have an alpha channel.
*
* @example
* sharp('rgba.png')
* .removeAlpha()
* .toFile('rgb.png', function(err, info) {
* // rgb.png is a 3 channel image without an alpha channel
* });
*
* @returns {Sharp}
*/
function removeAlpha () {
this.options.removeAlpha = true;
return this;
}
/**
* Extract a single channel from a multi-channel image.
*
@ -102,6 +119,7 @@ function bandbool (boolOp) {
module.exports = function (Sharp) {
// Public instance functions
[
removeAlpha,
extractChannel,
joinChannel,
bandbool

View File

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

View File

@ -28,6 +28,16 @@ using vips::VError;
namespace sharp {
/*
Removes alpha channel, if any.
*/
VImage RemoveAlpha(VImage image) {
if (HasAlpha(image)) {
image = image.extract_band(0, VImage::option()->set("n", image.bands() - 1));
}
return image;
}
/*
Composite overlayImage over image at given position
Assumes alpha channels are already premultiplied and will be unpremultiplied after
@ -223,10 +233,8 @@ namespace sharp {
VImage Gamma(VImage image, double const exponent) {
if (HasAlpha(image)) {
// Separate alpha channel
VImage imageWithoutAlpha = image.extract_band(0,
VImage::option()->set("n", image.bands() - 1));
VImage alpha = image[image.bands() - 1];
return imageWithoutAlpha.gamma(VImage::option()->set("exponent", exponent)).bandjoin(alpha);
return RemoveAlpha(image).gamma(VImage::option()->set("exponent", exponent)).bandjoin(alpha);
} else {
return image.gamma(VImage::option()->set("exponent", exponent));
}
@ -374,10 +382,8 @@ namespace sharp {
VImage Linear(VImage image, double const a, double const b) {
if (HasAlpha(image)) {
// Separate alpha channel
VImage imageWithoutAlpha = image.extract_band(0,
VImage::option()->set("n", image.bands() - 1));
VImage alpha = image[image.bands() - 1];
return imageWithoutAlpha.linear(a, b).bandjoin(alpha);
return RemoveAlpha(image).linear(a, b).bandjoin(alpha);
} else {
return image.linear(a, b);
}

View File

@ -25,6 +25,11 @@ using vips::VImage;
namespace sharp {
/*
Removes alpha channel, if any.
*/
VImage RemoveAlpha(VImage image);
/*
Alpha composite src over dst with given gravity.
Assumes alpha channels are already premultiplied and will be unpremultiplied after.

View File

@ -698,6 +698,12 @@ class PipelineWorker : public Nan::AsyncWorker {
.extract_band(baton->extractChannel)
.copy(VImage::option()->set("interpretation", VIPS_INTERPRETATION_B_W));
}
// Remove alpha channel, if any
if (baton->removeAlpha) {
image = sharp::RemoveAlpha(image);
}
// Convert image to sRGB, if not already
if (sharp::Is16Bit(image.interpretation())) {
image = image.cast(VIPS_FORMAT_USHORT);
@ -1235,6 +1241,7 @@ NAN_METHOD(pipeline) {
baton->extendLeft = AttrTo<int32_t>(options, "extendLeft");
baton->extendRight = AttrTo<int32_t>(options, "extendRight");
baton->extractChannel = AttrTo<int32_t>(options, "extractChannel");
baton->removeAlpha = AttrTo<bool>(options, "removeAlpha");
if (HasAttr(options, "boolean")) {
baton->boolean = CreateInputDescriptor(AttrAs<v8::Object>(options, "boolean"), buffersToPersist);
baton->booleanOp = sharp::GetBooleanOperation(AttrAsStr(options, "booleanOp"));

View File

@ -131,6 +131,7 @@ struct PipelineBaton {
VipsOperationBoolean booleanOp;
VipsOperationBoolean bandBoolOp;
int extractChannel;
bool removeAlpha;
VipsInterpretation colourspace;
int tileSize;
int tileOverlap;
@ -213,6 +214,7 @@ struct PipelineBaton {
booleanOp(VIPS_OPERATION_BOOLEAN_LAST),
bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST),
extractChannel(-1),
removeAlpha(false),
colourspace(VIPS_INTERPRETATION_LAST),
tileSize(256),
tileOverlap(0),

View File

@ -81,35 +81,45 @@ describe('Alpha transparency', function () {
});
});
it('Enlargement with non-nearest neighbor interpolation shouldnt cause dark edges', function (done) {
it('Enlargement with non-nearest neighbor interpolation shouldnt cause dark edges', function () {
const base = 'alpha-premultiply-enlargement-2048x1536-paper.png';
const actual = fixtures.path('output.' + base);
const expected = fixtures.expected(base);
sharp(fixtures.inputPngAlphaPremultiplicationSmall)
return sharp(fixtures.inputPngAlphaPremultiplicationSmall)
.resize(2048, 1536)
.toFile(actual, function (err) {
if (err) {
done(err);
} else {
.toFile(actual)
.then(function () {
fixtures.assertMaxColourDistance(actual, expected, 102);
done();
}
});
});
it('Reduction with non-nearest neighbor interpolation shouldnt cause dark edges', function (done) {
it('Reduction with non-nearest neighbor interpolation shouldnt cause dark edges', function () {
const base = 'alpha-premultiply-reduction-1024x768-paper.png';
const actual = fixtures.path('output.' + base);
const expected = fixtures.expected(base);
sharp(fixtures.inputPngAlphaPremultiplicationLarge)
return sharp(fixtures.inputPngAlphaPremultiplicationLarge)
.resize(1024, 768)
.toFile(actual, function (err) {
if (err) {
done(err);
} else {
.toFile(actual)
.then(function () {
fixtures.assertMaxColourDistance(actual, expected, 102);
done();
}
});
});
it('Removes alpha from fixtures with transparency, ignores those without', function () {
return Promise.all([
fixtures.inputPngWithTransparency,
fixtures.inputPngWithTransparency16bit,
fixtures.inputWebPWithTransparency,
fixtures.inputJpg,
fixtures.inputPng,
fixtures.inputWebP
].map(function (input) {
return sharp(input)
.removeAlpha()
.toBuffer({ resolveWithObject: true })
.then(function (result) {
assert.strictEqual(3, result.info.channels);
});
}));
});
});