fix: premultiply alpha before resizing (fix #14351) (#14353)

* fix: Premultiply alpha before resizing

* feat: Use rayon for process speedup

* Fix change tag

* `cargo fmt`

* Document reasoning & use imageops::resize directly

---------

Co-authored-by: Tony <legendmastertony@gmail.com>
This commit is contained in:
Chase Knowlden
2025-11-03 22:16:11 -05:00
committed by GitHub
parent 18464d9481
commit fd8c30b4f1
4 changed files with 36 additions and 2 deletions

View File

@@ -0,0 +1,6 @@
---
'tauri-cli': 'patch:bug'
'@tauri-apps/cli': 'patch:bug'
---
Premultiply Alpha before Resizing which gets rid of the gray fringe around the icons.

1
Cargo.lock generated
View File

@@ -8667,6 +8667,7 @@ dependencies = [
"plist",
"pretty_assertions",
"rand 0.9.1",
"rayon",
"regex",
"resvg",
"semver",

View File

@@ -113,6 +113,7 @@ uuid = { version = "1", features = ["v5"] }
rand = "0.9"
zip = { version = "4", default-features = false, features = ["deflate"] }
which = "8"
rayon = "1.10"
[dev-dependencies]
insta = "1"

View File

@@ -25,8 +25,9 @@ use image::{
png::{CompressionType, FilterType as PngFilterType, PngEncoder},
},
imageops::FilterType,
open, DynamicImage, ExtendedColorType, GenericImageView, ImageBuffer, ImageEncoder, Rgba,
open, DynamicImage, ExtendedColorType, GenericImageView, ImageBuffer, ImageEncoder, Pixel, Rgba,
};
use rayon::iter::ParallelIterator;
use resvg::{tiny_skia, usvg};
use serde::Deserialize;
@@ -136,7 +137,32 @@ impl Source {
let img_buffer = ImageBuffer::from_raw(size, size, pixmap.take()).unwrap();
Ok(DynamicImage::ImageRgba8(img_buffer))
}
Self::DynamicImage(i) => Ok(i.resize_exact(size, size, FilterType::Lanczos3)),
Self::DynamicImage(image) => {
// `image` does not use premultiplied alpha in resize, so we do it manually here,
// see https://github.com/image-rs/image/issues/1655
//
// image.resize_exact(size, size, FilterType::Lanczos3)
// Premultiply alpha
let premultiplied_image =
ImageBuffer::from_par_fn(image.width(), image.height(), |x, y| {
let mut pixel = image.get_pixel(x, y);
let alpha = pixel.0[3] as f32 / u8::MAX as f32;
pixel.apply_without_alpha(|channel_value| (channel_value as f32 * alpha) as u8);
pixel
});
let mut resized =
image::imageops::resize(&premultiplied_image, size, size, FilterType::Lanczos3);
// Unmultiply alpha
resized.par_pixels_mut().for_each(|pixel| {
let alpha = pixel.0[3] as f32 / u8::MAX as f32;
pixel.apply_without_alpha(|channel_value| (channel_value as f32 / alpha) as u8);
});
Ok(DynamicImage::ImageRgba8(resized))
}
}
}
}