From 541e24f67822c20f54b3ab36c1d3a7e6fa0caa76 Mon Sep 17 00:00:00 2001 From: Jonas Kulla Date: Tue, 4 Oct 2016 15:16:57 +0200 Subject: [PATCH] Bitmap: Use more accurate HSV-based hue shift algorithm 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. --- shader/hue.frag | 70 ++++++++++++++++++++++--------------------------- src/bitmap.cpp | 7 ++--- src/shader.cpp | 6 ----- src/shader.h | 3 +-- 4 files changed, 34 insertions(+), 52 deletions(-) diff --git a/shader/hue.frag b/shader/hue.frag index 405c91b..61143ac 100644 --- a/shader/hue.frag +++ b/shader/hue.frag @@ -1,48 +1,40 @@ -uniform sampler2D inputTexture; -uniform float hueAdjust; +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 () { - const vec4 kRGBToYPrime = vec4 (0.299, 0.587, 0.114, 0.0); - const vec4 kRGBToI = vec4 (0.596, -0.275, -0.321, 0.0); - const vec4 kRGBToQ = vec4 (0.212, -0.523, 0.311, 0.0); + vec4 color = texture2D (texture, v_texCoord.xy); + vec3 hsv = rgb2hsv(color.rgb); - const vec4 kYIQToR = vec4 (1.0, 0.956, 0.621, 0.0); - const vec4 kYIQToG = vec4 (1.0, -0.272, -0.647, 0.0); - const vec4 kYIQToB = vec4 (1.0, -1.107, 1.704, 0.0); + hsv.x += hueAdjust; + color.rgb = hsv2rgb(hsv); - /* Sample the input pixel */ - vec4 color = texture2D (inputTexture, v_texCoord.xy); - - /* Convert to YIQ */ - float YPrime = dot (color, kRGBToYPrime); - float I = dot (color, kRGBToI); - float Q = dot (color, kRGBToQ); - - /* Calculate the hue and chroma */ - float hue = atan (Q, I); - float chroma = sqrt (I * I + Q * Q); - - /* Make the user's adjustments */ - hue += hueAdjust; - - /* Remember old I and color */ - float IOriginal = I; - vec4 coOriginal = color; - - /* Convert back to YIQ */ - Q = chroma * sin (hue); - I = chroma * cos (hue); - - /* Convert back to RGB */ - vec4 yIQ = vec4 (YPrime, I, Q, 0.0); - color.r = dot (yIQ, kYIQToR); - color.g = dot (yIQ, kYIQToG); - color.b = dot (yIQ, kYIQToB); - - /* Save the result */ - gl_FragColor = (IOriginal == 0.0) ? coOriginal : color; + gl_FragColor = color; } diff --git a/src/bitmap.cpp b/src/bitmap.cpp index a677207..96cd968 100644 --- a/src/bitmap.cpp +++ b/src/bitmap.cpp @@ -854,13 +854,10 @@ void Bitmap::hueChange(int hue) quad.setTexPosRect(texRect, texRect); quad.setColor(Vec4(1, 1, 1, 1)); - /* Calculate hue parameter */ - hue = wrapRange(hue, 0, 359); - float hueAdj = -((M_PI * 2) / 360) * hue; - HueShader &shader = shState->shaders().hue; shader.bind(); - shader.setHueAdjust(hueAdj); + /* Shader expects normalized value */ + shader.setHueAdjust(wrapRange(hue, 0, 359) / 360.0f); FBO::bind(newTex.fbo); p->pushSetViewport(shader); diff --git a/src/shader.cpp b/src/shader.cpp index 3b22483..824454d 100644 --- a/src/shader.cpp +++ b/src/shader.cpp @@ -551,7 +551,6 @@ HueShader::HueShader() ShaderBase::init(); GET_U(hueAdjust); - GET_U(inputTexture); } void HueShader::setHueAdjust(float value) @@ -559,11 +558,6 @@ void HueShader::setHueAdjust(float value) gl.Uniform1f(u_hueAdjust, value); } -void HueShader::setInputTexture(TEX::ID tex) -{ - setTexUniform(u_inputTexture, 0, tex); -} - SimpleMatrixShader::SimpleMatrixShader() { diff --git a/src/shader.h b/src/shader.h index 323b10a..7b4eb02 100644 --- a/src/shader.h +++ b/src/shader.h @@ -241,10 +241,9 @@ public: HueShader(); void setHueAdjust(float value); - void setInputTexture(TEX::ID tex); private: - GLint u_hueAdjust, u_inputTexture; + GLint u_hueAdjust; }; class SimpleMatrixShader : public ShaderBase