Ensure removeAlpha removes all alpha channels #2266

This commit is contained in:
Lovell Fuller 2025-02-20 09:26:52 +00:00
parent 9d01dd20bf
commit edad89c531
10 changed files with 33 additions and 46 deletions

View File

@ -6,7 +6,7 @@ title: Channel manipulation
## removeAlpha ## removeAlpha
> removeAlpha() ⇒ <code>Sharp</code> > removeAlpha() ⇒ <code>Sharp</code>
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). See also [flatten](/api-operation#flatten).

View File

@ -11,6 +11,9 @@ Requires libvips v8.16.0
* Breaking: Support array of input images to be joined or animated. * Breaking: Support array of input images to be joined or animated.
[#1580](https://github.com/lovell/sharp/issues/1580) [#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. * Breaking: Support `info.size` on wide-character systems via upgrade to C++17.
[#3943](https://github.com/lovell/sharp/issues/3943) [#3943](https://github.com/lovell/sharp/issues/3943)

View File

@ -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}. * See also {@link /api-operation#flatten|flatten}.
* *

View File

@ -595,14 +595,6 @@ namespace sharp {
return image; 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) { static void* RemoveExifCallback(VipsImage *image, char const *field, GValue *value, void *data) {
std::vector<std::string> *fieldNames = static_cast<std::vector<std::string> *>(data); std::vector<std::string> *fieldNames = static_cast<std::vector<std::string> *>(data);
std::string fieldName(field); std::string fieldName(field);
@ -1011,13 +1003,13 @@ namespace sharp {
}; };
} }
// Add alpha channel to alphaColour colour // 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); alphaColour.push_back(colour[3] * multiplier);
} }
// Ensure alphaColour colour uses correct colourspace // Ensure alphaColour colour uses correct colourspace
alphaColour = sharp::GetRgbaAsColourspace(alphaColour, image.interpretation(), premultiply); alphaColour = sharp::GetRgbaAsColourspace(alphaColour, image.interpretation(), premultiply);
// Add non-transparent alpha channel, if required // 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( image = image.bandjoin(
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier).cast(image.format())); 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) { 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)); image = image.extract_band(0, VImage::option()->set("n", image.bands() - 1));
} }
return image; return image;
@ -1038,7 +1030,7 @@ namespace sharp {
Ensures alpha channel, if missing. Ensures alpha channel, if missing.
*/ */
VImage EnsureAlpha(VImage image, double const value) { VImage EnsureAlpha(VImage image, double const value) {
if (!HasAlpha(image)) { if (!image.has_alpha()) {
std::vector<double> alpha; std::vector<double> alpha;
alpha.push_back(value * sharp::MaximumImageAlpha(image.interpretation())); alpha.push_back(value * sharp::MaximumImageAlpha(image.interpretation()));
image = image.bandjoin_const(alpha); image = image.bandjoin_const(alpha);

View File

@ -245,12 +245,6 @@ namespace sharp {
*/ */
VImage SetProfile(VImage image, std::pair<char*, size_t> icc); VImage SetProfile(VImage image, std::pair<char*, size_t> 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. Remove all EXIF-related image fields.
*/ */
@ -381,7 +375,7 @@ namespace sharp {
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour, bool premultiply); std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour, bool premultiply);
/* /*
Removes alpha channel, if any. Removes alpha channels, if any.
*/ */
VImage RemoveAlpha(VImage image); VImage RemoveAlpha(VImage image);

View File

@ -95,7 +95,7 @@ class MetadataWorker : public Napi::AsyncWorker {
baton->background = image.get_array_double("background"); baton->background = image.get_array_double("background");
} }
// Derived attributes // Derived attributes
baton->hasAlpha = sharp::HasAlpha(image); baton->hasAlpha = image.has_alpha();
baton->orientation = sharp::ExifOrientation(image); baton->orientation = sharp::ExifOrientation(image);
// EXIF // EXIF
if (image.get_typeof(VIPS_META_EXIF_NAME) == VIPS_TYPE_BLOB) { if (image.get_typeof(VIPS_META_EXIF_NAME) == VIPS_TYPE_BLOB) {

View File

@ -40,7 +40,7 @@ namespace sharp {
typeBeforeTint = VIPS_INTERPRETATION_sRGB; typeBeforeTint = VIPS_INTERPRETATION_sRGB;
} }
// Apply lookup table // Apply lookup table
if (HasAlpha(image)) { if (image.has_alpha()) {
VImage alpha = image[image.bands() - 1]; VImage alpha = image[image.bands() - 1];
image = RemoveAlpha(image) image = RemoveAlpha(image)
.colourspace(VIPS_INTERPRETATION_B_W) .colourspace(VIPS_INTERPRETATION_B_W)
@ -83,7 +83,7 @@ namespace sharp {
// Scale luminance, join to chroma, convert back to original colourspace // Scale luminance, join to chroma, convert back to original colourspace
VImage normalized = luminance.linear(f, a).bandjoin(chroma).colourspace(typeBeforeNormalize); VImage normalized = luminance.linear(f, a).bandjoin(chroma).colourspace(typeBeforeNormalize);
// Attach original alpha channel, if any // Attach original alpha channel, if any
if (HasAlpha(image)) { if (image.has_alpha()) {
// Extract original alpha channel // Extract original alpha channel
VImage alpha = image[image.bands() - 1]; VImage alpha = image[image.bands() - 1];
// Join alpha channel to normalised image // Join alpha channel to normalised image
@ -106,7 +106,7 @@ namespace sharp {
* Gamma encoding/decoding * Gamma encoding/decoding
*/ */
VImage Gamma(VImage image, double const exponent) { VImage Gamma(VImage image, double const exponent) {
if (HasAlpha(image)) { if (image.has_alpha()) {
// Separate alpha channel // Separate alpha channel
VImage alpha = image[image.bands() - 1]; VImage alpha = image[image.bands() - 1];
return RemoveAlpha(image).gamma(VImage::option()->set("exponent", exponent)).bandjoin(alpha); return RemoveAlpha(image).gamma(VImage::option()->set("exponent", exponent)).bandjoin(alpha);
@ -132,7 +132,7 @@ namespace sharp {
* Produce the "negative" of the image. * Produce the "negative" of the image.
*/ */
VImage Negate(VImage image, bool const negateAlpha) { VImage Negate(VImage image, bool const negateAlpha) {
if (HasAlpha(image) && !negateAlpha) { if (image.has_alpha() && !negateAlpha) {
// Separate alpha channel // Separate alpha channel
VImage alpha = image[image.bands() - 1]; VImage alpha = image[image.bands() - 1];
return RemoveAlpha(image).invert().bandjoin(alpha); return RemoveAlpha(image).invert().bandjoin(alpha);
@ -205,7 +205,7 @@ namespace sharp {
VImage Modulate(VImage image, double const brightness, double const saturation, VImage Modulate(VImage image, double const brightness, double const saturation,
int const hue, double const lightness) { int const hue, double const lightness) {
VipsInterpretation colourspaceBeforeModulate = image.interpretation(); VipsInterpretation colourspaceBeforeModulate = image.interpretation();
if (HasAlpha(image)) { if (image.has_alpha()) {
// Separate alpha channel // Separate alpha channel
VImage alpha = image[image.bands() - 1]; VImage alpha = image[image.bands() - 1];
return RemoveAlpha(image) return RemoveAlpha(image)
@ -297,7 +297,7 @@ namespace sharp {
threshold *= 256.0; threshold *= 256.0;
} }
std::vector<double> backgroundAlpha({ background.back() }); std::vector<double> backgroundAlpha({ background.back() });
if (HasAlpha(image)) { if (image.has_alpha()) {
background.pop_back(); background.pop_back();
} else { } else {
background.resize(image.bands()); background.resize(image.bands());
@ -307,7 +307,7 @@ namespace sharp {
->set("background", background) ->set("background", background)
->set("line_art", lineArt) ->set("line_art", lineArt)
->set("threshold", threshold)); ->set("threshold", threshold));
if (HasAlpha(image)) { if (image.has_alpha()) {
// Search alpha channel (A) // Search alpha channel (A)
int leftA, topA, widthA, heightA; int leftA, topA, widthA, heightA;
VImage alpha = image[image.bands() - 1]; VImage alpha = image[image.bands() - 1];
@ -344,7 +344,7 @@ namespace sharp {
throw VError("Band expansion using linear is unsupported"); throw VError("Band expansion using linear is unsupported");
} }
bool const uchar = !Is16Bit(image.interpretation()); 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 // Separate alpha channel
VImage alpha = image[bands - 1]; VImage alpha = image[bands - 1];
return RemoveAlpha(image).linear(a, b, VImage::option()->set("uchar", uchar)).bandjoin(alpha); return RemoveAlpha(image).linear(a, b, VImage::option()->set("uchar", uchar)).bandjoin(alpha);
@ -357,7 +357,7 @@ namespace sharp {
* Unflatten * Unflatten
*/ */
VImage Unflatten(VImage image) { VImage Unflatten(VImage image) {
if (HasAlpha(image)) { if (image.has_alpha()) {
VImage alpha = image[image.bands() - 1]; VImage alpha = image[image.bands() - 1];
VImage noAlpha = RemoveAlpha(image); VImage noAlpha = RemoveAlpha(image);
return noAlpha.bandjoin(alpha & (noAlpha.colourspace(VIPS_INTERPRETATION_B_W) < 255)); return noAlpha.bandjoin(alpha & (noAlpha.colourspace(VIPS_INTERPRETATION_B_W) < 255));

View File

@ -51,11 +51,11 @@ class PipelineWorker : public Napi::AsyncWorker {
std::tie(image, inputImageType) = sharp::OpenInput(join); std::tie(image, inputImageType) = sharp::OpenInput(join);
image = sharp::EnsureColourspace(image, baton->colourspacePipeline); image = sharp::EnsureColourspace(image, baton->colourspacePipeline);
images.push_back(image); images.push_back(image);
hasAlpha |= sharp::HasAlpha(image); hasAlpha |= image.has_alpha();
} }
if (hasAlpha) { if (hasAlpha) {
for (auto &image : images) { for (auto &image : images) {
if (!sharp::HasAlpha(image)) { if (!image.has_alpha()) {
image = sharp::EnsureAlpha(image, 1); image = sharp::EnsureAlpha(image, 1);
} }
} }
@ -372,7 +372,7 @@ class PipelineWorker : public Napi::AsyncWorker {
} }
// Flatten image to remove alpha channel // Flatten image to remove alpha channel
if (baton->flatten && sharp::HasAlpha(image)) { if (baton->flatten && image.has_alpha()) {
image = sharp::Flatten(image, baton->flattenBackground); image = sharp::Flatten(image, baton->flattenBackground);
} }
@ -392,12 +392,12 @@ class PipelineWorker : public Napi::AsyncWorker {
bool const shouldSharpen = baton->sharpenSigma != 0.0; bool const shouldSharpen = baton->sharpenSigma != 0.0;
bool const shouldComposite = !baton->composite.empty(); bool const shouldComposite = !baton->composite.empty();
if (shouldComposite && !sharp::HasAlpha(image)) { if (shouldComposite && !image.has_alpha()) {
image = sharp::EnsureAlpha(image, 1); image = sharp::EnsureAlpha(image, 1);
} }
VipsBandFormat premultiplyFormat = image.format(); VipsBandFormat premultiplyFormat = image.format();
bool const shouldPremultiplyAlpha = sharp::HasAlpha(image) && bool const shouldPremultiplyAlpha = image.has_alpha() &&
(shouldResize || shouldBlur || shouldConv || shouldSharpen); (shouldResize || shouldBlur || shouldConv || shouldSharpen);
if (shouldPremultiplyAlpha) { if (shouldPremultiplyAlpha) {
@ -725,9 +725,7 @@ class PipelineWorker : public Napi::AsyncWorker {
} }
// Ensure image to composite is sRGB with unpremultiplied alpha // Ensure image to composite is sRGB with unpremultiplied alpha
compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB); 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(); if (composite->premultiplied) compositeImage = compositeImage.unpremultiply();
// Calculate position // Calculate position
int left; int left;
@ -828,7 +826,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// Extract channel // Extract channel
if (baton->extractChannel > -1) { if (baton->extractChannel > -1) {
if (baton->extractChannel >= image.bands()) { if (baton->extractChannel >= image.bands()) {
if (baton->extractChannel == 3 && sharp::HasAlpha(image)) { if (baton->extractChannel == 3 && image.has_alpha()) {
baton->extractChannel = image.bands() - 1; baton->extractChannel = image.bands() - 1;
} else { } else {
(baton->err) (baton->err)
@ -1052,7 +1050,7 @@ class PipelineWorker : public Napi::AsyncWorker {
} else if (baton->formatOut == "dz") { } else if (baton->formatOut == "dz") {
// Write DZ to buffer // Write DZ to buffer
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP; baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
if (!sharp::HasAlpha(image)) { if (!image.has_alpha()) {
baton->tileBackground.pop_back(); baton->tileBackground.pop_back();
} }
image = sharp::StaySequential(image, baton->tileAngle != 0); image = sharp::StaySequential(image, baton->tileAngle != 0);
@ -1256,7 +1254,7 @@ class PipelineWorker : public Napi::AsyncWorker {
if (isDzZip) { if (isDzZip) {
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP; baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
} }
if (!sharp::HasAlpha(image)) { if (!image.has_alpha()) {
baton->tileBackground.pop_back(); baton->tileBackground.pop_back();
} }
image = sharp::StaySequential(image, baton->tileAngle != 0); image = sharp::StaySequential(image, baton->tileAngle != 0);

View File

@ -58,7 +58,7 @@ class StatsWorker : public Napi::AsyncWorker {
baton->channelStats.push_back(cStats); baton->channelStats.push_back(cStats);
} }
// Image is not opaque when alpha layer is present and contains a non-mamixa value // 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<double>(stats.getpoint(STAT_MIN_INDEX, bands).front()); double const minAlpha = static_cast<double>(stats.getpoint(STAT_MIN_INDEX, bands).front());
if (minAlpha != sharp::MaximumImageAlpha(image.interpretation())) { if (minAlpha != sharp::MaximumImageAlpha(image.interpretation())) {
baton->isOpaque = false; baton->isOpaque = false;

View File

@ -233,10 +233,10 @@ Napi::Value _maxColourDistance(const Napi::CallbackInfo& info) {
double maxColourDistance; double maxColourDistance;
try { try {
// Premultiply and remove alpha // 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)); 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)); image2 = image2.premultiply().extract_band(1, VImage::option()->set("n", image2.bands() - 1));
} }
// Calculate colour distance // Calculate colour distance