diff --git a/docs/src/content/docs/api-channel.md b/docs/src/content/docs/api-channel.md
index 2d386e8f..29808348 100644
--- a/docs/src/content/docs/api-channel.md
+++ b/docs/src/content/docs/api-channel.md
@@ -6,7 +6,7 @@ title: Channel manipulation
## removeAlpha
> removeAlpha() ⇒ Sharp
-Remove alpha channel, if any. This is a no-op if the image does not have an alpha channel.
+Remove alpha channels, if any. This is a no-op if the image does not have an alpha channel.
See also [flatten](/api-operation#flatten).
diff --git a/docs/src/content/docs/changelog.md b/docs/src/content/docs/changelog.md
index ac83395b..b5711d98 100644
--- a/docs/src/content/docs/changelog.md
+++ b/docs/src/content/docs/changelog.md
@@ -11,6 +11,9 @@ Requires libvips v8.16.0
* Breaking: Support array of input images to be joined or animated.
[#1580](https://github.com/lovell/sharp/issues/1580)
+* Breaking: Ensure `removeAlpha` removes all alpha channels.
+ [#2266](https://github.com/lovell/sharp/issues/2266)
+
* Breaking: Support `info.size` on wide-character systems via upgrade to C++17.
[#3943](https://github.com/lovell/sharp/issues/3943)
diff --git a/lib/channel.js b/lib/channel.js
index 5e3e7aee..46bce3d5 100644
--- a/lib/channel.js
+++ b/lib/channel.js
@@ -16,7 +16,7 @@ const bool = {
};
/**
- * Remove alpha channel, if any. This is a no-op if the image does not have an alpha channel.
+ * Remove alpha channels, if any. This is a no-op if the image does not have an alpha channel.
*
* See also {@link /api-operation#flatten|flatten}.
*
diff --git a/src/common.cc b/src/common.cc
index 528ef7c7..fb85eefa 100644
--- a/src/common.cc
+++ b/src/common.cc
@@ -595,14 +595,6 @@ namespace sharp {
return image;
}
- /*
- Does this image have an alpha channel?
- Uses colour space interpretation with number of channels to guess this.
- */
- bool HasAlpha(VImage image) {
- return image.has_alpha();
- }
-
static void* RemoveExifCallback(VipsImage *image, char const *field, GValue *value, void *data) {
std::vector *fieldNames = static_cast *>(data);
std::string fieldName(field);
@@ -1011,13 +1003,13 @@ namespace sharp {
};
}
// Add alpha channel to alphaColour colour
- if (colour[3] < 255.0 || HasAlpha(image)) {
+ if (colour[3] < 255.0 || image.has_alpha()) {
alphaColour.push_back(colour[3] * multiplier);
}
// Ensure alphaColour colour uses correct colourspace
alphaColour = sharp::GetRgbaAsColourspace(alphaColour, image.interpretation(), premultiply);
// Add non-transparent alpha channel, if required
- if (colour[3] < 255.0 && !HasAlpha(image)) {
+ if (colour[3] < 255.0 && !image.has_alpha()) {
image = image.bandjoin(
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier).cast(image.format()));
}
@@ -1025,10 +1017,10 @@ namespace sharp {
}
/*
- Removes alpha channel, if any.
+ Removes alpha channels, if any.
*/
VImage RemoveAlpha(VImage image) {
- if (HasAlpha(image)) {
+ while (image.bands() > 1 && image.has_alpha()) {
image = image.extract_band(0, VImage::option()->set("n", image.bands() - 1));
}
return image;
@@ -1038,7 +1030,7 @@ namespace sharp {
Ensures alpha channel, if missing.
*/
VImage EnsureAlpha(VImage image, double const value) {
- if (!HasAlpha(image)) {
+ if (!image.has_alpha()) {
std::vector alpha;
alpha.push_back(value * sharp::MaximumImageAlpha(image.interpretation()));
image = image.bandjoin_const(alpha);
diff --git a/src/common.h b/src/common.h
index 3e0f7884..434c07d5 100644
--- a/src/common.h
+++ b/src/common.h
@@ -245,12 +245,6 @@ namespace sharp {
*/
VImage SetProfile(VImage image, std::pair icc);
- /*
- Does this image have an alpha channel?
- Uses colour space interpretation with number of channels to guess this.
- */
- bool HasAlpha(VImage image);
-
/*
Remove all EXIF-related image fields.
*/
@@ -381,7 +375,7 @@ namespace sharp {
std::tuple> ApplyAlpha(VImage image, std::vector colour, bool premultiply);
/*
- Removes alpha channel, if any.
+ Removes alpha channels, if any.
*/
VImage RemoveAlpha(VImage image);
diff --git a/src/metadata.cc b/src/metadata.cc
index 5bf86898..9991d104 100644
--- a/src/metadata.cc
+++ b/src/metadata.cc
@@ -95,7 +95,7 @@ class MetadataWorker : public Napi::AsyncWorker {
baton->background = image.get_array_double("background");
}
// Derived attributes
- baton->hasAlpha = sharp::HasAlpha(image);
+ baton->hasAlpha = image.has_alpha();
baton->orientation = sharp::ExifOrientation(image);
// EXIF
if (image.get_typeof(VIPS_META_EXIF_NAME) == VIPS_TYPE_BLOB) {
diff --git a/src/operations.cc b/src/operations.cc
index 775f6134..9856bb2a 100644
--- a/src/operations.cc
+++ b/src/operations.cc
@@ -40,7 +40,7 @@ namespace sharp {
typeBeforeTint = VIPS_INTERPRETATION_sRGB;
}
// Apply lookup table
- if (HasAlpha(image)) {
+ if (image.has_alpha()) {
VImage alpha = image[image.bands() - 1];
image = RemoveAlpha(image)
.colourspace(VIPS_INTERPRETATION_B_W)
@@ -83,7 +83,7 @@ namespace sharp {
// Scale luminance, join to chroma, convert back to original colourspace
VImage normalized = luminance.linear(f, a).bandjoin(chroma).colourspace(typeBeforeNormalize);
// Attach original alpha channel, if any
- if (HasAlpha(image)) {
+ if (image.has_alpha()) {
// Extract original alpha channel
VImage alpha = image[image.bands() - 1];
// Join alpha channel to normalised image
@@ -106,7 +106,7 @@ namespace sharp {
* Gamma encoding/decoding
*/
VImage Gamma(VImage image, double const exponent) {
- if (HasAlpha(image)) {
+ if (image.has_alpha()) {
// Separate alpha channel
VImage alpha = image[image.bands() - 1];
return RemoveAlpha(image).gamma(VImage::option()->set("exponent", exponent)).bandjoin(alpha);
@@ -132,7 +132,7 @@ namespace sharp {
* Produce the "negative" of the image.
*/
VImage Negate(VImage image, bool const negateAlpha) {
- if (HasAlpha(image) && !negateAlpha) {
+ if (image.has_alpha() && !negateAlpha) {
// Separate alpha channel
VImage alpha = image[image.bands() - 1];
return RemoveAlpha(image).invert().bandjoin(alpha);
@@ -205,7 +205,7 @@ namespace sharp {
VImage Modulate(VImage image, double const brightness, double const saturation,
int const hue, double const lightness) {
VipsInterpretation colourspaceBeforeModulate = image.interpretation();
- if (HasAlpha(image)) {
+ if (image.has_alpha()) {
// Separate alpha channel
VImage alpha = image[image.bands() - 1];
return RemoveAlpha(image)
@@ -297,7 +297,7 @@ namespace sharp {
threshold *= 256.0;
}
std::vector backgroundAlpha({ background.back() });
- if (HasAlpha(image)) {
+ if (image.has_alpha()) {
background.pop_back();
} else {
background.resize(image.bands());
@@ -307,7 +307,7 @@ namespace sharp {
->set("background", background)
->set("line_art", lineArt)
->set("threshold", threshold));
- if (HasAlpha(image)) {
+ if (image.has_alpha()) {
// Search alpha channel (A)
int leftA, topA, widthA, heightA;
VImage alpha = image[image.bands() - 1];
@@ -344,7 +344,7 @@ namespace sharp {
throw VError("Band expansion using linear is unsupported");
}
bool const uchar = !Is16Bit(image.interpretation());
- if (HasAlpha(image) && a.size() != bands && (a.size() == 1 || a.size() == bands - 1 || bands - 1 == 1)) {
+ if (image.has_alpha() && a.size() != bands && (a.size() == 1 || a.size() == bands - 1 || bands - 1 == 1)) {
// Separate alpha channel
VImage alpha = image[bands - 1];
return RemoveAlpha(image).linear(a, b, VImage::option()->set("uchar", uchar)).bandjoin(alpha);
@@ -357,7 +357,7 @@ namespace sharp {
* Unflatten
*/
VImage Unflatten(VImage image) {
- if (HasAlpha(image)) {
+ if (image.has_alpha()) {
VImage alpha = image[image.bands() - 1];
VImage noAlpha = RemoveAlpha(image);
return noAlpha.bandjoin(alpha & (noAlpha.colourspace(VIPS_INTERPRETATION_B_W) < 255));
diff --git a/src/pipeline.cc b/src/pipeline.cc
index 4495f2bf..f1638d63 100644
--- a/src/pipeline.cc
+++ b/src/pipeline.cc
@@ -51,11 +51,11 @@ class PipelineWorker : public Napi::AsyncWorker {
std::tie(image, inputImageType) = sharp::OpenInput(join);
image = sharp::EnsureColourspace(image, baton->colourspacePipeline);
images.push_back(image);
- hasAlpha |= sharp::HasAlpha(image);
+ hasAlpha |= image.has_alpha();
}
if (hasAlpha) {
for (auto &image : images) {
- if (!sharp::HasAlpha(image)) {
+ if (!image.has_alpha()) {
image = sharp::EnsureAlpha(image, 1);
}
}
@@ -372,7 +372,7 @@ class PipelineWorker : public Napi::AsyncWorker {
}
// Flatten image to remove alpha channel
- if (baton->flatten && sharp::HasAlpha(image)) {
+ if (baton->flatten && image.has_alpha()) {
image = sharp::Flatten(image, baton->flattenBackground);
}
@@ -392,12 +392,12 @@ class PipelineWorker : public Napi::AsyncWorker {
bool const shouldSharpen = baton->sharpenSigma != 0.0;
bool const shouldComposite = !baton->composite.empty();
- if (shouldComposite && !sharp::HasAlpha(image)) {
+ if (shouldComposite && !image.has_alpha()) {
image = sharp::EnsureAlpha(image, 1);
}
VipsBandFormat premultiplyFormat = image.format();
- bool const shouldPremultiplyAlpha = sharp::HasAlpha(image) &&
+ bool const shouldPremultiplyAlpha = image.has_alpha() &&
(shouldResize || shouldBlur || shouldConv || shouldSharpen);
if (shouldPremultiplyAlpha) {
@@ -725,9 +725,7 @@ class PipelineWorker : public Napi::AsyncWorker {
}
// Ensure image to composite is sRGB with unpremultiplied alpha
compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB);
- if (!sharp::HasAlpha(compositeImage)) {
- compositeImage = sharp::EnsureAlpha(compositeImage, 1);
- }
+ compositeImage = sharp::EnsureAlpha(compositeImage, 1);
if (composite->premultiplied) compositeImage = compositeImage.unpremultiply();
// Calculate position
int left;
@@ -828,7 +826,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// Extract channel
if (baton->extractChannel > -1) {
if (baton->extractChannel >= image.bands()) {
- if (baton->extractChannel == 3 && sharp::HasAlpha(image)) {
+ if (baton->extractChannel == 3 && image.has_alpha()) {
baton->extractChannel = image.bands() - 1;
} else {
(baton->err)
@@ -1052,7 +1050,7 @@ class PipelineWorker : public Napi::AsyncWorker {
} else if (baton->formatOut == "dz") {
// Write DZ to buffer
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
- if (!sharp::HasAlpha(image)) {
+ if (!image.has_alpha()) {
baton->tileBackground.pop_back();
}
image = sharp::StaySequential(image, baton->tileAngle != 0);
@@ -1256,7 +1254,7 @@ class PipelineWorker : public Napi::AsyncWorker {
if (isDzZip) {
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
}
- if (!sharp::HasAlpha(image)) {
+ if (!image.has_alpha()) {
baton->tileBackground.pop_back();
}
image = sharp::StaySequential(image, baton->tileAngle != 0);
diff --git a/src/stats.cc b/src/stats.cc
index b06d3bb5..61df71a2 100644
--- a/src/stats.cc
+++ b/src/stats.cc
@@ -58,7 +58,7 @@ class StatsWorker : public Napi::AsyncWorker {
baton->channelStats.push_back(cStats);
}
// Image is not opaque when alpha layer is present and contains a non-mamixa value
- if (sharp::HasAlpha(image)) {
+ if (image.has_alpha()) {
double const minAlpha = static_cast(stats.getpoint(STAT_MIN_INDEX, bands).front());
if (minAlpha != sharp::MaximumImageAlpha(image.interpretation())) {
baton->isOpaque = false;
diff --git a/src/utilities.cc b/src/utilities.cc
index 580bdf35..ed84458d 100644
--- a/src/utilities.cc
+++ b/src/utilities.cc
@@ -233,10 +233,10 @@ Napi::Value _maxColourDistance(const Napi::CallbackInfo& info) {
double maxColourDistance;
try {
// Premultiply and remove alpha
- if (sharp::HasAlpha(image1)) {
+ if (image1.has_alpha()) {
image1 = image1.premultiply().extract_band(1, VImage::option()->set("n", image1.bands() - 1));
}
- if (sharp::HasAlpha(image2)) {
+ if (image2.has_alpha()) {
image2 = image2.premultiply().extract_band(1, VImage::option()->set("n", image2.bands() - 1));
}
// Calculate colour distance