Jump to content

Hue adjustment algorithm is not optimal


Goblin Alchemist

Recommended Posts

Create a new square image, draw a black-to-red gradient from left to right. Add a new layer, draw a black-to-green gradient from bottom to top. Change the second layer mode to "Additive" and flatten the image. You get a smooth four-color gradient.

 

6f38b8d193bbc40fb9bf41bfc3f96ad6.jpg

 

Now go to the Hue/Saturation dialog and start dragging the Hue slider. You will immediately see that something weird is happening. For example, with Hue = 20 you get the following:

 

12ff7a0148b08f6bff542c13168e5e38.jpg
 

In the middle of a perfectly smooth image two sharp edges have appeared. One could say it's a rendering or display artifact? No, it isn't. Launch the Hue/Saturation dialog again, reset Hue to 0 and drag the Saturation slider to the left end:

 

05f8a89321beeaeb5ee467af8441fea6.jpg

 

In grayscale the new edges are clearly visible.

 

So, adjusting the hue of smooth images (photos etc.) introduces weird artifacts which may make it impossible to use the hue adjustment at all. In fact, only Hue = 120 and Hue = -120 yield a smooth result.

 

I guess I know why it's happening. It's the simplified algorithm of RGB to HSV conversion:

 

saturation = max(R,G, B) - min(R,G, B)

lightness = (min(R,G, B) + max(R,G, B)) / 2

 

This algorithm is fast and reversible, but not smooth in the RGB and HSV spaces. In some areas of the image, for example, where R=G, even though the colors are smooth gradients, this algorithm introduces a crease. (Geometrically, in the RGB space the surface of constant saturation is a hexagonal prism, hence the creases).

 

I guess it would be nice to use another algorithm which performs the color space conversion smoothly, without creases and edges. Something like:

 

saturation = sqrt(R² + G² + B² - (R+G+ B)² / 3)

lightness = (R + G + B) / 3

 

In this algorithm, the surface of constant saturation is a cylinder, and no creases and edges should occur. On the other hand, this algorithm is not 100% reversible, i.e. after changing hue the resulting color may happen to be outside the RGB cube and will have to be clamped; but this happens only for extreme 100% saturated colors which are rare in real photos. And this will be compensated by the general smoothness improvement.

Link to comment
Share on other sites

I don't know guy, I could be wrong but, to me it looks like your rotating a moire pattern around due to the modular mathematics shifting the grey scales which are already in an opposing pattern. In other words the as the 255's become zero, the zeros become 128's and the 128's become 255's the moire pattern evolves especially since the images are initially out of phase. 

 

 

 

Geometrically, in the RGB space the surface of constant saturation is a hexagonal prism, hence the creases

Isn't the RGB color space a cube and constant saturation the euclidan distance from zero, therefore a dome?

 

Wouldn't changing the way HSV is calculated warp the hexacone colors space and therefore can no longer be considered HSV?

Go out there and be amazing. Have Fun, TR
TRsSig.png?raw=1
Some Pretty Pictures Some Cool Plugins

Link to comment
Share on other sites

^^ Does the 'artifact' move if you shift the top layer with the arrow keys?
(I'm on 3.5.11 and cannot repro that pattern).

White is HSV saturation 0, so is black. Pure Red, Green and Blue are 100, so are Magenta,Cyan and Yellow.
I would expect the other 'rainbow' colours to have slightly less saturation but they don't - which would imply a cylindrical colour space?

I think of the colour space as a cube with a corner at black then at RGB then MCY with white at the opposite corner (should do a picture really!). And wouldn't saturation be the distance to the colour (RGB = xyz coords) perpendicular to the line from black to white?
Perhaps I should think of a cylinder - but that seems wrong as it would give too many shades of black. :/

off topic:
It worries me that any effect using an RGB to HSV conversion and back may lose information if the HSV values are integers. (RGB = 255 * 255 * 255 = 16581375 colours, HSV = 100 * 100 * 360 = 3600000 colours). I'm sure this has been considered but may be relevant.

Interesting thread - and I'm completely out of my depth. :P

I would think 'real' colour space is a very complex (non geometric) space and the maths would be a killer (for me atleast).
Something between a double cone and a globe with black and white as 'poles' and various distortions for human eyesight.
 

 

Red ochre Plugin pack.............. Diabolical Drawings ................Real Paintings

 

PdnForumSig2.jpg

Link to comment
Share on other sites

I would think 'real' colour space is a very complex (non geometric) space and the maths would be a killer (for me atleast).

Something between a double cone and a globe with black and white as 'poles' and various distortions for human eyesight.

a double cone is an HSL color space. Complex indeed, color spaces are artificial constructs to catalog what is a purely analog phenomena - the continuously decreasing wavelength and increasing frequencies of electromagnetism. We are trying to quantify and interpret the un-quantifiable, you can never have a resolution small enough. Perhaps a photon.

That being said this code performs the same demo in RGB - since RGB is a cube the creases move laterally and not in a circular fashion.Use it on the Alchemists example.

Hidden Content:
#region UICodeint Amount1 = 0; // [0,255] Rint Amount2 = 0; // [0,255] G Descriptionint Amount3 = 0; // [0,255] B#endregionvoid Render(Surface dst, Surface src, Rectangle rect){    ColorBgra CurrentPixel;    for (int y = rect.Top; y < rect.Bottom; y++)    {        if (IsCancelRequested) return;        for (int x = rect.Left; x < rect.Right; x++)        {            CurrentPixel = src[x,y];             int r = Math.Abs((int)CurrentPixel.R - Amount1);             int g = Math.Abs((int)CurrentPixel.G - Amount2);             int b = Math.Abs((int)CurrentPixel.B - Amount3);             CurrentPixel= ColorBgra.FromBgraClamped(b,g,r,255);            dst[x,y] = CurrentPixel;        }    }}

It's merely to show the effect is about out of phase patterns and not HSV since we are using RGB.

Go out there and be amazing. Have Fun, TR
TRsSig.png?raw=1
Some Pretty Pictures Some Cool Plugins

Link to comment
Share on other sites

I don't know guy, I could be wrong but, to me it looks like your rotating a moire pattern around due to the modular mathematics shifting the grey scales which are already in an opposing pattern. In other words the as the 255's become zero, the zeros become 128's and the 128's become 255's the moire pattern evolves especially since the images are initially out of phase. 

Sorry I didn't understand the thing about moire patterns. I don't have moire patterns there, I have a smooth gradient. And red, yellow and green are not opposing each other.

 

Isn't the RGB color space a cube and constant saturation the euclidan distance from zero, therefore a dome?

Yes, the RGB color space is a cube. But Euclidean distance from zero (black) is not saturation, it's rather a kind of lightness or brightness: white is also distant from zero. Saturation is the Euclidean distance from non-saturated colors, or the black-white diagonal of the cube, or the grayscale axis. Geometrically it would be a cylinder. But calculating the exact distance requires some square roots, so I believe a faster algorithm is employed, with simple comparisons and subtractions. But this fast agorithm is calculating something different than distance, so the surface of constant saturation becomes a hexagonal prism around the grayscale axis.

d788ba9d3e9b2a67d2f8a6cc3e7b9752.jpg

 

 

Wouldn't changing the way HSV is calculated warp the hexacone colors space and therefore can no longer be considered HSV?

I would rather argue that the current implementation is not HSV.  ;)   For example, "V" in HSV stands for Value, or lightness / brightness. Draw something in bright yellow color and change its hue by 180. The color will become deep blue, which is obviously darker, i.e. has different V (value).

 

 

^^ Does the 'artifact' move if you shift the top layer with the arrow keys?

(I'm on 3.5.11 and cannot repro that pattern).

 

I have flattened the image and merged the two layers into one. I believe 3.5.x should work the same in this aspect.

 

 

White is HSV saturation 0, so is black. Pure Red, Green and Blue are 100, so are Magenta,Cyan and Yellow.

I would expect the other 'rainbow' colours to have slightly less saturation but they don't - which would imply a cylindrical colour space?

I think of the colour space as a cube with a corner at black then at RGB then MCY with white at the opposite corner (should do a picture really!). And wouldn't saturation be the distance to the colour (RGB = xyz coords) perpendicular to the line from black to white?

Perhaps I should think of a cylinder - but that seems wrong as it would give too many shades of black. :/

In the RGB color cube it's hard to define what are "rainbow colors". The six "pure" colors are in corners of the cube, and the line connecting them is a broken zigzag line. I'd rather not call it a "rainbow", the rainbow should be something smoother.  ;)

Yes, I think that saturation is the distance to the grayscale axis, and "pure" colors have 100% saturation. Calculating it exactly as Euclidean distance yields a cylinder passing though these six corners. Orange is an intermediate color between red and yellow, so in RGB space it is on the straight line connecting red and yellow, therefore it's inside the cylinder and has less than 100% saturation. 

8cdd72bf0a0eff1ba6342fe2dfec98f1.jpg

 

 

off topic:

It worries me that any effect using an RGB to HSV conversion and back may lose information if the HSV values are integers. (RGB = 255 * 255 * 255 = 16581375 colours, HSV = 100 * 100 * 360 = 3600000 colours). I'm sure this has been considered but may be relevant.

 

Yes, on every conversion the calculations may produce fractional numbers which are then rounded, which introduces a small rounding error (not larger than one color step, or 1/255th of the full color range). Such error is usually not perceptible, but if you make several HSV adjustments and then do strong contrast/gamma adjustments, it may become visible. That's why some professional software operates in 16-bit color spaces and uses image formats such as 16-bit TIFF.

 

 

I would think 'real' colour space is a very complex (non geometric) space and the maths would be a killer (for me atleast).

Something between a double cone and a globe with black and white as 'poles' and various distortions for human eyesight.

 

http://en.wikipedia.org/wiki/HSL_and_HSV

There is a double cone indeed  :)

 

this code performs the same demo 

Wow, code, something interesting. How do I run it?

Edited by Goblin Alchemist
Link to comment
Share on other sites

 

TechnoRobbo, on 26 Sept 2014 - 6:10 PM, said:

snapback.png

I don't know guy, I could be wrong but, to me it looks like your rotating a moire pattern around due to the modular mathematics shifting the grey scales which are already in an opposing pattern. In other words the as the 255's become zero, the zeros become 128's and the 128's become 255's the moire pattern evolves especially since the images are initially out of phase. 

Sorry I didn't understand the thing about moire patterns. I don't have moire patterns there, I have a smooth gradient. And red, yellow and green are not opposing each other.

the gradients from black our out of phase

 

 

 

TechnoRobbo, on 26 Sept 2014 - 6:10 PM, said:

snapback.png

Wouldn't changing the way HSV is calculated warp the hexacone colors space and therefore can no longer be considered HSV?

I would rather argue that the current implementation is not HSV.   ;)   For example, "V" in HSV stands for Value, or lightness / brightness. Draw something in bright yellow color and change its hue by 180. The color will become deep blue, which is obviously darker, i.e. has different V (value).

 

are you sure it's not the same S(Saturation). S affects V and vice versa. In other words if you change the V you can't maintain the S and if you change the S you cant maintain the V. 

 

 

 

TechnoRobbo, on 27 Sept 2014 - 4:44 PM, said:

snapback.png

this code performs the same demo 

Wow, code, something interesting. How do I run it?

 

CodeLab

Go out there and be amazing. Have Fun, TR
TRsSig.png?raw=1
Some Pretty Pictures Some Cool Plugins

Link to comment
Share on other sites

CodeLab

Cool, didn't know about this feature.  :)

 

Your code introduces creases explicitly with the Math.Abs function. But at first glance in the Hue/Saturation dialog there should be no creases, it should smoothly adjust colors and that's all. This is the problem.

Link to comment
Share on other sites

 

 

I would rather argue that the current implementation is not HSV.   ;)   For example, "V" in HSV stands for Value, or lightness / brightness. Draw something in bright yellow color and change its hue by 180. The color will become deep blue, which is obviously darker, i.e. has different V (value).

I tested that and it kept the same V.

watch it fullscreen for HD image

 

Go out there and be amazing. Have Fun, TR
TRsSig.png?raw=1
Some Pretty Pictures Some Cool Plugins

Link to comment
Share on other sites

You may have a look to Color Harmonies.

 

There you can see the dependencies of the different color channels H, S, and V (especially if you right click the color wheel and select 'Full adaptive'). The cone is mapped in this plugin to a cylinder.

 

In the HSL case the double cone is mapped to a cylinder and the top and bottom planes are white and black.

midoras signature.gif

Link to comment
Share on other sites

I tested that and it kept the same V.

watch it fullscreen for HD image

After reading the Wikipedia link I agree that this is indeed correct HSV. In this definition, V = max ( R,G,B ), so bright yellow, deep blue and white all have 100% Value.

 

But please note that the conversion from RGB to HSV is not smooth. It's obvious from the fact that the non-smooth "max" functon is employed. Also see the picture from Wikipedia: conversion to HSV and to HSL requires flattening the RGB/CMY spatial polygon to a planar hexagon. This is when the creases occur. If then we perform some manipulation in the HSV space (e.g. shift the hue) and convert back to RGB using the same conversion, new creases are added; the two sets of creases either cancel each other or not.

 

So my point is that the Hue/Saturation dialog should use something other than HSV or HSL. For example, the Luma/Chroma/Hue model may be better because the conversion from RGB is simple affine transformation without any "min" or "max" functions, and a smooth gradient will remain smooth.

Edited by Goblin Alchemist
Link to comment
Share on other sites

I agree that HSV is not perfect due to it's conical shape the properties merge into a point This confuses the data.

 

Midora's Cylindrical color space may hold a better solution. A cylindrical colorspace would keep shades and tints parallel and make the adjustment more intuitive. In other words red can go black but still be red and when lightened it would retain it's red identity and not turn grey.

Go out there and be amazing. Have Fun, TR
TRsSig.png?raw=1
Some Pretty Pictures Some Cool Plugins

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...