Objective-c - Getting least used and most used color in a image Objective-c - Getting least used and most used color in a image objective-c objective-c

Objective-c - Getting least used and most used color in a image


The method below takes an image and analyses it for its main colours, in the following steps:

1.) scale down the image and determine the main pixel colours.

2.) add some colour flexibility to allow for the loss during scaling

3.) distinguish colours, removing similar ones

4.) return the colours as an ordered array or with their percentages

You could adapt it to return a specific number of colours, e.g. top 10 colours in image if you needed a guaranteed number of colours returned, or just use the "detail" variable if you don't.

Larger images will take a long time to analyse at high detail.

No doubt the method could be cleaned up a bit but could be a good starting point.

Use like this:

 NSDictionary * mainColours = [s mainColoursInImage:image detail:1];

Example images run through the method at detail "1"

-(NSDictionary*)mainColoursInImage:(UIImage *)image detail:(int)detail {//1. determine detail vars (0==low,1==default,2==high)//default detailfloat dimension = 10;float flexibility = 2;float range = 60;//low detailif (detail==0){    dimension = 4;    flexibility = 1;    range = 100;//high detail (patience!)} else if (detail==2){    dimension = 100;    flexibility = 10;    range = 20;}//2. determine the colours in the imageNSMutableArray * colours = [NSMutableArray new];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);float x = 0;float y = 0;for (int n = 0; n<(dimension*dimension); n++){    int index = (bytesPerRow * y) + x * bytesPerPixel;    int red   = rawData[index];    int green = rawData[index + 1];    int blue  = rawData[index + 2];    int alpha = rawData[index + 3];    NSArray * a = [NSArray arrayWithObjects:[NSString stringWithFormat:@"%i",red],[NSString stringWithFormat:@"%i",green],[NSString stringWithFormat:@"%i",blue],[NSString stringWithFormat:@"%i",alpha], nil];    [colours addObject:a];    y++;    if (y==dimension){        y=0;        x++;    }}free(rawData);//3. add some colour flexibility (adds more colours either side of the colours in the image)NSArray * copyColours = [NSArray arrayWithArray:colours];NSMutableArray * flexibleColours = [NSMutableArray new];float flexFactor = flexibility * 2 + 1;float factor = flexFactor * flexFactor * 3; //(r,g,b) == *3for (int n = 0; n<(dimension * dimension); n++){    NSArray * pixelColours = copyColours[n];    NSMutableArray * reds = [NSMutableArray new];    NSMutableArray * greens = [NSMutableArray new];    NSMutableArray * blues = [NSMutableArray new];    for (int p = 0; p<3; p++){        NSString * rgbStr = pixelColours[p];        int rgb = [rgbStr intValue];        for (int f = -flexibility; f<flexibility+1; f++){            int newRGB = rgb+f;            if (newRGB<0){                newRGB = 0;            }            if (p==0){                [reds addObject:[NSString stringWithFormat:@"%i",newRGB]];            } else if (p==1){                [greens addObject:[NSString stringWithFormat:@"%i",newRGB]];            } else if (p==2){                [blues addObject:[NSString stringWithFormat:@"%i",newRGB]];            }        }    }    int r = 0;    int g = 0;    int b = 0;    for (int k = 0; k<factor; k++){        int red = [reds[r] intValue];        int green = [greens[g] intValue];        int blue = [blues[b] intValue];        NSString * rgbString = [NSString stringWithFormat:@"%i,%i,%i",red,green,blue];        [flexibleColours addObject:rgbString];        b++;        if (b==flexFactor){ b=0; g++; }        if (g==flexFactor){ g=0; r++; }    }}//4. distinguish the colours//orders the flexible colours by their occurrence//then keeps them if they are sufficiently disimilarNSMutableDictionary * colourCounter = [NSMutableDictionary new];//count the occurences in the arrayNSCountedSet *countedSet = [[NSCountedSet alloc] initWithArray:flexibleColours];for (NSString *item in countedSet) {    NSUInteger count = [countedSet countForObject:item];    [colourCounter setValue:[NSNumber numberWithInteger:count] forKey:item];}//sort keys highest occurrence to lowestNSArray *orderedKeys = [colourCounter keysSortedByValueUsingComparator:^NSComparisonResult(id obj1, id obj2){    return [obj2 compare:obj1];}];//checks if the colour is similar to another one already includedNSMutableArray * ranges = [NSMutableArray new];for (NSString * key in orderedKeys){    NSArray * rgb = [key componentsSeparatedByString:@","];    int r = [rgb[0] intValue];    int g = [rgb[1] intValue];    int b = [rgb[2] intValue];    bool exclude = false;    for (NSString * ranged_key in ranges){        NSArray * ranged_rgb = [ranged_key componentsSeparatedByString:@","];        int ranged_r = [ranged_rgb[0] intValue];        int ranged_g = [ranged_rgb[1] intValue];        int ranged_b = [ranged_rgb[2] intValue];        if (r>= ranged_r-range && r<= ranged_r+range){            if (g>= ranged_g-range && g<= ranged_g+range){                if (b>= ranged_b-range && b<= ranged_b+range){                    exclude = true;                }            }        }    }    if (!exclude){ [ranges addObject:key]; }}//return ranges array here if you just want the ordered colours high to lowNSMutableArray * colourArray = [NSMutableArray new];for (NSString * key in ranges){    NSArray * rgb = [key componentsSeparatedByString:@","];    float r = [rgb[0] floatValue];    float g = [rgb[1] floatValue];    float b = [rgb[2] floatValue];    UIColor * colour = [UIColor colorWithRed:(r/255.0f) green:(g/255.0f) blue:(b/255.0f) alpha:1.0f];    [colourArray addObject:colour];}//if you just want an array of images of most common to least, return here//return [NSDictionary dictionaryWithObject:colourArray forKey:@"colours"];//if you want percentages to colours continue belowNSMutableDictionary * temp = [NSMutableDictionary new];float totalCount = 0.0f;for (NSString * rangeKey in ranges){    NSNumber * count = colourCounter[rangeKey];    totalCount += [count intValue];    temp[rangeKey]=count;}//set percentagesNSMutableDictionary * colourDictionary = [NSMutableDictionary new];for (NSString * key in temp){    float count = [temp[key] floatValue];    float percentage = count/totalCount;    NSArray * rgb = [key componentsSeparatedByString:@","];    float r = [rgb[0] floatValue];    float g = [rgb[1] floatValue];    float b = [rgb[2] floatValue];    UIColor * colour = [UIColor colorWithRed:(r/255.0f) green:(g/255.0f) blue:(b/255.0f) alpha:1.0f];    colourDictionary[colour]=[NSNumber numberWithFloat:percentage];}return colourDictionary;}


Not sure about finding most color or least color, but here is a method to find out the average color.

- (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] > 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];    }}

You can probably follow a similar approach to find out the most used color.

Also check this answer about counting red color pixels in an image.


Thanks a lot for your code, @JohnnyRockex. It was really helpful in getting me started towards my goal (finding accent colors depending on the most predominant color in an image).

After going through it, I found the code could be simplified and made easier to read, so I'd like to give back to the community my own version; the -colors selector is in a UIImage extension.

- (NSArray *)colors {// Original code by Johnny Rockex http://stackoverflow.com/a/29266983/825644// Higher the dimension, the more pixels are checked against.const float pixelDimension = 10;// Higher the range, more similar colors are removed.const float filterRange = 60;unsigned char *rawData = (unsigned char*) calloc(pixelDimension * pixelDimension * kBytesPerPixel, sizeof(unsigned char));NSUInteger bytesPerRow = kBytesPerPixel * pixelDimension;CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();CGContextRef context = CGBitmapContextCreate(rawData, pixelDimension, pixelDimension, kBitsInAByte, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);CGColorSpaceRelease(colorSpace);CGContextDrawImage(context, CGRectMake(0, 0, pixelDimension, pixelDimension), [self CGImage]);CGContextRelease(context);NSMutableArray * colors = [[NSMutableArray alloc] init];float x = 0;float y = 0;const int pixelMatrixSize = pixelDimension * pixelDimension;for (int i = 0; i < pixelMatrixSize; i++){    int index = (bytesPerRow * y) + x * kBytesPerPixel;    int red   = rawData[index];    int green = rawData[index + 1];    int blue  = rawData[index + 2];    int alpha = rawData[index + 3];    UIColor * color = [UIColor colorWithRed:(red / 255.0f) green:(green / 255.0f) blue:(blue / 255.0f) alpha:alpha];    [colors addObject:color];    y++;    if (y == pixelDimension){        y = 0;        x++;    }}free(rawData);NSMutableDictionary * colorCounter = [[NSMutableDictionary alloc] init];NSCountedSet *countedSet = [[NSCountedSet alloc] initWithArray:colors];for (NSString *item in countedSet) {    NSUInteger count = [countedSet countForObject:item];    [colorCounter setValue:[NSNumber numberWithInteger:count] forKey:item];}NSArray *orderedColors = [colorCounter keysSortedByValueUsingComparator:^NSComparisonResult(id obj1, id obj2){    return [obj2 compare:obj1];}];NSMutableArray *filteredColors = [NSMutableArray new];for (UIColor *color in orderedColors){    bool filtered = false;    for (UIColor *rangedColor in filteredColors){        if (abs(color.redRGBComponent - rangedColor.redRGBComponent) <= filterRange &&            abs(color.greenRGBComponent - rangedColor.greenRGBComponent) <= filterRange &&            abs(color.blueRGBComponent - rangedColor.blueRGBComponent) <= filterRange) {            filtered = true;            break;        }    }    if (!filtered) {        [filteredColors addObject:color];    }}return [filteredColors copy];

The code for UIColor's extension adding the -rgbComponent function can be found underneath, but I wrote it in Swift (trying to write all new classes in Swift, but this wasn't the case for the -colors selector):

extension UIColor {    open func redRGBComponent() -> UInt8 {        let colorComponents = cgColor.components!        return UInt8(colorComponents[0] * 255)    }    open func greenRGBComponent() -> UInt8 {        let colorComponents = cgColor.components!        return UInt8(colorComponents[1] * 255)    }    open func blueRGBComponent() -> UInt8 {        let colorComponents = cgColor.components!         return UInt8(colorComponents[2] * 255)    }}

Enjoy!