Created
July 25, 2012 14:04
-
-
Save liovch/3176346 to your computer and use it in GitHub Desktop.
GPUImageHueSaturationFilter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "GPUImageFilter.h" | |
@interface GPUImageHueSaturationFilter : GPUImageFilter { | |
GLfloat hue[7]; | |
GLfloat lightness[7]; | |
GLfloat saturation[7]; | |
GLint hueUniform; | |
GLint lightnessUniform; | |
GLint saturationUniform; | |
GLint overlapUniform; | |
} | |
typedef enum { GPU_ALL_HUES, GPU_RED_HUES, GPU_YELLOW_HUES, GPU_GREEN_HUES, GPU_CYAN_HUES, GPU_BLUE_HUES, GPU_MAGENTA_HUES } GPUHueRange; | |
// Hue, Lightness, and Saturation range from -1.0 to 1.0, with 0.0 as the normal level. | |
// For Hue -1.0 corresponds to -180 degrees, 1.0 corresponds to 180 degrees, e.g. 60 degrees = 60/180 = 0.333333 | |
- (void)setHue:(GLfloat[7]) _hue; | |
- (void)setLightness:(GLfloat[7]) _lightness; | |
- (void)setSaturation:(GLfloat[7]) _saturation; | |
// Overlap ranges from 0.0 to 1.0, with 0.0 as the normal level. | |
@property(readwrite, nonatomic) GLfloat overlap; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "GPUImageHueSaturationFilter.h" | |
NSString *const kGPUImageHueSaturationFragmentShaderString = SHADER_STRING | |
( | |
varying highp vec2 textureCoordinate; | |
uniform sampler2D inputImageTexture; | |
uniform lowp float hueShift[7]; | |
uniform lowp float lightnessShift[7]; | |
uniform lowp float saturationShift[7]; | |
uniform lowp float overlap; | |
/* Color space conversion functions by: | |
** http://mouaif.wordpress.com/2009/01/05/photoshop-math-with-glsl-shaders/ | |
** Photoshop & misc math | |
** Blending modes, RGB/HSL/Contrast/Desaturate, levels control | |
** | |
** Romain Dura | Romz | |
** Blog: http://blog.mouaif.org | |
** Post: http://blog.mouaif.org/?p=94 | |
*/ | |
lowp vec3 RGBToHSL(lowp vec3 color) | |
{ | |
lowp vec3 hsl; // init to 0 to avoid warnings ? (and reverse if + remove first part) | |
lowp float fmin = min(min(color.r, color.g), color.b); //Min. value of RGB | |
lowp float fmax = max(max(color.r, color.g), color.b); //Max. value of RGB | |
lowp float delta = fmax - fmin; //Delta RGB value | |
hsl.z = (fmax + fmin) / 2.0; // Luminance | |
if (delta == 0.0) //This is a gray, no chroma... | |
{ | |
hsl.x = 0.0; // Hue | |
hsl.y = 0.0; // Saturation | |
} | |
else //Chromatic data... | |
{ | |
if (hsl.z < 0.5) | |
hsl.y = delta / (fmax + fmin); // Saturation | |
else | |
hsl.y = delta / (2.0 - fmax - fmin); // Saturation | |
lowp float deltaR = (((fmax - color.r) / 6.0) + (delta / 2.0)) / delta; | |
lowp float deltaG = (((fmax - color.g) / 6.0) + (delta / 2.0)) / delta; | |
lowp float deltaB = (((fmax - color.b) / 6.0) + (delta / 2.0)) / delta; | |
if (color.r == fmax ) | |
hsl.x = deltaB - deltaG; // Hue | |
else if (color.g == fmax) | |
hsl.x = (1.0 / 3.0) + deltaR - deltaB; // Hue | |
else if (color.b == fmax) | |
hsl.x = (2.0 / 3.0) + deltaG - deltaR; // Hue | |
if (hsl.x < 0.0) | |
hsl.x += 1.0; // Hue | |
else if (hsl.x > 1.0) | |
hsl.x -= 1.0; // Hue | |
} | |
return hsl; | |
} | |
lowp float HueToRGB(lowp float f1, lowp float f2, lowp float hue) | |
{ | |
if (hue < 0.0) | |
hue += 1.0; | |
else if (hue > 1.0) | |
hue -= 1.0; | |
lowp float res; | |
if ((6.0 * hue) < 1.0) | |
res = f1 + (f2 - f1) * 6.0 * hue; | |
else if ((2.0 * hue) < 1.0) | |
res = f2; | |
else if ((3.0 * hue) < 2.0) | |
res = f1 + (f2 - f1) * ((2.0 / 3.0) - hue) * 6.0; | |
else | |
res = f1; | |
return res; | |
} | |
lowp vec3 HSLToRGB(lowp vec3 hsl) | |
{ | |
lowp vec3 rgb; | |
if (hsl.y == 0.0) | |
rgb = vec3(hsl.z); // Luminance | |
else | |
{ | |
lowp float f2; | |
if (hsl.z < 0.5) | |
f2 = hsl.z * (1.0 + hsl.y); | |
else | |
f2 = (hsl.z + hsl.y) - (hsl.y * hsl.z); | |
lowp float f1 = 2.0 * hsl.z - f2; | |
rgb.r = HueToRGB(f1, f2, hsl.x + (1.0/3.0)); | |
rgb.g = HueToRGB(f1, f2, hsl.x); | |
rgb.b= HueToRGB(f1, f2, hsl.x - (1.0/3.0)); | |
} | |
return rgb; | |
} | |
lowp float RGBToL(lowp vec3 color) | |
{ | |
lowp float fmin = min(min(color.r, color.g), color.b); //Min. value of RGB | |
lowp float fmax = max(max(color.r, color.g), color.b); //Max. value of RGB | |
return (fmax + fmin) / 2.0; // Luminance | |
} | |
// The code below is based on GIMP source code: gimpoperationhuesaturation.c | |
/* GIMP - The GNU Image Manipulation Program | |
* Copyright (C) 1995 Spencer Kimball and Peter Mattis | |
* | |
* gimpoperationhuesaturation.c | |
* Copyright (C) 2007 Michael Natterer <[email protected]> | |
* | |
* This program is free software: you can redistribute it and/or modify | |
* it under the terms of the GNU General Public License as published by | |
* the Free Software Foundation; either version 3 of the License, or | |
* (at your option) any later version. | |
* | |
* This program is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
* GNU General Public License for more details. | |
* | |
* You should have received a copy of the GNU General Public License | |
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |
*/ | |
mediump float map_hue(lowp int range, mediump float value) | |
{ | |
value += (hueShift[0] + hueShift[range]) / 2.0; | |
if (value < 0.0) | |
return value + 1.0; | |
else if (value > 1.0) | |
return value - 1.0; | |
else | |
return value; | |
} | |
lowp float map_saturation(lowp int range, mediump float value) | |
{ | |
mediump float v = saturationShift[0] + saturationShift[range]; | |
value *= (v + 1.0); | |
return clamp(value, 0.0, 1.0); | |
} | |
mediump float map_lightness(lowp int range, mediump float value) | |
{ | |
mediump float v = (lightnessShift[0] + lightnessShift[range]) / 2.0; | |
if (v < 0.0) | |
return value * (v + 1.0); | |
else | |
return value + (v * (1.0 - value)); | |
} | |
void main() | |
{ | |
lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate); | |
mediump vec3 hsl = RGBToHSL(textureColor.rgb); | |
mediump float h = hsl.x * 6.0; | |
lowp int hue_counter; | |
lowp int hue = 0; | |
lowp int secondary_hue = 0; | |
lowp int use_secondary_hue = 0; // TODO: Should be of bool type | |
mediump float primary_intensity = 0.0; | |
mediump float secondary_intensity = 0.0; | |
for (hue_counter = 0; hue_counter < 7; hue_counter++) { | |
mediump float hue_threshold = float(hue_counter) + 0.5; | |
if (h < (hue_threshold + overlap)) { | |
hue = hue_counter; | |
if (overlap > 0.0 && h > (hue_threshold - overlap)) { | |
use_secondary_hue = 1; | |
secondary_hue = hue_counter + 1; | |
secondary_intensity = (h - hue_threshold + overlap) / (2.0 * overlap); | |
primary_intensity = 1.0 - secondary_intensity; | |
} else { | |
use_secondary_hue = 0; | |
} | |
break; | |
} | |
} | |
if (hue >= 6) { | |
hue = 0; | |
use_secondary_hue = 0; | |
} | |
if (secondary_hue >= 6) { | |
secondary_hue = 0; | |
} | |
// transform into GPUHueRange values | |
hue++; | |
secondary_hue++; | |
if (use_secondary_hue == 1) { | |
mediump float mapped_primary_hue; | |
mediump float mapped_secondary_hue; | |
mediump float diff; | |
mapped_primary_hue = map_hue(hue, hsl.x); | |
mapped_secondary_hue = map_hue(secondary_hue, hsl.x); | |
// Find nearest hue on the circle between primary and secondary hue | |
diff = mapped_primary_hue - mapped_secondary_hue; | |
if (diff < -0.5) { | |
mapped_secondary_hue -= 1.0; | |
} else if (diff >= 0.5) { | |
mapped_secondary_hue += 1.0; | |
} | |
hsl.x = (mapped_primary_hue * primary_intensity + | |
mapped_secondary_hue * secondary_intensity); | |
hsl.y = (map_saturation(hue, hsl.y) * primary_intensity + | |
map_saturation(secondary_hue, hsl.y) * secondary_intensity); | |
hsl.z = (map_lightness(hue, hsl.z) * primary_intensity + | |
map_lightness(secondary_hue, hsl.z) * secondary_intensity); | |
} else { | |
hsl.x = map_hue (hue, hsl.x); | |
hsl.y = map_saturation(hue, hsl.y); | |
hsl.z = map_lightness (hue, hsl.z); | |
} | |
textureColor.rgb = HSLToRGB(hsl); | |
gl_FragColor = textureColor; | |
} | |
); | |
@implementation GPUImageHueSaturationFilter | |
@synthesize overlap = _overlap; | |
#pragma mark - | |
#pragma mark Initialization | |
- (id)init; | |
{ | |
if (!(self = [super initWithFragmentShaderFromString:kGPUImageHueSaturationFragmentShaderString])) | |
{ | |
return nil; | |
} | |
hueUniform = [filterProgram uniformIndex:@"hueShift"]; | |
lightnessUniform = [filterProgram uniformIndex:@"lightnessShift"]; | |
saturationUniform = [filterProgram uniformIndex:@"saturationShift"]; | |
overlapUniform = [filterProgram uniformIndex:@"overlap"]; | |
memset(hue, 0, sizeof(hue)); | |
memset(lightness, 0, sizeof(lightness)); | |
memset(saturation, 0, sizeof(saturation)); | |
self.overlap = 0.0; | |
return self; | |
} | |
#pragma mark - | |
#pragma mark Accessors | |
- (void)setHue:(GLfloat[7]) _hue | |
{ | |
memcpy(hue, _hue, sizeof(hue)); | |
[GPUImageOpenGLESContext useImageProcessingContext]; | |
[filterProgram use]; | |
glUniform1fv(hueUniform, 7, hue); | |
} | |
- (void)setLightness:(GLfloat[7]) _lightness | |
{ | |
memcpy(lightness, _lightness, sizeof(lightness)); | |
[GPUImageOpenGLESContext useImageProcessingContext]; | |
[filterProgram use]; | |
glUniform1fv(lightnessUniform, 7, lightness); | |
} | |
- (void)setSaturation:(GLfloat[7]) _saturation | |
{ | |
memcpy(saturation, _saturation, sizeof(saturation)); | |
[GPUImageOpenGLESContext useImageProcessingContext]; | |
[filterProgram use]; | |
glUniform1fv(saturationUniform, 7, saturation); | |
} | |
- (void)setOverlap:(GLfloat)newValue | |
{ | |
_overlap = newValue; | |
[GPUImageOpenGLESContext useImageProcessingContext]; | |
[filterProgram use]; | |
glUniform1f(overlapUniform, _overlap / 2.0); | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment