Reputation:
I was wondering if someone could help me figure out how I can determine the main primary and secondary colors in a UIImage. I haven't been able to find anything terribly useful in Google.
Upvotes: 5
Views: 4883
Reputation: 4196
Based on Panic blog above, here's a fast method (<10ms depending on variables).
Takes a UIImage input and outputs:
When passing the UIImage to the method, pass an 'edge' as well. This refers to which part of the image is exposed to the rest of the view (see image below for clarification):
**
Step 1.
** set dimension. This refers to how many pixels across and down the image should be.
Step 2. resize the image based on the dimension input (e.g. 20 x 20 = 400px).
Step 3. create a colour array, pulling the RGB values from the raw data created in step 2. Additionally collect the pixels that run along the chosen edge.
Step 4. calculate the edge or background colour. This is used later for contrasting the accent colours, as per the Panic blog.
Step 5. go through the collected colours (RGB values) and determine whether or not they contrast sufficiently with the edge/background colour. If they do not, the minimum required contrast is reduced. Also, for each colour object set its 'distance' to other colours. This is a rough estimate of how similar the colour is to other colours in the image.
Step 6. sort the accents by distance (shortest distance means most similar), with the most similar colour appearing at the top. Set the most frequent colour as the primary colour.
Step 7. go through the remaining colours and determine which contrasts the most with the primary colour. Then set your secondary colour.
Caveat: I'm sure the method could be improved, but it's a quick starting point. You could for instance determine the colour of the pixels in the top right or top left of the image (where back icons etc are often placed) and suggest a contrasting colour for the icons there.
Here's the code:
-(NSDictionary *)coloursForImage:(UIImage *)image forEdge:(int)edge {
NSLog(@"start");
//1. set vars
float dimension = 20;
//2. resize image and grab raw data
//this part pulls the raw data from the image
CGImageRef imageRef = [image CGImage];
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char *rawData = (unsigned char*) calloc(dimension * dimension * 4, sizeof(unsigned char));
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * dimension;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(rawData, dimension, dimension, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, dimension, dimension), imageRef);
CGContextRelease(context);
//3. create colour array
NSMutableArray * colours = [NSMutableArray new];
float x = 0, y = 0; //used to set coordinates
float eR = 0, eB = 0, eG = 0; //used for mean edge colour
for (int n = 0; n<(dimension*dimension); n++){
Colour * c = [Colour new]; //create colour
int i = (bytesPerRow * y) + x * bytesPerPixel; //pull index
c.r = rawData[i]; //set red
c.g = rawData[i + 1]; //set green
c.b = rawData[i + 2]; //set blue
[colours addObject:c]; //add colour
//add to edge if true
if ((edge == 0 && y == 0) || //top
(edge == 1 && x == 0) || //left
(edge == 2 && y == dimension-1) || //bottom
(edge == 3 && x == dimension-1)){ //right
eR+=c.r; eG+=c.g; eB+=c.b; //add the colours
}
//update pixel coordinate
x = (x == dimension - 1) ? 0 : x+1;
y = (x == 0) ? y+1 : y;
}
free(rawData);
//4. calculate edge colour
Colour * e = [Colour new];
e.r = eR/dimension;
e.g = eG/dimension;
e.b = eB/dimension;
//5. calculate the frequency of colour
NSMutableArray * accents = [NSMutableArray new]; //holds valid accents
float minContrast = 3.1; //play with this value
while (accents.count < 3) { //minimum number of accents
for (Colour * a in colours){
//NSLog(@"contrast value is %f", [self contrastValueFor:a andB:e]);
//5.1 ignore if it does not contrast with edge
if ([self contrastValueFor:a andB:e] < minContrast){ continue;}
//5.2 set distance (frequency)
for (Colour * b in colours){
a.d += [self colourDistance:a andB:b];
}
//5.3 add colour to accents
[accents addObject:a];
}
minContrast-=0.1f;
}
//6. sort colours by the most common
NSArray * sorted = [[NSArray arrayWithArray:accents] sortedArrayUsingDescriptors:@[[[NSSortDescriptor alloc] initWithKey:@"d" ascending:true]]];
//6.1 set primary colour (most common)
Colour * p = sorted[0];
//7. get most contrasting colour
float high = 0.0f; //the high
int index = 0; //the index
for (int n = 1; n < sorted.count; n++){
Colour * c = sorted[n];
float contrast = [self contrastValueFor:c andB:p];
//float sat = [self saturationValueFor:c andB:p];
if (contrast > high){
high = contrast;
index = n;
}
}
//7.1 set secondary colour (most contrasting)
Colour * s = sorted[index];
NSLog(@"er %i eg %i eb %i", e.r, e.g, e.b);
NSLog(@"pr %i pg %i pb %i", p.r, p.g, p.b);
NSLog(@"sr %i sg %i sb %i", s.r, s.g, s.b);
NSMutableDictionary * result = [NSMutableDictionary new];
[result setValue:[UIColor colorWithRed:e.r/255.0f green:e.g/255.0f blue:e.b/255.0f alpha:1.0f] forKey:@"background"];
[result setValue:[UIColor colorWithRed:p.r/255.0f green:p.g/255.0f blue:p.b/255.0f alpha:1.0f] forKey:@"primary"];
[result setValue:[UIColor colorWithRed:s.r/255.0f green:s.g/255.0f blue:s.b/255.0f alpha:1.0f] forKey:@"secondary"];
NSLog(@"end");
return result;
}
-(float)contrastValueFor:(Colour *)a andB:(Colour *)b {
float aL = 0.2126 * a.r + 0.7152 * a.g + 0.0722 * a.b;
float bL = 0.2126 * b.r + 0.7152 * b.g + 0.0722 * b.b;
return (aL>bL) ? (aL + 0.05) / (bL + 0.05) : (bL + 0.05) / (aL + 0.05);
}
-(float)saturationValueFor:(Colour *)a andB:(Colour *)b {
float min = MIN(a.r, MIN(a.g, a.b)); //grab min
float max = MAX(b.r, MAX(b.g, b.b)); //grab max
return (max - min)/max;
}
-(int)colourDistance:(Colour *)a andB:(Colour *)b {
return abs(a.r-b.r)+abs(a.g-b.g)+abs(a.b-b.b);
}
The Colour object is a simple custom class:
@interface Colour : NSObject
@property int r, g, b, d;
@end
Upvotes: 8
Reputation: 21249
The engineers at Panic wrote an algorithm for doing colour analysis similar to that found in iTunes 11 (to determine good primary, secondary, and detail colours). They posted an explanation of how it works and the code on their blog.
Upvotes: 3