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.
			
			
This commit is contained in:
		
							parent
							
								
									0ec1fce4ac
								
							
						
					
					
						commit
						541e24f678
					
				
					 4 changed files with 34 additions and 52 deletions
				
			
		| 
						 | 
					@ -1,48 +1,40 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
uniform sampler2D inputTexture;
 | 
					uniform sampler2D texture;
 | 
				
			||||||
uniform float hueAdjust;
 | 
					uniform mediump float hueAdjust;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
varying vec2 v_texCoord;
 | 
					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 ()
 | 
					void main ()
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	const vec4  kRGBToYPrime = vec4 (0.299, 0.587, 0.114, 0.0);
 | 
						vec4 color = texture2D (texture, v_texCoord.xy);
 | 
				
			||||||
	const vec4  kRGBToI      = vec4 (0.596, -0.275, -0.321, 0.0);
 | 
						vec3 hsv = rgb2hsv(color.rgb);
 | 
				
			||||||
	const vec4  kRGBToQ      = vec4 (0.212, -0.523, 0.311, 0.0);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const vec4  kYIQToR      = vec4 (1.0, 0.956, 0.621, 0.0);
 | 
						hsv.x += hueAdjust;
 | 
				
			||||||
	const vec4  kYIQToG      = vec4 (1.0, -0.272, -0.647, 0.0);
 | 
						color.rgb = hsv2rgb(hsv);
 | 
				
			||||||
	const vec4  kYIQToB      = vec4 (1.0, -1.107, 1.704, 0.0);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* Sample the input pixel */
 | 
						gl_FragColor = color;
 | 
				
			||||||
	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;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -854,13 +854,10 @@ void Bitmap::hueChange(int hue)
 | 
				
			||||||
	quad.setTexPosRect(texRect, texRect);
 | 
						quad.setTexPosRect(texRect, texRect);
 | 
				
			||||||
	quad.setColor(Vec4(1, 1, 1, 1));
 | 
						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;
 | 
						HueShader &shader = shState->shaders().hue;
 | 
				
			||||||
	shader.bind();
 | 
						shader.bind();
 | 
				
			||||||
	shader.setHueAdjust(hueAdj);
 | 
						/* Shader expects normalized value */
 | 
				
			||||||
 | 
						shader.setHueAdjust(wrapRange(hue, 0, 359) / 360.0f);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	FBO::bind(newTex.fbo);
 | 
						FBO::bind(newTex.fbo);
 | 
				
			||||||
	p->pushSetViewport(shader);
 | 
						p->pushSetViewport(shader);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -551,7 +551,6 @@ HueShader::HueShader()
 | 
				
			||||||
	ShaderBase::init();
 | 
						ShaderBase::init();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	GET_U(hueAdjust);
 | 
						GET_U(hueAdjust);
 | 
				
			||||||
	GET_U(inputTexture);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void HueShader::setHueAdjust(float value)
 | 
					void HueShader::setHueAdjust(float value)
 | 
				
			||||||
| 
						 | 
					@ -559,11 +558,6 @@ void HueShader::setHueAdjust(float value)
 | 
				
			||||||
	gl.Uniform1f(u_hueAdjust, value);
 | 
						gl.Uniform1f(u_hueAdjust, value);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void HueShader::setInputTexture(TEX::ID tex)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	setTexUniform(u_inputTexture, 0, tex);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
SimpleMatrixShader::SimpleMatrixShader()
 | 
					SimpleMatrixShader::SimpleMatrixShader()
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -241,10 +241,9 @@ public:
 | 
				
			||||||
	HueShader();
 | 
						HueShader();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void setHueAdjust(float value);
 | 
						void setHueAdjust(float value);
 | 
				
			||||||
	void setInputTexture(TEX::ID tex);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
	GLint u_hueAdjust, u_inputTexture;
 | 
						GLint u_hueAdjust;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SimpleMatrixShader : public ShaderBase
 | 
					class SimpleMatrixShader : public ShaderBase
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue