Blur for images with alpha is incorrect, bleeds color from transparent pixels
With this test image the blur implementation shows artifacts:
It's just a white rectangle in the middle, surrounded by fully transparent pixels.
Gaussian blur in image bleeds color from fully transparent pixels into the white rectangle, resulting in the following image:
The fully transparent pixels have R set to 255 while all the other channels are set to 0, and that red color bleeds into the white rectangle.
The expected blur as produced by GIMP is like this - blurred white rectangle without any red color:
To avoid such artifacts, the pixel's contribution to the color change should be weighted by its alpha channel value. Fully transparent pixels should get multiplied by 0 and not contribute to the changes in non-alpha channels at all.
Code used for testing:
use std::env;
use std::error::Error;
use std::path::Path;
use std::process;
fn main() -> Result<(), Box<dyn Error>> {
// Collect command-line arguments
let args: Vec<String> = env::args().collect();
// Ensure that we have 2 CLI arguments
if args.len() != 3 {
eprintln!("Usage: {} <path> <radius>", args[0]);
process::exit(1);
}
let radius: f32 = args[2].parse()?;
// Load the input image
let path_str = &args[1];
let path = Path::new(path_str);
let input = image::open(path)?;
// Correct but slow Gaussian blur
let mut out_path_gauss = path.to_owned();
out_path_gauss.set_extension("gaussian.png");
let blurred = input.blur(radius);
blurred.save(out_path_gauss)?;
Ok(())
}
Originally found in https://github.com/image-rs/image/pull/2302#issuecomment-2346108041, moving it to the issue tracker so that it doesn't get lost.
Tested on image from git on commit 98ceb7197f5d8469b9b2364fbf9d671ce161ae97
Wow, good catch! I think this is a general issue coming from image::imageops::sample::horizontal_sample which basically affects all filters defined by convolution with 2d functions. I think your suggested fix (using the alpha channel as weight) is correct and I am willing to implement this as long as this is the suggested fix.
I am not 100% certain this is the correct fix. I am not a blur expert and I may be missing something. It's probably worth researching this.
Or, if you want a shortcut on the research, here's what ChatGPT says about it: https://chatgpt.com/share/66e8069d-6198-800f-8ca4-dfd8fb5360ae
This StackOverflow answer seems to suggest the same thing, with the contributions being weighted by alpha naturally if the RBG values are multiplied by alpha first: https://computergraphics.stackexchange.com/a/5517
Yes, premultiplied alpha color space is the right answer here.
I've added utilities for premultiplying by alpha:
https://github.com/image-rs/image/blob/ee7c5d979cb503ed0a78b984542af990dd7ecd87/src/imageops/resize.rs#L156-L197
However, they currently only work for DynamicImage because Subpixel trait is not expressive enough and does not provide the necessary bounds.