Skip to content

Conversation

youssefelzedy
Copy link

This PR adds median filtering capability to the existing blur node, providing users with a third filtering option alongside Gaussian and box blur.

Changes Made

  • Added median boolean parameter to the blur node
  • Implemented median_filter_algorithm() function that processes each color channel independently
  • Added median_quickselect() helper function using select_nth_unstable_by() for O(n) median calculation
  • Updated node documentation to describe median filter use case
  • Maintains existing gamma space support for consistency

Use Case

Median filtering is particularly useful for:

  • Removing salt-and-pepper noise from images
  • Preserving sharp edges while reducing noise
  • Situations where traditional blur would be too destructive to image details

Technical Notes

  • Uses quickselect algorithm (O(n) average case) instead of full sorting (O(n log n))
  • Operates on square neighborhoods defined by radius parameter
  • Processes RGBA channels independently to maintain color fidelity
  • Integrates seamlessly with existing blur node UI and parameters

The implementation follows the same pattern as existing blur algorithms with proper gamma space handling and associated alpha processing.

Closes feature in Issue No. #912

- Add median parameter to blur function for noise reduction
- Implement median_filter_algorithm with efficient quickselect
- Support gamma space calculations for median filtering
- Preserve edges while removing noise, complementing existing blur options

Feature in Issue: GraphiteEditor#912
@youssefelzedy youssefelzedy changed the title Add median filter option to blur node Feature: Add median filter option to blur node Sep 19, 2025
@Keavon Keavon requested a review from TrueDoctor September 22, 2025 08:09
Copy link
Member

@TrueDoctor TrueDoctor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Keavon the code looks fine, but I have not checked if the functionality behaves as you would expect

fn median_quickselect(values: &mut [f32]) -> f32 {
let mid: usize = values.len() / 2;
// nth_unstable is like quickselect: average O(n)
*values.select_nth_unstable_by(mid, |a, b| a.partial_cmp(b).unwrap()).1

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am still new to rust and this repo, so I might be wrong here. could unwrap() potentially panic in some cases? If that happens the function wouldn't return f32. Also, what happens if values is empty?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They would only panic if NAN values are compared, which should never happen since those are not valid colors. But it will panic on empty Images

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case, it's good to check for that condition.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, what I meant to say is it will panic if the function is called with an empty range, but for an empty image it won't because the function will never be called so this is fine

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about replacing partial_cmp(...).unwrap() with f32::total_cmp to avoid panics on NaN values?
Also, I think for even-length slices the current logic picks only the upper median (len/2).
If accuracy matters, the median should be computed as the average of the two middle values instead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That should work. Regarding the median averaging, I don't know what the expected result would be (pinging @Keavon) the disadvantage of averaging is that we can now create brightness values which never appear in the artwork. I don't know if that is important for anything. Computing the second value should be relatively cheap though, which is good

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, it works fine. I tested it before making a PR, but I'm not sure about the performance, and maybe I didn't recognize something else. We should wait until @Keavon makes a last review of the code.

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.

3 participants