#import "GPUImageAverageColor.h" NSString *const kGPUImageColorAveragingVertexShaderString = SHADER_STRING ( attribute vec4 position; attribute vec4 inputTextureCoordinate; uniform float texelWidth; uniform float texelHeight; varying vec2 upperLeftInputTextureCoordinate; varying vec2 upperRightInputTextureCoordinate; varying vec2 lowerLeftInputTextureCoordinate; varying vec2 lowerRightInputTextureCoordinate; void main() { gl_Position = position; upperLeftInputTextureCoordinate = inputTextureCoordinate.xy + vec2(-texelWidth, -texelHeight); upperRightInputTextureCoordinate = inputTextureCoordinate.xy + vec2(texelWidth, -texelHeight); lowerLeftInputTextureCoordinate = inputTextureCoordinate.xy + vec2(-texelWidth, texelHeight); lowerRightInputTextureCoordinate = inputTextureCoordinate.xy + vec2(texelWidth, texelHeight); } ); #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE NSString *const kGPUImageColorAveragingFragmentShaderString = SHADER_STRING ( precision highp float; uniform sampler2D inputImageTexture; varying highp vec2 outputTextureCoordinate; varying highp vec2 upperLeftInputTextureCoordinate; varying highp vec2 upperRightInputTextureCoordinate; varying highp vec2 lowerLeftInputTextureCoordinate; varying highp vec2 lowerRightInputTextureCoordinate; void main() { highp vec4 upperLeftColor = texture2D(inputImageTexture, upperLeftInputTextureCoordinate); highp vec4 upperRightColor = texture2D(inputImageTexture, upperRightInputTextureCoordinate); highp vec4 lowerLeftColor = texture2D(inputImageTexture, lowerLeftInputTextureCoordinate); highp vec4 lowerRightColor = texture2D(inputImageTexture, lowerRightInputTextureCoordinate); gl_FragColor = 0.25 * (upperLeftColor + upperRightColor + lowerLeftColor + lowerRightColor); } ); #else NSString *const kGPUImageColorAveragingFragmentShaderString = SHADER_STRING ( uniform sampler2D inputImageTexture; varying vec2 outputTextureCoordinate; varying vec2 upperLeftInputTextureCoordinate; varying vec2 upperRightInputTextureCoordinate; varying vec2 lowerLeftInputTextureCoordinate; varying vec2 lowerRightInputTextureCoordinate; void main() { vec4 upperLeftColor = texture2D(inputImageTexture, upperLeftInputTextureCoordinate); vec4 upperRightColor = texture2D(inputImageTexture, upperRightInputTextureCoordinate); vec4 lowerLeftColor = texture2D(inputImageTexture, lowerLeftInputTextureCoordinate); vec4 lowerRightColor = texture2D(inputImageTexture, lowerRightInputTextureCoordinate); gl_FragColor = 0.25 * (upperLeftColor + upperRightColor + lowerLeftColor + lowerRightColor); } ); #endif @implementation GPUImageAverageColor @synthesize colorAverageProcessingFinishedBlock = _colorAverageProcessingFinishedBlock; #pragma mark - #pragma mark Initialization and teardown - (id)init; { if (!(self = [super initWithVertexShaderFromString:kGPUImageColorAveragingVertexShaderString fragmentShaderFromString:kGPUImageColorAveragingFragmentShaderString])) { return nil; } texelWidthUniform = [filterProgram uniformIndex:@"texelWidth"]; texelHeightUniform = [filterProgram uniformIndex:@"texelHeight"]; finalStageSize = CGSizeMake(1.0, 1.0); __unsafe_unretained GPUImageAverageColor *weakSelf = self; [self setFrameProcessingCompletionBlock:^(GPUImageOutput *filter, CMTime frameTime) { [weakSelf extractAverageColorAtFrameTime:frameTime]; }]; return self; } - (void)dealloc; { if (rawImagePixels != NULL) { free(rawImagePixels); } } #pragma mark - #pragma mark Managing the display FBOs - (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates; { if (self.preventRendering) { [firstInputFramebuffer unlock]; return; } outputFramebuffer = nil; [GPUImageContext setActiveShaderProgram:filterProgram]; glVertexAttribPointer(filterPositionAttribute, 2, GL_FLOAT, 0, 0, vertices); glVertexAttribPointer(filterTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, textureCoordinates); GLuint currentTexture = [firstInputFramebuffer texture]; NSUInteger numberOfReductionsInX = floor(log(inputTextureSize.width) / log(4.0)); NSUInteger numberOfReductionsInY = floor(log(inputTextureSize.height) / log(4.0)); NSUInteger reductionsToHitSideLimit = MIN(numberOfReductionsInX, numberOfReductionsInY); for (NSUInteger currentReduction = 0; currentReduction < reductionsToHitSideLimit; currentReduction++) { CGSize currentStageSize = CGSizeMake(floor(inputTextureSize.width / pow(4.0, currentReduction + 1.0)), floor(inputTextureSize.height / pow(4.0, currentReduction + 1.0))); if ( (currentStageSize.height < 2.0) || (currentStageSize.width < 2.0) ) { // A really small last stage seems to cause significant errors in the average, so I abort and leave the rest to the CPU at this point break; // currentStageSize.height = 2.0; // TODO: Rotate the image to account for this case, which causes FBO construction to fail } [outputFramebuffer unlock]; outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:currentStageSize textureOptions:self.outputTextureOptions onlyTexture:NO]; [outputFramebuffer activateFramebuffer]; glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, currentTexture); glUniform1i(filterInputTextureUniform, 2); glUniform1f(texelWidthUniform, 0.5 / currentStageSize.width); glUniform1f(texelHeightUniform, 0.5 / currentStageSize.height); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); currentTexture = [outputFramebuffer texture]; finalStageSize = currentStageSize; } [firstInputFramebuffer unlock]; } - (void)setInputRotation:(GPUImageRotationMode)newInputRotation atIndex:(NSInteger)textureIndex; { inputRotation = kGPUImageNoRotation; } - (void)extractAverageColorAtFrameTime:(CMTime)frameTime; { runSynchronouslyOnVideoProcessingQueue(^{ // we need a normal color texture for averaging the color values NSAssert(self.outputTextureOptions.internalFormat == GL_RGBA, @"The output texture internal format for this filter must be GL_RGBA."); NSAssert(self.outputTextureOptions.type == GL_UNSIGNED_BYTE, @"The type of the output texture of this filter must be GL_UNSIGNED_BYTE."); NSUInteger totalNumberOfPixels = round(finalStageSize.width * finalStageSize.height); if (rawImagePixels == NULL) { rawImagePixels = (GLubyte *)malloc(totalNumberOfPixels * 4); } [GPUImageContext useImageProcessingContext]; [outputFramebuffer activateFramebuffer]; glReadPixels(0, 0, (int)finalStageSize.width, (int)finalStageSize.height, GL_RGBA, GL_UNSIGNED_BYTE, rawImagePixels); NSUInteger redTotal = 0, greenTotal = 0, blueTotal = 0, alphaTotal = 0; NSUInteger byteIndex = 0; for (NSUInteger currentPixel = 0; currentPixel < totalNumberOfPixels; currentPixel++) { redTotal += rawImagePixels[byteIndex++]; greenTotal += rawImagePixels[byteIndex++]; blueTotal += rawImagePixels[byteIndex++]; alphaTotal += rawImagePixels[byteIndex++]; } CGFloat normalizedRedTotal = (CGFloat)redTotal / (CGFloat)totalNumberOfPixels / 255.0; CGFloat normalizedGreenTotal = (CGFloat)greenTotal / (CGFloat)totalNumberOfPixels / 255.0; CGFloat normalizedBlueTotal = (CGFloat)blueTotal / (CGFloat)totalNumberOfPixels / 255.0; CGFloat normalizedAlphaTotal = (CGFloat)alphaTotal / (CGFloat)totalNumberOfPixels / 255.0; if (_colorAverageProcessingFinishedBlock != NULL) { _colorAverageProcessingFinishedBlock(normalizedRedTotal, normalizedGreenTotal, normalizedBlueTotal, normalizedAlphaTotal, frameTime); } }); } @end