Skip to content

Instantly share code, notes, and snippets.

@liovch
Created July 25, 2012 14:04
Show Gist options
  • Save liovch/3176346 to your computer and use it in GitHub Desktop.
Save liovch/3176346 to your computer and use it in GitHub Desktop.
GPUImageHueSaturationFilter
#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
#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