Skip to content

Blur using sigma and document as radius#2848

Open
RunDevelopment wants to merge 2 commits intoimage-rs:mainfrom
RunDevelopment:blur-sigma
Open

Blur using sigma and document as radius#2848
RunDevelopment wants to merge 2 commits intoimage-rs:mainfrom
RunDevelopment:blur-sigma

Conversation

@RunDevelopment
Copy link
Copy Markdown
Member

Resolves #2842

Changes:

  • The f32 parameter blur and DynamicImage::blur is now the standard deviation of the Gaussian blur, aka. sigma. The parameter is called sigma to be consistent with other functions. The documentation of all blurring-related functionality has been changed to describe the sigma parameter as the "radius of the blur". This is heavily inspired by the MDN docs for blur.
  • All uses of blur_advanced(GaussianBlurParameters::new_from_sigma(x)) have been replaced with blur(x).

Since the motivation for not using sigma was UX, I hope that more documentation solves the same issue without needing a separate new definition of "radius." I think the new documentation should adequately convey how to use sigma parameters even if users don't know what a standard deviation or normal distribution is.

@awxkee Do you think this is enough, or should I rename all relevant sigma parameters to radius as well?

Also, should I remove GaussianBlurParameters::new_from_radius? It's unused now. If we go with parameter renaming, I think it should definitely be removed, because it's going to be confusing otherwise.

@awxkee
Copy link
Copy Markdown
Contributor

awxkee commented Mar 12, 2026

I'd prefer to keep name "radius" as it's actually much more common for casual or other platforms users. I believe anyone who actually wants to investigate will understand that "radius" means "sigma" in any case, and both groups will get the same result.
In addition the doc line ``sigma - Radius of the blur. reads to me a bit controversial currently.

Also, should I remove GaussianBlurParameters::new_from_radius? It's unused now. If we go with parameter renaming, I think it should definitely be removed, because it's going to be confusing otherwise.

Probably, yes.


Last time we changed the implementation, we broke it for some users, and now we're going to do so again silently. There isn't any obvious solution, but it might be nice if we break the API in a way that at least forces them to take a look on it. It’s probably another matter, but we could change the type to f64 just to achieve that. This is rather remainder to think, or for another PR if you get sign off for this one.

@197g
Copy link
Copy Markdown
Member

197g commented Mar 12, 2026

I really don't know if it's a good idea. Having a constructor that is defined via a kernel size, or 3-sigma, still makes more sense to me than saying radius=sigma. Resolving this for colleagues from different domains involves, in other APIs, choosing a name that is not contended with a pre-existing meaning and providing doc(alias=) hints for finding the intended function to call where necessary. I really don't want to silently break DynamicImage::blur again.

I'd rather deprecate the float-parameter version entirely or make it an alias to blur_advanced (because having an advanced version as the only alternative seems silly). We have strong types in Rust so this should not be super surprising even to beginners. And the lesson from 19 years of broken time in JS is not to trust Browsers to have sensible types and naming and that sometimes having more types is good expressiveness, not an thought overhead.

@RunDevelopment
Copy link
Copy Markdown
Member Author

I don't super like the idea of only blur being different again. fast_blur and unsharpen also use sigma, so I don't see why blur should be different.

I see the argument for silent breakage, but I'm only comfortable making this change because the next version is 1.0. Breaking changes are to be expected there, and a change that brings blur in line with the others is arguably a fixing change.

I'd rather deprecate the float-parameter version entirely or make it an alias to blur_advanced (because having an advanced version as the only alternative seems silly). We have strong types in Rust so this should not be super surprising even to beginners.

Sounds like you're proposing fn blur(img, params: GaussianBlurParams) -> Image, and I'm not a huge fan of that. It's unnecessarily complex IMO. Blurring an image is a conceptually simple operation, so it should be simple to use.

If we go with this, then I think we should at least impl From<f32> for GaussianBlurParams and take params: impl Into<GaussianBlurParams>, so users can continue to write img.blur(12.34).

in other APIs

Speaking of other APIs, what are other libraries doing? So I did a little survey. I asked Gemini to give me a list of popular image processing libraries in different programming languages. Probably not the best way of doing this, but I simply don't know many such libraries across languages.

Survey details

So here's the list and what they are doing for (Gaussian) blurring:

  • Python/C++ OpenCV: Their GaussianBlur takes both sigma and kernel size without clear preference.
  • Python Pillow: GaussianBlur calls its parameter "radius" but the docs clarify that it's sigma.
  • Python Scikit-Image: gaussian takes a sigma.
  • Python Mahotas: gaussian_filter takes a sigma.
  • Java BoofCV: gaussian takes sigma and int radius (which is mapped to kernel size =2*r+1). Their examples seem to prefer radius.
  • Java JavaCV: Wrapper around OpenCV.
  • Java ImageJ: GaussianBlur uses sigma. (Deprecated API calls sigma "radius".)
  • Java ZXing: Excluded because it's a library for reading barcodes.
  • C++ Dlib: gaussian_blur uses sigma.
  • C++ Cimg: blur uses sigma.
  • C++ GEGL: gaussian-blur calls its parameter "size" but it's sigma
  • JS Sharp: blur uses sigma.
  • JS Tensorflow.js: Excluded because it doesn't have a blurring function or similar AFAICT.
  • JS JimP: blur calls it radius, but it's actually a sigma (had to check the code).
  • JS Farbric.js: Excluded because their blur is doing something unholy with central limit. It's unclear to me what the number they take corresponds to.
  • Rust image: Exclude for obvious reasons.
  • Rust imageproc: Uses sigma, but also excluded for obvious reasons.
  • Rust Candle: Excluded because it doesn't have a blur function AFAICT.
  • C# SixLabors.ImageSharp: GaussianBlur uses sigma.
  • C# SkiaSharp: SKImageFilter.CreateBlur uses sigma.
  • C# OpenCvSharp: Same as OpenCV.
  • C# Magick.NET: See below.
  • C# System.Drawing.Common: Excluded because it's deprecated and because it's radius is weird.

Lastly, I also want to include ImageMagick. Its -blur option takes a radius and sigma with a clear preference for sigma. Radius is encouraged to be left as 0 to let ImageMagick pick a value.

In summary:

  • The following libraries only take a sigma parameter without options to specify a kernel radius/size:
    • Python Pillow
    • Python Scikit-Image
    • Python Mahotas
    • Java ImageJ
    • C++ Cimg
    • C++ GEGL
    • JS Sharp
    • JS JimP
    • C# SixLabors.ImageSharp
    • C# SkiaSharp
  • The following libraries take both a sigma and kernel size/radius with clear preference for sigma:
    • ImageMagick
    • C++ Dlib (only let's you specify a maximum kernel size, not a kernel size directly)
  • The following libraries take both a sigma and kernel size/radius without clear preference:
    • OpenCV
  • The following libraries take both a sigma and kernel size/radius with clear preference for kernel size/radius:
    • Java BoofCV

Overall, the trend is clearly. Sigma is the most common way of specifying the parameters of a Gaussian blur.

@awxkee
Copy link
Copy Markdown
Contributor

awxkee commented Mar 13, 2026

@fintelia
Copy link
Copy Markdown
Contributor

On the 0.25.x series we use sigma as the argument for all the blur methods. We previously merged an API breaking change to instead using radius for the 1.0 series. But based on all the discussion so far here, it isn't sounding like switching would be worth the disruption?

@awxkee
Copy link
Copy Markdown
Contributor

awxkee commented Mar 13, 2026

I mostly want to make sure the main API is easy and obvious to use. If everyone agrees that using sigma with proper documentation is fine for that purpose, then I don't see a problem. Both are fine with me. I personally think radius might be a bit more intuitive, though sigma is also fine.
This API breakage could be solved by changing the sigma type to f64, which would at least force users to visit the method and if we're lucky, they'll check whether anything changed. Ofc this isn't perfect, but it could work.

@RunDevelopment
Copy link
Copy Markdown
Member Author

RunDevelopment commented Mar 13, 2026

Is there even API breakage?

As @fintelia said, 0.25.x uses sigma. Even 0.25.10 still does. This PR basically reverts the breaking change (=switching to radius using ::new_from_radius).

@awxkee
Copy link
Copy Markdown
Contributor

awxkee commented Mar 13, 2026

Yeah, there is an API breakage. The previous implementation was completely wrong, so we had to silently change it to at least a correct version, which caused complaints. Now we want to do it again.

@awxkee
Copy link
Copy Markdown
Contributor

awxkee commented Mar 13, 2026

Ah, no, it seems it’s actually still sigma. Then, there’s probably no API breakage. I actually thought radius was published.

@RunDevelopment
Copy link
Copy Markdown
Member Author

Yep, just tested it and 0.25.10 uses sigma and produces the exact same output as blur_advanced(new_from_sigma(x)) on main. So this PR indeed reverts a breaking change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Gaussian blur API is inconsistent with image editing programs

4 participants