framework/Source/GPUImageHistogramEqualizationFilter.m
//
//  GPUImageHistogramEqualizationFilter.m
//  FilterShowcase
//
//  Created by Adam Marcus on 19/08/2014.
//  Copyright (c) 2014 Sunset Lake Software LLC. All rights reserved.
//

#import "GPUImageHistogramEqualizationFilter.h"

#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
NSString *const kGPUImageRedHistogramEqualizationFragmentShaderString = SHADER_STRING
(
 varying highp vec2 textureCoordinate;
 uniform sampler2D inputImageTexture;
 uniform sampler2D inputImageTexture2;
 
 void main()
 {
     lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
     lowp float redCurveValue = texture2D(inputImageTexture2, vec2(textureColor.r, 0.0)).r;
     
     gl_FragColor = vec4(redCurveValue, textureColor.g, textureColor.b, textureColor.a);
 }
 );
#else
NSString *const kGPUImageRedHistogramEqualizationFragmentShaderString = SHADER_STRING
(
 varying vec2 textureCoordinate;
 uniform sampler2D inputImageTexture;
 uniform sampler2D inputImageTexture2;
 
 void main()
 {
     vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
     float redCurveValue = texture2D(inputImageTexture2, vec2(textureColor.r, 0.0)).r;
     
     gl_FragColor = vec4(redCurveValue, textureColor.g, textureColor.b, textureColor.a);
 }
 );
#endif

#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
NSString *const kGPUImageGreenHistogramEqualizationFragmentShaderString = SHADER_STRING
(
 varying highp vec2 textureCoordinate;
 uniform sampler2D inputImageTexture;
 uniform sampler2D inputImageTexture2;
 
 void main()
 {
     lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
     lowp float greenCurveValue = texture2D(inputImageTexture2, vec2(textureColor.g, 0.0)).g;
     
     gl_FragColor = vec4(textureColor.r, greenCurveValue, textureColor.b, textureColor.a);
 }
 );
#else
NSString *const kGPUImageGreenHistogramEqualizationFragmentShaderString = SHADER_STRING
(
 varying vec2 textureCoordinate;
 uniform sampler2D inputImageTexture;
 uniform sampler2D inputImageTexture2;
 
 void main()
 {
     vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
     float greenCurveValue = texture2D(inputImageTexture2, vec2(textureColor.g, 0.0)).g;
     
     gl_FragColor = vec4(textureColor.r, greenCurveValue, textureColor.b, textureColor.a);
 }
 );
#endif

#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
NSString *const kGPUImageBlueHistogramEqualizationFragmentShaderString = SHADER_STRING
(
 varying highp vec2 textureCoordinate;
 uniform sampler2D inputImageTexture;
 uniform sampler2D inputImageTexture2;
 
 void main()
 {
     lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
     lowp float blueCurveValue = texture2D(inputImageTexture2, vec2(textureColor.b, 0.0)).b;
     
     gl_FragColor = vec4(textureColor.r, textureColor.g, blueCurveValue, textureColor.a);
 }
 );
#else
NSString *const kGPUImageBlueHistogramEqualizationFragmentShaderString = SHADER_STRING
(
 varying vec2 textureCoordinate;
 uniform sampler2D inputImageTexture;
 uniform sampler2D inputImageTexture2;
 
 void main()
 {
     vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
     float blueCurveValue = texture2D(inputImageTexture2, vec2(textureColor.b, 0.0)).b;
     
     gl_FragColor = vec4(textureColor.r, textureColor.g, blueCurveValue, textureColor.a);
 }
 );
#endif

#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
NSString *const kGPUImageRGBHistogramEqualizationFragmentShaderString = SHADER_STRING
(
 varying highp vec2 textureCoordinate; 
 uniform sampler2D inputImageTexture;
 uniform sampler2D inputImageTexture2;
 
 void main()
 {
     lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
     lowp float redCurveValue = texture2D(inputImageTexture2, vec2(textureColor.r, 0.0)).r;
     lowp float greenCurveValue = texture2D(inputImageTexture2, vec2(textureColor.g, 0.0)).g;
     lowp float blueCurveValue = texture2D(inputImageTexture2, vec2(textureColor.b, 0.0)).b;
     
     gl_FragColor = vec4(redCurveValue, greenCurveValue, blueCurveValue, textureColor.a);
 }
 );
#else
NSString *const kGPUImageRGBHistogramEqualizationFragmentShaderString = SHADER_STRING
(
 varying vec2 textureCoordinate;
 uniform sampler2D inputImageTexture;
 uniform sampler2D inputImageTexture2;
 
 void main()
 {
     vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
     float redCurveValue = texture2D(inputImageTexture2, vec2(textureColor.r, 0.0)).r;
     float greenCurveValue = texture2D(inputImageTexture2, vec2(textureColor.g, 0.0)).g;
     float blueCurveValue = texture2D(inputImageTexture2, vec2(textureColor.b, 0.0)).b;
     
     gl_FragColor = vec4(redCurveValue, greenCurveValue, blueCurveValue, textureColor.a);
 }
 );
#endif

#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
NSString *const kGPUImageLuminanceHistogramEqualizationFragmentShaderString = SHADER_STRING
(
 varying highp vec2 textureCoordinate;
 uniform sampler2D inputImageTexture;
 uniform sampler2D inputImageTexture2;
 
 const lowp vec3 W = vec3(0.2125, 0.7154, 0.0721);
 
 void main()
 {
     lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
     lowp float luminance = dot(textureColor.rgb, W);
     lowp float newLuminance = texture2D(inputImageTexture2, vec2(luminance, 0.0)).r;
     lowp float deltaLuminance = newLuminance - luminance;
     
     lowp float red   = clamp(textureColor.r + deltaLuminance, 0.0, 1.0);
     lowp float green = clamp(textureColor.g + deltaLuminance, 0.0, 1.0);
     lowp float blue  = clamp(textureColor.b + deltaLuminance, 0.0, 1.0);

     gl_FragColor = vec4(red, green, blue, textureColor.a);
 }
 );
#else
NSString *const kGPUImageLuminanceHistogramEqualizationFragmentShaderString = SHADER_STRING
(
 varying vec2 textureCoordinate;
 uniform sampler2D inputImageTexture;
 uniform sampler2D inputImageTexture2;
 
 const vec3 W = vec3(0.2125, 0.7154, 0.0721);

 void main()
 {
     vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
     float luminance = dot(textureColor.rgb, W);
     float newLuminance = texture2D(inputImageTexture2, vec2(luminance, 0.0)).r;
     float deltaLuminance = newLuminance - luminance;
     
     float red   = clamp(textureColor.r + deltaLuminance, 0.0, 1.0);
     float green = clamp(textureColor.g + deltaLuminance, 0.0, 1.0);
     float blue  = clamp(textureColor.b + deltaLuminance, 0.0, 1.0);
     
     gl_FragColor = vec4(red, green, blue, textureColor.a);
 }
 );
#endif

@implementation GPUImageHistogramEqualizationFilter

@synthesize downsamplingFactor = _downsamplingFactor;

#pragma mark -
#pragma mark Initialization

- (id)init;
{
    if (!(self = [self initWithHistogramType:kGPUImageHistogramRGB]))
    {
        return nil;
    }
    
    return self;
}

- (id)initWithHistogramType:(GPUImageHistogramType)newHistogramType
{
    if (!(self = [super init]))
    {
        return nil;
    }
    
    histogramFilter = [[GPUImageHistogramFilter alloc] initWithHistogramType:newHistogramType];
    [self addFilter:histogramFilter];
    
    GLubyte dummyInput[4 * 256]; // NB: No way to initialise GPUImageRawDataInput without providing bytes
    rawDataInputFilter = [[GPUImageRawDataInput alloc] initWithBytes:dummyInput size:CGSizeMake(256.0, 1.0) pixelFormat:GPUPixelFormatBGRA type:GPUPixelTypeUByte];
    rawDataOutputFilter = [[GPUImageRawDataOutput alloc] initWithImageSize:CGSizeMake(256.0, 3.0) resultsInBGRAFormat:YES];
    
    __unsafe_unretained GPUImageRawDataOutput *_rawDataOutputFilter = rawDataOutputFilter;
    __unsafe_unretained GPUImageRawDataInput *_rawDataInputFilter = rawDataInputFilter;
    [rawDataOutputFilter setNewFrameAvailableBlock:^{
        
        unsigned int histogramBins[3][256];
        
        [_rawDataOutputFilter lockFramebufferForReading];
        
        GLubyte *data  = [_rawDataOutputFilter rawBytesForImage];
        data += [_rawDataOutputFilter bytesPerRowInOutput];

        histogramBins[0][0] = *data++;
        histogramBins[1][0] = *data++;
        histogramBins[2][0] = *data++;
        data++;
        
        for (unsigned int x = 1; x < 256; x++) {
            histogramBins[0][x] = histogramBins[0][x-1] + *data++;
            histogramBins[1][x] = histogramBins[1][x-1] + *data++;
            histogramBins[2][x] = histogramBins[2][x-1] + *data++;
            data++;
        }
        
        [_rawDataOutputFilter unlockFramebufferAfterReading];

        GLubyte colorMapping[4 * 256];
        GLubyte *_colorMapping = colorMapping;
        
        for (unsigned int x = 0; x < 256; x++) {
            *_colorMapping++ = (GLubyte) (((histogramBins[0][x] - histogramBins[0][0]) * 255) / histogramBins[0][255]);
            *_colorMapping++ = (GLubyte) (((histogramBins[1][x] - histogramBins[1][0]) * 255) / histogramBins[1][255]);
            *_colorMapping++ = (GLubyte) (((histogramBins[2][x] - histogramBins[2][0]) * 255) / histogramBins[2][255]);
            *_colorMapping++ = 255;
        }
        
        _colorMapping = colorMapping;
        [_rawDataInputFilter updateDataFromBytes:_colorMapping size:CGSizeMake(256.0, 1.0)];
        [_rawDataInputFilter processData];
    }];
    [histogramFilter addTarget:rawDataOutputFilter];
    
    NSString *fragmentShader = nil;
    switch (newHistogramType) {
        case kGPUImageHistogramRed:
            fragmentShader = kGPUImageRedHistogramEqualizationFragmentShaderString;
            break;
        case kGPUImageHistogramGreen:
            fragmentShader = kGPUImageGreenHistogramEqualizationFragmentShaderString;
            break;
        case kGPUImageHistogramBlue:
            fragmentShader = kGPUImageBlueHistogramEqualizationFragmentShaderString;
            break;
        default:
        case kGPUImageHistogramRGB:
            fragmentShader = kGPUImageRGBHistogramEqualizationFragmentShaderString;
            break;
        case kGPUImageHistogramLuminance:
            fragmentShader = kGPUImageLuminanceHistogramEqualizationFragmentShaderString;
            break;
    }
    GPUImageFilter *equalizationFilter = [[GPUImageTwoInputFilter alloc] initWithFragmentShaderFromString:fragmentShader];
    [rawDataInputFilter addTarget:equalizationFilter atTextureLocation:1];
    
    [self addFilter:equalizationFilter];
    
    self.initialFilters = [NSArray arrayWithObjects:histogramFilter, equalizationFilter, nil];
    self.terminalFilter = equalizationFilter;
    
    self.downsamplingFactor = 16;
    
    return self;
}

#pragma mark -
#pragma mark Accessors

- (void)setDownsamplingFactor:(NSUInteger)newValue;
{
    if (_downsamplingFactor != newValue)
    {
        _downsamplingFactor = newValue;
        histogramFilter.downsamplingFactor = newValue;
    }
}

@end
