Efficiently finding the average color of a UIImage

Update 07/05/2012: As commenter Andy Matuschak kindly pointed out, this approach does not use GPU acceleration. Nonetheless, this approach is very likely to be faster than anything you write yourself, because the place where all the time is spent (resample_byte_h_4cpp_armv7) is written in tight assembly and makes use of the CPU’s vector unit.

For my final project for Stanford’s iPhone app class, I built World of Spectra, which challenges users to interact with the world around them by capturing and collecting its colors. In brief, users would take a picture of a real world object, and this picture would be reduced to a single color and added to a collection of colors (my talk includes a demo). To make this happen, I needed a way to find the “average” color of an image. Although there are a lot of clever approaches to extracting the most “important” color of an image, I just wanted something that would find a simple average of the colors in each pixel of the image.

The header file is pretty straightforward. Note that you need to remove the space in “< UIKit/UIKit.h>” (it is included to get around a WordPress parsing issue):

/*
 UIImage+AverageColor.h
*/
 
#import &lt; UIKit/UIKit.h&gt;
 
@interface UIImage (AverageColor)
- (UIColor *)averageColor;
@end

Now on to the actual code. The naive approach to finding the average color would be to loop over each pixel of the source image, separately accumulating the RGB values and then dividing by the total number of pixels. Of course, not only are things not so simple if we want to support images with transparency, but looping over measuring a few megapixels in this way is not very efficient.

A better approach is to use Apple’s existing graphics libraries to achieve this in much fewer lines of code and with the benefit of hardware acceleration. I create a Core Graphics bitmap context backed by a 4 element array (representing the RGBA values of a 1×1 image) and have Core Graphics draw the image to this context. Then, it is simple to return a UIColor correctly initialized with the “average” color of the image based on the aforementioned RGBA values. By using Core Graphics, which leverages the device’s GPU via OpenGL (see above), we avoid having the CPU loop through the image’s pixels to find an average, instead passing off this work to the GPU, which is designed for tasks like scaling images quickly. The result is much cleaner code and better performance:

/*
 UIImage+AverageColor.m
 
 Copyright (c) 2010, Mircea "Bobby" Georgescu
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright
 notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 notice, this list of conditions and the following disclaimer in the
 documentation and/or other materials provided with the distribution.
 * Neither the name of the Mircea "Bobby" Georgescu nor the
 names of its contributors may be used to endorse or promote products
 derived from this software without specific prior written permission.
 
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. IN NO EVENT SHALL Mircea "Bobby" Georgescu BE LIABLE FOR ANY
 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
 
#import "UIImage+AverageColor.h"
 
@implementation UIImage (AverageColor)
 
- (UIColor *)averageColor {
 
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    unsigned char rgba[4];
    CGContextRef context = CGBitmapContextCreate(rgba, 1, 1, 8, 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
 
    CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), self.CGImage);
    CGColorSpaceRelease(colorSpace);
    CGContextRelease(context);  
 
    if(rgba[3] &gt; 0) {
        CGFloat alpha = ((CGFloat)rgba[3])/255.0;
        CGFloat multiplier = alpha/255.0;
        return [UIColor colorWithRed:((CGFloat)rgba[0])*multiplier
                               green:((CGFloat)rgba[1])*multiplier
                                blue:((CGFloat)rgba[2])*multiplier
                               alpha:alpha];
    }
    else {
        return [UIColor colorWithRed:((CGFloat)rgba[0])/255.0
                               green:((CGFloat)rgba[1])/255.0
                                blue:((CGFloat)rgba[2])/255.0
                               alpha:((CGFloat)rgba[3])/255.0];
    }
}
 
@end

Feel free to use the code however you want (it is BSD licensed), and please drop me a line or leave a comment if it is useful to you.

4 thoughts on “Efficiently finding the average color of a UIImage

  1. This will not in fact leverage the GPU. CGContextDrawImage is performed on the CPU in this context. If you run this method in a tight loop, you’ll see CPU time = clock time and all of it spent in resample_byte_h_4cpp_armv7, which is tightly-implemented and uses the vector unit, but it doesn’t involve the GPU.

  2. Hey man, thanks for posting this! I’m thinking about ideas for a hackathon this summer and was messing around with things I could do with color. Your algo worked perfectly. Cheers
    Jeff

  3. I have implemented it on my project.
    However I found that the result is not ideal.
    It is likely to return the RGB value of the top left corner of the image instead of the real average color.
    I tested it with an image (like a Italy flag) of different orientation, and the result is different.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>