Skip to content

Commit fd8c30b

Browse files
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 <[email protected]>
1 parent 18464d9 commit fd8c30b

File tree

4 files changed

+36
-2
lines changed

4 files changed

+36
-2
lines changed

.changes/image-premultiply-fix.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'tauri-cli': 'patch:bug'
3+
'@tauri-apps/cli': 'patch:bug'
4+
---
5+
6+
Premultiply Alpha before Resizing which gets rid of the gray fringe around the icons.

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/tauri-cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ uuid = { version = "1", features = ["v5"] }
113113
rand = "0.9"
114114
zip = { version = "4", default-features = false, features = ["deflate"] }
115115
which = "8"
116+
rayon = "1.10"
116117

117118
[dev-dependencies]
118119
insta = "1"

crates/tauri-cli/src/icon.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ use image::{
2525
png::{CompressionType, FilterType as PngFilterType, PngEncoder},
2626
},
2727
imageops::FilterType,
28-
open, DynamicImage, ExtendedColorType, GenericImageView, ImageBuffer, ImageEncoder, Rgba,
28+
open, DynamicImage, ExtendedColorType, GenericImageView, ImageBuffer, ImageEncoder, Pixel, Rgba,
2929
};
30+
use rayon::iter::ParallelIterator;
3031
use resvg::{tiny_skia, usvg};
3132
use serde::Deserialize;
3233

@@ -136,7 +137,32 @@ impl Source {
136137
let img_buffer = ImageBuffer::from_raw(size, size, pixmap.take()).unwrap();
137138
Ok(DynamicImage::ImageRgba8(img_buffer))
138139
}
139-
Self::DynamicImage(i) => Ok(i.resize_exact(size, size, FilterType::Lanczos3)),
140+
Self::DynamicImage(image) => {
141+
// `image` does not use premultiplied alpha in resize, so we do it manually here,
142+
// see https://github.com/image-rs/image/issues/1655
143+
//
144+
// image.resize_exact(size, size, FilterType::Lanczos3)
145+
146+
// Premultiply alpha
147+
let premultiplied_image =
148+
ImageBuffer::from_par_fn(image.width(), image.height(), |x, y| {
149+
let mut pixel = image.get_pixel(x, y);
150+
let alpha = pixel.0[3] as f32 / u8::MAX as f32;
151+
pixel.apply_without_alpha(|channel_value| (channel_value as f32 * alpha) as u8);
152+
pixel
153+
});
154+
155+
let mut resized =
156+
image::imageops::resize(&premultiplied_image, size, size, FilterType::Lanczos3);
157+
158+
// Unmultiply alpha
159+
resized.par_pixels_mut().for_each(|pixel| {
160+
let alpha = pixel.0[3] as f32 / u8::MAX as f32;
161+
pixel.apply_without_alpha(|channel_value| (channel_value as f32 / alpha) as u8);
162+
});
163+
164+
Ok(DynamicImage::ImageRgba8(resized))
165+
}
140166
}
141167
}
142168
}

0 commit comments

Comments
 (0)