Is there a way to efficiently change hue of a 2D OpenGL texture using GLSL (fragment shader)?
Do someone have some code for it?
UPDATE: This is the code resulting from user1118321 suggestion:
uniform sampler2DRect texture;
const mat3 rgb2yiq = mat3(0.299, 0.587, 0.114, 0.595716, -0.274453, -0.321263, 0.211456, -0.522591, 0.311135);
const mat3 yiq2rgb = mat3(1.0, 0.9563, 0.6210, 1.0, -0.2721, -0.6474, 1.0, -1.1070, 1.7046);
uniform float hue;
void main() {
vec3 yColor = rgb2yiq * texture2DRect(texture, gl_TexCoord[0].st).rgb;
float originalHue = atan(yColor.b, yColor.g);
float finalHue = originalHue + hue;
float chroma = sqrt(yColor.b*yColor.b+yColor.g*yColor.g);
vec3 yFinalColor = vec3(yColor.r, chroma * cos(finalHue), chroma * sin(finalHue));
gl_FragColor = vec4(yiq2rgb*yFinalColor, 1.0);
}
And this is the result compared with a reference:
I have tried to switch I with Q inside atan but the result is wrong even around 0°
Have you got any hint?
If needed for comparison, this is the original unmodified image:
Andrea3000, in comparing YIQ examples on the net, i came across your posting, but i think there is an issue with the 'updated' version of your code.... i'm sure your 'mat3' definitions are flip/flopped on the column/row ordering... (maybe that's why you were still having troubles)...
FYI: OpenGL matrix ordering: "For more values, matrices are filled in in column-major order. That is, the first X values are the first column, the second X values are the next column, and so forth." See: http://www.opengl.org/wiki/GLSL_Types
While what @awoodland says is correct, that method may cause issues with changes in luminance, I believe.
HSV and HLS color systems are problematic for a number of reasons. I talked with a color scientist about this recently, and his recommendation was to convert to YIQ or YCbCr space and adjust the the chroma channels (I&Q, or Cb&Cr) accordingly. (You can learn how to do that here and here.)
Once in one of those spaces, you can get the hue from the angle formed by the chroma channels, by doing
hue = atan(cr/cb)
(watching for cb == 0). This gives you a value in radians. Simply rotate it by adding the hue rotation amount. Once you've done that, you can calculate the magnitude of the chroma withchroma = sqrt(cr*cr+cb*cb)
. To get back to RGB, calculate the new Cb and Cr (or I & Q) usingCr = chroma * sin (hue)
,Cb = chroma * cos (hue)
. Then convert back to RGB as described on the above web pages.EDIT: Here's a solution that I've tested and seems to give me the same results as your reference. You can probably collapse some of the dot products into matrix multiplies: