Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blur for images with alpha is incorrect, bleeds color from transparent pixels #2324

Open
Shnatsel opened this issue Sep 13, 2024 · 4 comments
Labels
kind: bug kind: incorrect This bug causes wrong image data

Comments

@Shnatsel
Copy link
Contributor

With this test image the blur implementation shows artifacts:

blur-test-input

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:

blur-test-input fast

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:

blurred-gimp-20px

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 #2302 (comment), moving it to the issue tracker so that it doesn't get lost.

Tested on image from git on commit 98ceb71

@Shnatsel Shnatsel added kind: bug kind: incorrect This bug causes wrong image data labels Sep 13, 2024
@torfmaster
Copy link
Contributor

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.

@Shnatsel
Copy link
Contributor Author

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

@Shnatsel
Copy link
Contributor Author

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

@kornelski
Copy link
Contributor

Yes, premultiplied alpha color space is the right answer here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind: bug kind: incorrect This bug causes wrong image data
Projects
None yet
Development

No branches or pull requests

3 participants