diff --git a/.changes/image-premultiply-fix.md b/.changes/image-premultiply-fix.md new file mode 100644 index 000000000000..4abe9b867f56 --- /dev/null +++ b/.changes/image-premultiply-fix.md @@ -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. diff --git a/Cargo.lock b/Cargo.lock index cd5f07d458c6..5e1ff78a893f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8667,6 +8667,7 @@ dependencies = [ "plist", "pretty_assertions", "rand 0.9.1", + "rayon", "regex", "resvg", "semver", diff --git a/crates/tauri-cli/Cargo.toml b/crates/tauri-cli/Cargo.toml index c9f4b79711d4..d1300b9e925d 100644 --- a/crates/tauri-cli/Cargo.toml +++ b/crates/tauri-cli/Cargo.toml @@ -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" diff --git a/crates/tauri-cli/src/icon.rs b/crates/tauri-cli/src/icon.rs index e072d2c5aae6..474fb8ad65c9 100644 --- a/crates/tauri-cli/src/icon.rs +++ b/crates/tauri-cli/src/icon.rs @@ -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)) + } } } }