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

Simplify RGB to HSV conversion #8432

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open

Conversation

Yay295
Copy link
Contributor

@Yay295 Yay295 commented Oct 1, 2024

An incorrect hue was being calculated when "h/6 is negative" because Python's % and C's fmod use a slightly different strategy. So to match Python, we need to implement it ourselves. It can also be simplified some due to the second parameter being 1.

The starting hue calculation has also been simplified by inlining some values. ex.:

h = bc - gc;
h = (((float)(maxc - b)) / cr) - (((float)(maxc - g)) / cr);
h = ((maxc - b) - (maxc - g)) / cr;
h = (maxc - b - maxc + g) / cr;
h = (g - b) / cr

@Yay295
Copy link
Contributor Author

Yay295 commented Oct 1, 2024

For the fmod simplification, fmod looks like this (if it wasn't implemented in assembly):

double fmod(double x, double y) {
    return x - y * trunc(x / y);
}

Since y is 1.0 in our case, this becomes just x - trunc(x). And then replace trunc with floor to match Python.

@Yay295
Copy link
Contributor Author

Yay295 commented Oct 1, 2024

I've been trying to tweak this code for long enough that I forgot the + 1.0 in the original code (fmod((h / 6.0 + 1.0), 1.0)) already handles the negative values. So this change doesn't fix anything; it's just optimizations.

@Yay295 Yay295 changed the title Handle negative intermediate value in rgb to hsv conversion Simplify rgb to hsv conversion Oct 1, 2024
@radarhere
Copy link
Member

From what I can see, there are four basic changes here.

  1. You've rearranged the declarations. I don't think there's a need to change them being grouped at the start. It is a pattern used elsewhere, e.g.
    frompalette(Imaging imOut, Imaging imIn, const char *mode) {
    ImagingSectionCookie cookie;
    int alpha;
    int y;
    void (*convert)(UINT8 *, const UINT8 *, int, ImagingPalette);
  2. You've rearranged math operations to divide once rather than twice.
  3. You've replaced fmod with other code. From the back and forth comments, I'm a bit confused about why you've done that?
  4. You've removed CLIP8. Could you explain why?

@hugovk
Copy link
Member

hugovk commented Oct 1, 2024

re: 1., I think declarations at the start of a block was required by the C89 standard, but is no longer by modern compilers. And I find declaring on first use more readable, rather than have to refer back.

@Yay295
Copy link
Contributor Author

Yay295 commented Oct 1, 2024

You've replaced fmod with other code. From the back and forth comments, I'm a bit confused about why you've done that?

Originally I did it because the existing comment confused me, thinking negative values weren't being handled correctly. But the new code does replace fmod with a simple floor, so it should be slightly faster.

You've removed CLIP8. Could you explain why?

Because of the fmod, h at that point will be between 0 and 1, so there's no need to clip it after multiplying by 255 because it will already be between 0 and 255.

s in the original code is 255.0 * (float)(maxc - minc) / (float)(maxc). (float)(maxc - minc) / (float)(maxc) will be between 0 and 1, so again the result will already be between 0 and 255. This calculation can also be done without converting anything to floating-point, so I did that too.

src/libImaging/Convert.c Outdated Show resolved Hide resolved
@radarhere
Copy link
Member

So this change doesn't fix anything; it's just optimizations.

To be clear, you're saying that this PR should not adjust the output data?

@Yay295
Copy link
Contributor Author

Yay295 commented Oct 2, 2024

Correct

@radarhere
Copy link
Member

radarhere commented Oct 3, 2024

To see if the output stayed the same, I converted hopper to HSV, and saved the output to a file for comparison - radarhere@8811b00 / https://github.com/radarhere/Pillow/actions/runs/11159666541

When I introduced this change, the test started failing - meaning that this does change the output.

@hugovk
Copy link
Member

hugovk commented Oct 3, 2024

See also python/cpython#124880 for a similar CPython change to colorsys.rgb_to_hsv(), which the Pillow function was originally based on.

@radarhere radarhere changed the title Simplify rgb to hsv conversion Simplify RGB to HSV conversion Oct 3, 2024
@Yay295 Yay295 force-pushed the patch-2 branch 2 times, most recently from 25acdbf to c24ece0 Compare October 7, 2024 13:51
src/libImaging/Convert.c Outdated Show resolved Hide resolved
src/libImaging/Convert.c Outdated Show resolved Hide resolved
Yay295 and others added 2 commits October 12, 2024 18:31
By inlining some values, the starting hue calculation can be reduced from using three subtractions and two divisions to using one subtraction and one division.
"fmod(n+1,1)" can be replaced with "n - floor(n)" due to the second parameter being 1.
The saturation calculation can be done without floating point values.
The "CLIP8"s are unnecessary because the values are already in the correct range.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants