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. --> <!-- 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 ## extractChannel
Extract a single channel from a multi-channel image. 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) - `maxX` (x-coordinate of one of the pixel where the maximum lies)
- `maxY` (y-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 - `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 ### Parameters

View File

@ -6,6 +6,9 @@ Requires libvips v8.6.1.
#### v0.20.6 - TBD #### 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. * Expose mozjpeg quant_table flag.
[#1285](https://github.com/lovell/sharp/pull/1285) [#1285](https://github.com/lovell/sharp/pull/1285)
[@rexxars](https://github.com/rexxars) [@rexxars](https://github.com/rexxars)

View File

@ -12,6 +12,23 @@ const bool = {
eor: 'eor' 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. * Extract a single channel from a multi-channel image.
* *
@ -102,6 +119,7 @@ function bandbool (boolOp) {
module.exports = function (Sharp) { module.exports = function (Sharp) {
// Public instance functions // Public instance functions
[ [
removeAlpha,
extractChannel, extractChannel,
joinChannel, joinChannel,
bandbool bandbool

View File

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

View File

@ -28,6 +28,16 @@ using vips::VError;
namespace sharp { 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 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
@ -223,10 +233,8 @@ namespace sharp {
VImage Gamma(VImage image, double const exponent) { VImage Gamma(VImage image, double const exponent) {
if (HasAlpha(image)) { if (HasAlpha(image)) {
// Separate alpha channel // Separate alpha channel
VImage imageWithoutAlpha = image.extract_band(0,
VImage::option()->set("n", image.bands() - 1));
VImage alpha = image[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 { } else {
return image.gamma(VImage::option()->set("exponent", exponent)); return image.gamma(VImage::option()->set("exponent", exponent));
} }
@ -374,10 +382,8 @@ namespace sharp {
VImage Linear(VImage image, double const a, double const b) { VImage Linear(VImage image, double const a, double const b) {
if (HasAlpha(image)) { if (HasAlpha(image)) {
// Separate alpha channel // Separate alpha channel
VImage imageWithoutAlpha = image.extract_band(0,
VImage::option()->set("n", image.bands() - 1));
VImage alpha = image[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 { } else {
return image.linear(a, b); return image.linear(a, b);
} }

View File

@ -25,6 +25,11 @@ using vips::VImage;
namespace sharp { namespace sharp {
/*
Removes alpha channel, if any.
*/
VImage RemoveAlpha(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

@ -698,6 +698,12 @@ class PipelineWorker : public Nan::AsyncWorker {
.extract_band(baton->extractChannel) .extract_band(baton->extractChannel)
.copy(VImage::option()->set("interpretation", VIPS_INTERPRETATION_B_W)); .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 // 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);
@ -1235,6 +1241,7 @@ NAN_METHOD(pipeline) {
baton->extendLeft = AttrTo<int32_t>(options, "extendLeft"); baton->extendLeft = AttrTo<int32_t>(options, "extendLeft");
baton->extendRight = AttrTo<int32_t>(options, "extendRight"); baton->extendRight = AttrTo<int32_t>(options, "extendRight");
baton->extractChannel = AttrTo<int32_t>(options, "extractChannel"); baton->extractChannel = AttrTo<int32_t>(options, "extractChannel");
baton->removeAlpha = AttrTo<bool>(options, "removeAlpha");
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

@ -131,6 +131,7 @@ struct PipelineBaton {
VipsOperationBoolean booleanOp; VipsOperationBoolean booleanOp;
VipsOperationBoolean bandBoolOp; VipsOperationBoolean bandBoolOp;
int extractChannel; int extractChannel;
bool removeAlpha;
VipsInterpretation colourspace; VipsInterpretation colourspace;
int tileSize; int tileSize;
int tileOverlap; int tileOverlap;
@ -213,6 +214,7 @@ struct PipelineBaton {
booleanOp(VIPS_OPERATION_BOOLEAN_LAST), booleanOp(VIPS_OPERATION_BOOLEAN_LAST),
bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST), bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST),
extractChannel(-1), extractChannel(-1),
removeAlpha(false),
colourspace(VIPS_INTERPRETATION_LAST), colourspace(VIPS_INTERPRETATION_LAST),
tileSize(256), tileSize(256),
tileOverlap(0), 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 base = 'alpha-premultiply-enlargement-2048x1536-paper.png';
const actual = fixtures.path('output.' + base); const actual = fixtures.path('output.' + base);
const expected = fixtures.expected(base); const expected = fixtures.expected(base);
sharp(fixtures.inputPngAlphaPremultiplicationSmall) return sharp(fixtures.inputPngAlphaPremultiplicationSmall)
.resize(2048, 1536) .resize(2048, 1536)
.toFile(actual, function (err) { .toFile(actual)
if (err) { .then(function () {
done(err); fixtures.assertMaxColourDistance(actual, expected, 102);
} else {
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 base = 'alpha-premultiply-reduction-1024x768-paper.png';
const actual = fixtures.path('output.' + base); const actual = fixtures.path('output.' + base);
const expected = fixtures.expected(base); const expected = fixtures.expected(base);
sharp(fixtures.inputPngAlphaPremultiplicationLarge) return sharp(fixtures.inputPngAlphaPremultiplicationLarge)
.resize(1024, 768) .resize(1024, 768)
.toFile(actual, function (err) { .toFile(actual)
if (err) { .then(function () {
done(err); fixtures.assertMaxColourDistance(actual, expected, 102);
} else {
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);
});
}));
});
}); });