user319436
user319436

Reputation:

Determine primary and secondary colors of a UIImage

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

Answers (2)

Johnny Rockex
Johnny Rockex

Reputation: 4196

enter image description here

Based on Panic blog above, here's a fast method (<10ms depending on variables).

Takes a UIImage input and outputs:

  • background colour
  • primary colour
  • secondary colour

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):

  • edge = 0 = top
  • edge = 1 = left
  • edge = 2 = bottom
  • edge = 3 = right

**

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

Related Questions