/Dependencies/GPUImage/Source/GPUImageToneCurveFilter.m
Objective C | 621 lines | 479 code | 116 blank | 26 comment | 57 complexity | 2d3d95fbb354636ed40419c735c4967a MD5 | raw file
- #import "GPUImageToneCurveFilter.h"
-
- #pragma mark -
- #pragma mark GPUImageACVFile Helper
-
- // GPUImageACVFile
- //
- // ACV File format Parser
- // Please refer to http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577411_pgfId-1056330
- //
-
- @interface GPUImageACVFile : NSObject{
- short version;
- short totalCurves;
-
- NSArray *rgbCompositeCurvePoints;
- NSArray *redCurvePoints;
- NSArray *greenCurvePoints;
- NSArray *blueCurvePoints;
- }
-
- @property(strong,nonatomic) NSArray *rgbCompositeCurvePoints;
- @property(strong,nonatomic) NSArray *redCurvePoints;
- @property(strong,nonatomic) NSArray *greenCurvePoints;
- @property(strong,nonatomic) NSArray *blueCurvePoints;
-
- - (id) initWithACVFileData:(NSData*)data;
-
-
- unsigned short int16WithBytes(Byte* bytes);
- @end
-
- @implementation GPUImageACVFile
-
- @synthesize rgbCompositeCurvePoints, redCurvePoints, greenCurvePoints, blueCurvePoints;
-
- - (id) initWithACVFileData:(NSData *)data {
- self = [super init];
- if (self != nil)
- {
- if (data.length == 0)
- {
- NSLog(@"failed to init ACVFile with data:%@", data);
-
- return self;
- }
-
- Byte* rawBytes = (Byte*) [data bytes];
- version = int16WithBytes(rawBytes);
- rawBytes+=2;
-
- totalCurves = int16WithBytes(rawBytes);
- rawBytes+=2;
-
- NSMutableArray *curves = [NSMutableArray new];
-
- float pointRate = (1.0 / 255);
- // The following is the data for each curve specified by count above
- for (NSInteger x = 0; x<totalCurves; x++)
- {
- unsigned short pointCount = int16WithBytes(rawBytes);
- rawBytes+=2;
-
- NSMutableArray *points = [NSMutableArray new];
- // point count * 4
- // Curve points. Each curve point is a pair of short integers where
- // the first number is the output value (vertical coordinate on the
- // Curves dialog graph) and the second is the input value. All coordinates have range 0 to 255.
- for (NSInteger y = 0; y<pointCount; y++)
- {
- unsigned short y = int16WithBytes(rawBytes);
- rawBytes+=2;
- unsigned short x = int16WithBytes(rawBytes);
- rawBytes+=2;
- #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
- [points addObject:[NSValue valueWithCGSize:CGSizeMake(x * pointRate, y * pointRate)]];
- #else
- [points addObject:[NSValue valueWithSize:CGSizeMake(x * pointRate, y * pointRate)]];
- #endif
- }
- [curves addObject:points];
- }
- rgbCompositeCurvePoints = [curves objectAtIndex:0];
- redCurvePoints = [curves objectAtIndex:1];
- greenCurvePoints = [curves objectAtIndex:2];
- blueCurvePoints = [curves objectAtIndex:3];
- }
- return self;
- }
-
- unsigned short int16WithBytes(Byte* bytes) {
- uint16_t result;
- memcpy(&result, bytes, sizeof(result));
- return CFSwapInt16BigToHost(result);
- }
- @end
-
- #pragma mark -
- #pragma mark GPUImageToneCurveFilter Implementation
-
- #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
- NSString *const kGPUImageToneCurveFragmentShaderString = SHADER_STRING
- (
- varying highp vec2 textureCoordinate;
- uniform sampler2D inputImageTexture;
- uniform sampler2D toneCurveTexture;
-
- void main()
- {
- lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
- lowp float redCurveValue = texture2D(toneCurveTexture, vec2(textureColor.r, 0.0)).r;
- lowp float greenCurveValue = texture2D(toneCurveTexture, vec2(textureColor.g, 0.0)).g;
- lowp float blueCurveValue = texture2D(toneCurveTexture, vec2(textureColor.b, 0.0)).b;
-
- gl_FragColor = vec4(redCurveValue, greenCurveValue, blueCurveValue, textureColor.a);
- }
- );
- #else
- NSString *const kGPUImageToneCurveFragmentShaderString = SHADER_STRING
- (
- varying vec2 textureCoordinate;
- uniform sampler2D inputImageTexture;
- uniform sampler2D toneCurveTexture;
-
- void main()
- {
- vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
- float redCurveValue = texture2D(toneCurveTexture, vec2(textureColor.r, 0.0)).r;
- float greenCurveValue = texture2D(toneCurveTexture, vec2(textureColor.g, 0.0)).g;
- float blueCurveValue = texture2D(toneCurveTexture, vec2(textureColor.b, 0.0)).b;
-
- gl_FragColor = vec4(redCurveValue, greenCurveValue, blueCurveValue, textureColor.a);
- }
- );
- #endif
-
- @interface GPUImageToneCurveFilter()
- {
- GLint toneCurveTextureUniform;
- GLuint toneCurveTexture;
- GLubyte *toneCurveByteArray;
-
- NSArray *_redCurve, *_greenCurve, *_blueCurve, *_rgbCompositeCurve;
- }
-
- @end
-
- @implementation GPUImageToneCurveFilter
-
- @synthesize rgbCompositeControlPoints = _rgbCompositeControlPoints;
- @synthesize redControlPoints = _redControlPoints;
- @synthesize greenControlPoints = _greenControlPoints;
- @synthesize blueControlPoints = _blueControlPoints;
-
- #pragma mark -
- #pragma mark Initialization and teardown
-
- - (id)init;
- {
- if (!(self = [super initWithFragmentShaderFromString:kGPUImageToneCurveFragmentShaderString]))
- {
- return nil;
- }
-
- toneCurveTextureUniform = [filterProgram uniformIndex:@"toneCurveTexture"];
- #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
- NSArray *defaultCurve = [NSArray arrayWithObjects:[NSValue valueWithCGPoint:CGPointMake(0.0, 0.0)], [NSValue valueWithCGPoint:CGPointMake(0.5, 0.5)], [NSValue valueWithCGPoint:CGPointMake(1.0, 1.0)], nil];
- #else
- NSArray *defaultCurve = [NSArray arrayWithObjects:[NSValue valueWithPoint:NSMakePoint(0.0, 0.0)], [NSValue valueWithPoint:NSMakePoint(0.5, 0.5)], [NSValue valueWithPoint:NSMakePoint(1.0, 1.0)], nil];
- #endif
- [self setRgbCompositeControlPoints:defaultCurve];
- [self setRedControlPoints:defaultCurve];
- [self setGreenControlPoints:defaultCurve];
- [self setBlueControlPoints:defaultCurve];
-
- return self;
- }
-
- // This pulls in Adobe ACV curve files to specify the tone curve
- - (id)initWithACVData:(NSData *)data {
- if (!(self = [super initWithFragmentShaderFromString:kGPUImageToneCurveFragmentShaderString]))
- {
- return nil;
- }
-
- toneCurveTextureUniform = [filterProgram uniformIndex:@"toneCurveTexture"];
-
- GPUImageACVFile *curve = [[GPUImageACVFile alloc] initWithACVFileData:data];
-
- [self setRgbCompositeControlPoints:curve.rgbCompositeCurvePoints];
- [self setRedControlPoints:curve.redCurvePoints];
- [self setGreenControlPoints:curve.greenCurvePoints];
- [self setBlueControlPoints:curve.blueCurvePoints];
-
- curve = nil;
-
- return self;
- }
-
- - (id)initWithACV:(NSString*)curveFilename
- {
- return [self initWithACVURL:[[NSBundle mainBundle] URLForResource:curveFilename
- withExtension:@"acv"]];
- }
-
- - (id)initWithACVURL:(NSURL*)curveFileURL
- {
- NSData* fileData = [NSData dataWithContentsOfURL:curveFileURL];
- return [self initWithACVData:fileData];
- }
-
- - (void)setPointsWithACV:(NSString*)curveFilename
- {
- [self setPointsWithACVURL:[[NSBundle mainBundle] URLForResource:curveFilename withExtension:@"acv"]];
- }
-
- - (void)setPointsWithACVURL:(NSURL*)curveFileURL
- {
- NSData* fileData = [NSData dataWithContentsOfURL:curveFileURL];
- GPUImageACVFile *curve = [[GPUImageACVFile alloc] initWithACVFileData:fileData];
-
- [self setRgbCompositeControlPoints:curve.rgbCompositeCurvePoints];
- [self setRedControlPoints:curve.redCurvePoints];
- [self setGreenControlPoints:curve.greenCurvePoints];
- [self setBlueControlPoints:curve.blueCurvePoints];
-
- curve = nil;
- }
-
- - (void)dealloc
- {
- runSynchronouslyOnVideoProcessingQueue(^{
- [GPUImageContext useImageProcessingContext];
-
- if (toneCurveTexture)
- {
- glDeleteTextures(1, &toneCurveTexture);
- toneCurveTexture = 0;
- free(toneCurveByteArray);
- }
- });
- }
-
- #pragma mark -
- #pragma mark Curve calculation
-
- - (NSArray *)getPreparedSplineCurve:(NSArray *)points
- {
- if (points && [points count] > 0)
- {
- // Sort the array.
- NSArray *sortedPoints = [points sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
- #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
- float x1 = [(NSValue *)a CGPointValue].x;
- float x2 = [(NSValue *)b CGPointValue].x;
- #else
- float x1 = [(NSValue *)a pointValue].x;
- float x2 = [(NSValue *)b pointValue].x;
- #endif
- return x1 > x2;
- }];
-
- // Convert from (0, 1) to (0, 255).
- NSMutableArray *convertedPoints = [NSMutableArray arrayWithCapacity:[sortedPoints count]];
- for (int i=0; i<[points count]; i++){
- #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
- CGPoint point = [[sortedPoints objectAtIndex:i] CGPointValue];
- #else
- NSPoint point = [[sortedPoints objectAtIndex:i] pointValue];
- #endif
- point.x = point.x * 255;
- point.y = point.y * 255;
-
- #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
- [convertedPoints addObject:[NSValue valueWithCGPoint:point]];
- #else
- [convertedPoints addObject:[NSValue valueWithPoint:point]];
- #endif
- }
-
-
- NSMutableArray *splinePoints = [self splineCurve:convertedPoints];
-
- // If we have a first point like (0.3, 0) we'll be missing some points at the beginning
- // that should be 0.
- #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
- CGPoint firstSplinePoint = [[splinePoints objectAtIndex:0] CGPointValue];
- #else
- NSPoint firstSplinePoint = [[splinePoints objectAtIndex:0] pointValue];
- #endif
-
- if (firstSplinePoint.x > 0) {
- for (int i=firstSplinePoint.x; i >= 0; i--) {
- #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
- CGPoint newCGPoint = CGPointMake(i, 0);
- [splinePoints insertObject:[NSValue valueWithCGPoint:newCGPoint] atIndex:0];
- #else
- NSPoint newNSPoint = NSMakePoint(i, 0);
- [splinePoints insertObject:[NSValue valueWithPoint:newNSPoint] atIndex:0];
- #endif
- }
- }
-
- // Insert points similarly at the end, if necessary.
- #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
- CGPoint lastSplinePoint = [[splinePoints lastObject] CGPointValue];
-
- if (lastSplinePoint.x < 255) {
- for (int i = lastSplinePoint.x + 1; i <= 255; i++) {
- CGPoint newCGPoint = CGPointMake(i, 255);
- [splinePoints addObject:[NSValue valueWithCGPoint:newCGPoint]];
- }
- }
- #else
- NSPoint lastSplinePoint = [[splinePoints lastObject] pointValue];
-
- if (lastSplinePoint.x < 255) {
- for (int i = lastSplinePoint.x + 1; i <= 255; i++) {
- NSPoint newNSPoint = NSMakePoint(i, 255);
- [splinePoints addObject:[NSValue valueWithPoint:newNSPoint]];
- }
- }
- #endif
-
- // Prepare the spline points.
- NSMutableArray *preparedSplinePoints = [NSMutableArray arrayWithCapacity:[splinePoints count]];
- for (int i=0; i<[splinePoints count]; i++)
- {
- #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
- CGPoint newPoint = [[splinePoints objectAtIndex:i] CGPointValue];
- #else
- NSPoint newPoint = [[splinePoints objectAtIndex:i] pointValue];
- #endif
- CGPoint origPoint = CGPointMake(newPoint.x, newPoint.x);
-
- float distance = sqrt(pow((origPoint.x - newPoint.x), 2.0) + pow((origPoint.y - newPoint.y), 2.0));
-
- if (origPoint.y > newPoint.y)
- {
- distance = -distance;
- }
-
- [preparedSplinePoints addObject:[NSNumber numberWithFloat:distance]];
- }
-
- return preparedSplinePoints;
- }
-
- return nil;
- }
-
-
- - (NSMutableArray *)splineCurve:(NSArray *)points
- {
- NSMutableArray *sdA = [self secondDerivative:points];
-
- // [points count] is equal to [sdA count]
- NSInteger n = [sdA count];
- if (n < 1)
- {
- return nil;
- }
- double sd[n];
-
- // From NSMutableArray to sd[n];
- for (int i=0; i<n; i++)
- {
- sd[i] = [[sdA objectAtIndex:i] doubleValue];
- }
-
-
- NSMutableArray *output = [NSMutableArray arrayWithCapacity:(n+1)];
-
- for(int i=0; i<n-1 ; i++)
- {
- #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
- CGPoint cur = [[points objectAtIndex:i] CGPointValue];
- CGPoint next = [[points objectAtIndex:(i+1)] CGPointValue];
- #else
- NSPoint cur = [[points objectAtIndex:i] pointValue];
- NSPoint next = [[points objectAtIndex:(i+1)] pointValue];
- #endif
-
- for(int x=cur.x;x<(int)next.x;x++)
- {
- double t = (double)(x-cur.x)/(next.x-cur.x);
-
- double a = 1-t;
- double b = t;
- double h = next.x-cur.x;
-
- double y= a*cur.y + b*next.y + (h*h/6)*( (a*a*a-a)*sd[i]+ (b*b*b-b)*sd[i+1] );
-
- if (y > 255.0)
- {
- y = 255.0;
- }
- else if (y < 0.0)
- {
- y = 0.0;
- }
- #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
- [output addObject:[NSValue valueWithCGPoint:CGPointMake(x, y)]];
- #else
- [output addObject:[NSValue valueWithPoint:NSMakePoint(x, y)]];
- #endif
- }
- }
-
- // The above always misses the last point because the last point is the last next, so we approach but don't equal it.
- [output addObject:[points lastObject]];
- return output;
- }
-
- - (NSMutableArray *)secondDerivative:(NSArray *)points
- {
- const NSInteger n = [points count];
- if ((n <= 0) || (n == 1))
- {
- return nil;
- }
-
- double matrix[n][3];
- double result[n];
- matrix[0][1]=1;
- // What about matrix[0][1] and matrix[0][0]? Assuming 0 for now (Brad L.)
- matrix[0][0]=0;
- matrix[0][2]=0;
-
- for(int i=1;i<n-1;i++)
- {
- #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
- CGPoint P1 = [[points objectAtIndex:(i-1)] CGPointValue];
- CGPoint P2 = [[points objectAtIndex:i] CGPointValue];
- CGPoint P3 = [[points objectAtIndex:(i+1)] CGPointValue];
- #else
- NSPoint P1 = [[points objectAtIndex:(i-1)] pointValue];
- NSPoint P2 = [[points objectAtIndex:i] pointValue];
- NSPoint P3 = [[points objectAtIndex:(i+1)] pointValue];
- #endif
-
- matrix[i][0]=(double)(P2.x-P1.x)/6;
- matrix[i][1]=(double)(P3.x-P1.x)/3;
- matrix[i][2]=(double)(P3.x-P2.x)/6;
- result[i]=(double)(P3.y-P2.y)/(P3.x-P2.x) - (double)(P2.y-P1.y)/(P2.x-P1.x);
- }
-
- // What about result[0] and result[n-1]? Assuming 0 for now (Brad L.)
- result[0] = 0;
- result[n-1] = 0;
-
- matrix[n-1][1]=1;
- // What about matrix[n-1][0] and matrix[n-1][2]? For now, assuming they are 0 (Brad L.)
- matrix[n-1][0]=0;
- matrix[n-1][2]=0;
-
- // solving pass1 (up->down)
- for(int i=1;i<n;i++)
- {
- double k = matrix[i][0]/matrix[i-1][1];
- matrix[i][1] -= k*matrix[i-1][2];
- matrix[i][0] = 0;
- result[i] -= k*result[i-1];
- }
- // solving pass2 (down->up)
- for(NSInteger i=n-2;i>=0;i--)
- {
- double k = matrix[i][2]/matrix[i+1][1];
- matrix[i][1] -= k*matrix[i+1][0];
- matrix[i][2] = 0;
- result[i] -= k*result[i+1];
- }
-
- double y2[n];
- for(int i=0;i<n;i++) y2[i]=result[i]/matrix[i][1];
-
- NSMutableArray *output = [NSMutableArray arrayWithCapacity:n];
- for (int i=0;i<n;i++)
- {
- [output addObject:[NSNumber numberWithDouble:y2[i]]];
- }
-
- return output;
- }
-
- - (void)updateToneCurveTexture;
- {
- runSynchronouslyOnVideoProcessingQueue(^{
- [GPUImageContext useImageProcessingContext];
- if (!toneCurveTexture)
- {
- glActiveTexture(GL_TEXTURE3);
- glGenTextures(1, &toneCurveTexture);
- glBindTexture(GL_TEXTURE_2D, toneCurveTexture);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-
- toneCurveByteArray = calloc(256 * 4, sizeof(GLubyte));
- }
- else
- {
- glActiveTexture(GL_TEXTURE3);
- glBindTexture(GL_TEXTURE_2D, toneCurveTexture);
- }
-
- if ( ([_redCurve count] >= 256) && ([_greenCurve count] >= 256) && ([_blueCurve count] >= 256) && ([_rgbCompositeCurve count] >= 256))
- {
- for (unsigned int currentCurveIndex = 0; currentCurveIndex < 256; currentCurveIndex++)
- {
- // BGRA for upload to texture
- GLubyte b = fmin(fmax(currentCurveIndex + [[_blueCurve objectAtIndex:currentCurveIndex] floatValue], 0), 255);
- toneCurveByteArray[currentCurveIndex * 4] = fmin(fmax(b + [[_rgbCompositeCurve objectAtIndex:b] floatValue], 0), 255);
- GLubyte g = fmin(fmax(currentCurveIndex + [[_greenCurve objectAtIndex:currentCurveIndex] floatValue], 0), 255);
- toneCurveByteArray[currentCurveIndex * 4 + 1] = fmin(fmax(g + [[_rgbCompositeCurve objectAtIndex:g] floatValue], 0), 255);
- GLubyte r = fmin(fmax(currentCurveIndex + [[_redCurve objectAtIndex:currentCurveIndex] floatValue], 0), 255);
- toneCurveByteArray[currentCurveIndex * 4 + 2] = fmin(fmax(r + [[_rgbCompositeCurve objectAtIndex:r] floatValue], 0), 255);
- toneCurveByteArray[currentCurveIndex * 4 + 3] = 255;
- }
-
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256 /*width*/, 1 /*height*/, 0, GL_BGRA, GL_UNSIGNED_BYTE, toneCurveByteArray);
- }
- });
- }
-
- #pragma mark -
- #pragma mark Rendering
-
- - (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates;
- {
- if (self.preventRendering)
- {
- [firstInputFramebuffer unlock];
- return;
- }
-
- [GPUImageContext setActiveShaderProgram:filterProgram];
- outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:[self sizeOfFBO] textureOptions:self.outputTextureOptions onlyTexture:NO];
- [outputFramebuffer activateFramebuffer];
- if (usingNextFrameForImageCapture)
- {
- [outputFramebuffer lock];
- }
-
- glClearColor(backgroundColorRed, backgroundColorGreen, backgroundColorBlue, backgroundColorAlpha);
- glClear(GL_COLOR_BUFFER_BIT);
-
- glActiveTexture(GL_TEXTURE2);
- glBindTexture(GL_TEXTURE_2D, [firstInputFramebuffer texture]);
- glUniform1i(filterInputTextureUniform, 2);
-
- glActiveTexture(GL_TEXTURE3);
- glBindTexture(GL_TEXTURE_2D, toneCurveTexture);
- glUniform1i(toneCurveTextureUniform, 3);
-
- glVertexAttribPointer(filterPositionAttribute, 2, GL_FLOAT, 0, 0, vertices);
- glVertexAttribPointer(filterTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, textureCoordinates);
-
- glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
- [firstInputFramebuffer unlock];
- if (usingNextFrameForImageCapture)
- {
- dispatch_semaphore_signal(imageCaptureSemaphore);
- }
- }
-
- #pragma mark -
- #pragma mark Accessors
-
- - (void)setRGBControlPoints:(NSArray *)points
- {
- _redControlPoints = [points copy];
- _redCurve = [self getPreparedSplineCurve:_redControlPoints];
-
- _greenControlPoints = [points copy];
- _greenCurve = [self getPreparedSplineCurve:_greenControlPoints];
-
- _blueControlPoints = [points copy];
- _blueCurve = [self getPreparedSplineCurve:_blueControlPoints];
-
- [self updateToneCurveTexture];
- }
-
-
- - (void)setRgbCompositeControlPoints:(NSArray *)newValue
- {
- _rgbCompositeControlPoints = [newValue copy];
- _rgbCompositeCurve = [self getPreparedSplineCurve:_rgbCompositeControlPoints];
-
- [self updateToneCurveTexture];
- }
-
-
- - (void)setRedControlPoints:(NSArray *)newValue;
- {
- _redControlPoints = [newValue copy];
- _redCurve = [self getPreparedSplineCurve:_redControlPoints];
-
- [self updateToneCurveTexture];
- }
-
-
- - (void)setGreenControlPoints:(NSArray *)newValue
- {
- _greenControlPoints = [newValue copy];
- _greenCurve = [self getPreparedSplineCurve:_greenControlPoints];
-
- [self updateToneCurveTexture];
- }
-
-
- - (void)setBlueControlPoints:(NSArray *)newValue
- {
- _blueControlPoints = [newValue copy];
- _blueCurve = [self getPreparedSplineCurve:_blueControlPoints];
-
- [self updateToneCurveTexture];
- }
-
- @end