PDA

View Full Version : HSL transforms



masonwheeler
23-01-2008, 12:50 AM
Does anyone know of a good way to do HSL-based transformations? I'm looking for two specific things: Ways to palette-swap an image by sliding the Hue value around, and ways to change the saturation of the entire image on-screen (fade-to-gray).

When I ask in graphics-related forums, people say "look at shaders." When I look at shaders, though, I find that they won't work for what I'm trying to do, since as far as I can tell, shaders are completely canned algorithms, and I need to be able to input an arbitrary value to use at runtime. (Although this may be totally inaccurate. It just looks that way.) Anyone know anything that might help?

Mason

LP
23-01-2008, 01:39 AM
You will need to do your own investigation on RGB<->HSL conversion and writing/using shaders. It's difficult that somebody will write and post a ready-to-use program for you. You can discuss the very same topic on the many (http://www.afterwarp.net/forum/thread1318.html) different (http://developer.nvidia.com/forums/index.php?s=3b5a7df943d406aeed7fba61a41ee381&showtopic=903&pid=2066&st=0&#entry2066) forums, but until you start working on it yourself - it won't help.

Indeed, learning shaders will be a time consuming experience, so you have several options:

1) Learn shaders, write your own to solve the problem. (pros: a lot of fresh knowledge; cons: time consuming)

2) Use some kind of software rendering to solve your problem. (pros: slow, can't be used in real-time; cons: less time required)

3) Hire someone to solve the problem for you (pros: quickly resolve the problem; cons: it might be expensive)

4) Change the problem so you don't need to use per-pixel HSL-RGB transformations in real-time. (pros: no need to invest time and money; cons: may not be possible depending on your requirements)

Yes, shaders will work for you, and no, they are not "completely canned algorithms", and yes, you can set arbitrary values at runtime.

masonwheeler
23-01-2008, 03:14 AM
All I know is, what I'm trying to do has been around since before pixel shaders were invented, and worked just fine without slowing everything down. (And on computers that are much less powerful than we've got today.) So obviously there's another way. I just don't know what it is, and in fact it's far enough out of my area of expertise that I don't know where to start looking.

I downloaded FX Composer--a program for building shaders--and read through the "basic quick start tutorial," and nothing in it made any sense to me. It came with a built-in library, including a RGB-to-HSL-transform shader. I tried to place that into a sample project, and it gave error messages with no explanations for them. Simply put, I'm trying to program something, and I don't know where to start, and all the "help" I've gotten from people in the past has been--to my level of knowledge, at least--supremely unhelpful gibberish.

That's my situation. Is anyone actually able to help out?

JSoftware
23-01-2008, 03:47 AM
Why do you need to modify the color values using HSL? Could you try to specify the problem?

If you need something, then I threw the following GLSL shader code together. It's tested and does seemingly work


vec3 HSL2RGB&#40;vec3 hsl&#41;
&#123;
if&#40;hsl.z == 0&#41;
return vec3&#40;0,0,0&#41;;
else
&#123;
if&#40;hsl.y == 0&#41;
return vec3&#40;hsl.z&#41;;
else
&#123;
float tmp2 = 0;
if&#40;hsl.z<=0.5&#41;
tmp2 = hsl.z*&#40;1.0+hsl.y&#41;;
else
tmp2 = hsl.z+hsl.y-hsl.z*hsl.y;
float tmp1 = 2.0*hsl.z-tmp2;

vec3 t3 = vec3&#40;hsl.x+1.0/3.0, hsl.x, hsl.x-1.0/3.0&#41;;
vec3 clr = vec3&#40;0&#41;;

for&#40;int i=0; i<3; i++&#41;
&#123;
if&#40;t3&#91;i&#93;<0>1.0&#41;
t3&#91;i&#93;-=1.0;

if&#40;6.0*t3&#91;i&#93; < 1.0&#41;
clr&#91;i&#93; = tmp1+&#40;tmp2-tmp1&#41;*t3&#91;i&#93;*6.0;
else if&#40;2.0*t3&#91;i&#93; < 1.0&#41;
clr&#91;i&#93; = tmp2;
else if&#40;3.0*t3&#91;i&#93; < 2.0&#41;
clr&#91;i&#93; = &#40;tmp1+&#40;tmp2-tmp1&#41;*&#40;&#40;2.0/3.0&#41;-t3&#91;i&#93;&#41;*6.0&#41;;
else
clr&#91;i&#93; = tmp1;
&#125;

return clr;
&#125;
&#125;
&#125;

vec3 RGB2HSL&#40;vec3 rgb&#41;
&#123;
float var_Min = min&#40;min&#40; rgb.x, rgb.y&#41;, rgb.z &#41;; //Min. value of RGB
float var_Max = max&#40;max&#40; rgb.x, rgb.y&#41;, rgb.z &#41;; //Max. value of RGB
float del_Max = var_Max - var_Min; //Delta RGB value

float H,S,L;

L = &#40; var_Max + var_Min &#41; / 2.0;

if &#40; del_Max == 0.0 &#41; //This is a gray, no chroma...
&#123;
H = 0.0; //HSL results = 0 ?? 1
S = 0.0;
&#125;
else //Chromatic data...
&#123;
if &#40; L < 0.5 &#41; S = del_Max / &#40; var_Max + var_Min &#41;;
else S = del_Max / &#40; 2.0 - var_Max - var_Min &#41;;

float del_R = &#40; &#40; &#40; var_Max - rgb.x &#41; / 6.0 &#41; + &#40; del_Max / 2.0 &#41; &#41; / del_Max;
float del_G = &#40; &#40; &#40; var_Max - rgb.y &#41; / 6.0 &#41; + &#40; del_Max / 2.0 &#41; &#41; / del_Max;
float del_B = &#40; &#40; &#40; var_Max - rgb.z &#41; / 6.0 &#41; + &#40; del_Max / 2.0 &#41; &#41; / del_Max;

if &#40; rgb.x == var_Max &#41; H = del_B - del_G;
else if &#40; rgb.y == var_Max &#41; H = &#40; 1.0 / 3.0 &#41; + del_R - del_B;
else if &#40; rgb.z == var_Max &#41; H = &#40; 2.0 / 3.0 &#41; + del_G - del_R;

if &#40; H <0> 1.0 &#41; H -= 1.0;
&#125;

return vec3&#40;H,S,L&#41;;
&#125;

masonwheeler
23-01-2008, 05:40 AM
I'm trying to do two very specific (and completely separate) things.

1. Palette-swap an image via "hue-sliding". (Leave S and L the same, but add an arbitrary value to the H of every pixel, wrapping around if necessary). It would take an image and a byte value (the magnitude of the hue-slide) as an input, and perform the modification. This doesn't have to be done in real-time, (and probably doesn't even require a pixel shader), but it has to be fairly fast. If I were to write it in Pascal, it would look something like this:

&#123;$Q-&#125; &#123;$R-&#125;
procedure hueSlide&#40;theImage&#58; TGraphic; const magnitude&#58; byte&#41;;
var
x, y&#58; word;
dummy&#58; THslImage; //a pure handwavium class whose pixels are
//represented internally as a record of 3 8-bit HSL
//values, instead of RGB ones
begin
if magnitude = 0 then
Exit;

dummy &#58;= THslImage.create&#40;theImage&#41;; //a "copy constructor" of sorts
for y &#58;= 0 to height&#40;dummy&#41; do
for x &#58;= 0 to width&#40;dummy&#41; do
inc&#40;dummy.pixel&#91;x, y&#93;.H, magnitude&#41;;
//end FOR
//end FOR
dummy.assignTo&#40;theImage&#41;; //converts it back
end;
&#123;$Q+&#125; &#123;$R+&#125;

2. Saturation change. This is the really tricky one, as it would have to affect the entire screen and run in real-time. Probably the simplest way to explain would be to write it out in fake-code. Assume that the entire scene so far is rendered to a render target.


procedure saturationChange&#40;scene&#58; TImaginaryRenderTarget; const magnitude&#58; single&#41;;
var
x, y&#58; word;
dummy&#58; THslImage;
begin
if magnitude = 1 then //magnitude can be any value from 0 to 2. 1 is
Exit; //100% &#40;ie. unchanged&#41;
if &#40;magnitude <0> 2&#41; then
raise EFatalError.Create&#40;'Bad magnitude value&#58; ' + intToStr&#40;magnitude&#41;&#41;;

dummy &#58;= THslImage.create&#40;scene.image&#41;;
for y &#58;= 0 to height&#40;dummy&#41; do
for x &#58;= o to width&#40;dummy&#41; do
dummy.pixel&#91;x, y&#93;.S, &#58;= lesserOf&#40;dummy.pixel&#91;x, y&#93;.S * magnitude, 255&#41;;
//end FOR
//end FOR
dummy.assignTo&#40;scene.image&#41;; //converts it back
end;

Basically, a way to desaturate (fade to gray) or oversaturate the entire screen by an arbitrary value, which could change from one frame to the next. I've seen programs that predate the invention of the pixel shader, running under DirectX 2, do stuff like this in real-time with no perceptible slowdown. So this also doesn't really need a shader either, although that would probably be the best way to do it now that the technology's available. I just have no idea how to go about doing it.

Mason

JernejL
23-01-2008, 05:12 PM
This is some simple GLSL hue shift shader i wrote for TDC, it has some additional stuff for alpha opacity control and it uses uniforms to specify hue shift and alpha control values.


// This source code contains modified/translated code
// from Jedi Component Library, which is used under terms of MPL - Mozilla Public License.
// Portions of this source code are based on online mathematical formulas from easyrgb website&#58;
// http&#58;//www.easyrgb.com/math.php?MATH=M19#text19

// This is a simple hue shift fragment shader with alpha color component control.

uniform sampler2D Texture1;
uniform float AddHue, AddSat, AddLit;
uniform float ShadeMode;

float Hue_2_RGB&#40;float v1, float v2, float vH &#41;
&#123;

float result;

if &#40; vH <0> 1.0 &#41; vH -= 1.0;
if &#40; &#40; 6.0 * vH &#41; < 1.0 &#41; &#123; result = &#40; v1 + &#40; v2 - v1 &#41; * 6.0 * vH &#41;; &#125;
else if &#40; &#40; 2.0 * vH &#41; < 1.0 &#41; &#123; result = &#40; v2 &#41;; &#125;
else if &#40; &#40; 3.0 * vH &#41; <2> 0.0 &#41;
&#123;
gl_FragColor = vec4&#40;0,0,0, &#40;color.a * ShadeMode&#41; &#41;;
&#125;
else
&#123;

R = color.r;
G = color.g;
B = color.b;

// properly process white color which cannot be properly converted to hsl and back &#40;ends up black&#41;
if &#40;&#40;R == 1.0&#41; && &#40;G == 1.0&#41; && &#40;B == 1.0&#41;&#41;
&#123;
gl_FragColor = vec4&#40;color.r, color.g, color.b, color.a&#41;;
&#125;
else
&#123;

// convert to HSL

Cmax = max &#40;R, max &#40;G, B&#41;&#41;;
Cmin = min &#40;R, min &#40;G, B&#41;&#41;;

// calculate lightness
L = &#40;Cmax + Cmin&#41; / 2.0;

if &#40;Cmax == Cmin&#41; // it's grey
&#123;
H = 0.0; // it's actually undefined
S = 0.0;
&#125;
else
&#123;
D = Cmax - Cmin;
&#125;


// calculate Saturation
if &#40;L < 0.5&#41;
&#123;
S = D / &#40;Cmax + Cmin&#41;;
&#125;
else
&#123;
S = D / &#40;2.0 - Cmax - Cmin&#41;;
&#125;

// calculate Hue
if &#40;R == Cmax&#41;
&#123;
H = &#40;G - B&#41; / D;
&#125;
else
&#123;
if &#40;G == Cmax&#41;
&#123;
H = 2.0 + &#40;B - R&#41; /D;
&#125;
else
&#123;
H = 4.0 + &#40;R - G&#41; / D;
&#125;
&#125;

H = H / 6.0;

if &#40;H < 0.0&#41;
&#123;
H = H + 1.0;
&#125;

// modify H/S/L values
H += AddHue;
S += AddSat;
L += AddLit;

// convert back to RGB

float var_2, var_1;

if &#40;S == 0.0&#41;
&#123;
R = L;
G = L;
B = L;
&#125;
else
&#123;
if &#40; L < 0.5 &#41;
&#123;
var_2 = L * &#40; 1.0 + S &#41;;
&#125;
else
&#123;
var_2 = &#40; L + S &#41; - &#40; S * L &#41;;
&#125;

var_1 = 2.0 * L - var_2;

R = Hue_2_RGB&#40; var_1, var_2, H + &#40; 1.0 / 3.0 &#41; &#41;;
G = Hue_2_RGB&#40; var_1, var_2, H &#41;;
B = Hue_2_RGB&#40; var_1, var_2, H - &#40; 1.0 / 3.0 &#41; &#41;;
&#125;

gl_FragColor = vec4&#40;R,G,B, color.a&#41;;

&#125; // white color check

&#125; // not shadow mode

&#125;

LP
23-01-2008, 06:17 PM
Mason, in DirectX 2 they've used to modify 256-color palette, which instantly changed what you see on the screen. If you work with RGB images, you need to modify each pixel to obtain the same effect.

About desaturation - long time ago I've posted an example in Asphyre eXtreme (http://www.afterwarp.net/content/view/14/30/), which could convert the screen to grayscale and back with variable intensity. This should be equal to desaturation in your case, although the brightness is conserved (while in HSL case the colors will get darker or brighter since L component is not perceptually uniform and not even linear).

If you have a limited range of sprites, why not pre-rendering each sprite in different Hue and then using each sprite accordingly?

masonwheeler
23-01-2008, 07:12 PM
Delfi: Thanks! I can almost understand that. (Which just means that, as long as it's been since I've touched the C language, it hasn't been long enough!) :P It shouldn't be too hard to modify for my purposes.


Mason, in DirectX 2 they've used to modify 256-color palette, which instantly changed what you see on the screen. If you work with RGB images, you need to modify each pixel to obtain the same effect.
Figures. Yeah, that makes sense. It was all in 256-color graphics IIRC. :(


About desaturation - long time ago I've posted an example in Asphyre eXtreme (http://www.afterwarp.net/content/view/14/30/), which could convert the screen to grayscale and back with variable intensity. This should be equal to desaturation in your case, although the brightness is conserved (while in HSL case the colors will get darker or brighter since L component is not perceptually uniform and not even linear).
GoneGray? I've seen that. It works pretty well, actually, and conserving brightness is just fine. But is there any way to modify it to also oversaturate as well as desaturate? I tried looking at the code to that and, again, couldn't make heads or tails of it. Too many DirectX calls.


If you have a limited range of sprites, why not pre-rendering each sprite in different Hue and then using each sprite accordingly?
Yeah, that's probably what I'll end up doing. :(

LP
23-01-2008, 07:40 PM
GoneGray? I've seen that. It works pretty well, actually, and conserving brightness is just fine. But is there any way to modify it to also oversaturate as well as desaturate? I tried looking at the code to that and, again, couldn't make heads or tails of it. Too many DirectX calls.
No, it's not how it work. Besides, if the image is completely gray (S = 0), there is ambiguity if you start increasing the saturation.