The previously YIQ-based algorithm turned out to be both slow, and horribly inaccurate. Another algorithm based on rotating the color value in the RGB cube along the diagonal axis was also considered, which was acceptable in terms of accuracy, and very fast. In the end, I decided on a HSV-based one, because it is by far the most accurate one, while still being a tad faster than the YIQ solution. Algorithm source: gamedev.stackexchange.com/a/59808/24839 A very simple GPU time benchmark when shifting a 2048^2 bitmap: YIQ rot RGB rot HSV shift radeon 13.4 ms 2.8 ms 11.4 ms intel 13.0 ms 6.0 ms 10.5 ms radeon: HD 3650 mobility intel: N3540 integrated (Baytrail) However hue shifting has never shown up as a bottleneck before, so these are more academic.
40 lines
1 KiB
GLSL
40 lines
1 KiB
GLSL
|
|
uniform sampler2D texture;
|
|
uniform mediump float hueAdjust;
|
|
|
|
varying vec2 v_texCoord;
|
|
|
|
/* Source: gamedev.stackexchange.com/a/59808/24839 */
|
|
vec3 rgb2hsv(vec3 c)
|
|
{
|
|
const vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
|
|
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
|
|
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
|
|
|
|
float d = q.x - min(q.w, q.y);
|
|
|
|
/* Avoid divide-by-zero situations by adding a very tiny delta.
|
|
* Since we always deal with underlying 8-Bit color values, this
|
|
* should never mask a real value */
|
|
const float eps = 1.0e-10;
|
|
|
|
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + eps)), d / (q.x + eps), q.x);
|
|
}
|
|
|
|
vec3 hsv2rgb(vec3 c)
|
|
{
|
|
const vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
|
|
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
|
|
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
|
|
}
|
|
|
|
void main ()
|
|
{
|
|
vec4 color = texture2D (texture, v_texCoord.xy);
|
|
vec3 hsv = rgb2hsv(color.rgb);
|
|
|
|
hsv.x += hueAdjust;
|
|
color.rgb = hsv2rgb(hsv);
|
|
|
|
gl_FragColor = color;
|
|
}
|