Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to replace a given color with another on an opaque UIImage [duplicate]

I want to change the color of an opaque UIImage. My original image is as follows:

enter image description here

and I want to convert the image to the following format

enter image description here

So, basically I want to convert red color in the image into black (Any other color) color. Above two images are added for better understanding.

like image 669
iPhone Avatar asked Feb 18 '16 09:02

iPhone


1 Answers

I couldn't see any answers on the 'duplicates' (this question shouldn't have been flagged as a duplicate) that will let you replace a given color with another color and work on an opaque image, so I decided to add one that would.


I created a UIImage category to do this, it basically works by looping through each pixel and detecting how close it is to a given colour, and blends it with your replacement colour if it is.

This will work for images with both transparency and opaque backgrounds.

@implementation UIImage (UIImageColorReplacement)

-(UIImage*) imageByReplacingColor:(UIColor*)sourceColor withMinTolerance:(CGFloat)minTolerance withMaxTolerance:(CGFloat)maxTolerance withColor:(UIColor*)destinationColor {

    // components of the source color
    const CGFloat* sourceComponents = CGColorGetComponents(sourceColor.CGColor);
    UInt8* source255Components = malloc(sizeof(UInt8)*4);
    for (int i = 0; i < 4; i++) source255Components[i] = (UInt8)round(sourceComponents[i]*255.0);

    // components of the destination color
    const CGFloat* destinationComponents = CGColorGetComponents(destinationColor.CGColor);
    UInt8* destination255Components = malloc(sizeof(UInt8)*4);
    for (int i = 0; i < 4; i++) destination255Components[i] = (UInt8)round(destinationComponents[i]*255.0);

    // raw image reference
    CGImageRef rawImage = self.CGImage;

    // image attributes
    size_t width = CGImageGetWidth(rawImage);
    size_t height = CGImageGetHeight(rawImage);
    CGRect rect = {CGPointZero, {width, height}};

    // bitmap format
    size_t bitsPerComponent = 8;
    size_t bytesPerRow = width*4;
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big;

    // data pointer
    UInt8* data = calloc(bytesPerRow, height);

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

    // create bitmap context
    CGContextRef ctx = CGBitmapContextCreate(data, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo);
    CGContextDrawImage(ctx, rect, rawImage);

    // loop through each pixel's components
    for (int byte = 0; byte < bytesPerRow*height; byte += 4) {

        UInt8 r = data[byte];
        UInt8 g = data[byte+1];
        UInt8 b = data[byte+2];

        // delta components
        UInt8 dr = abs(r-source255Components[0]);
        UInt8 dg = abs(g-source255Components[1]);
        UInt8 db = abs(b-source255Components[2]);

        // ratio of 'how far away' each component is from the source color
        CGFloat ratio = (dr+dg+db)/(255.0*3.0);
        if (ratio > maxTolerance) ratio = 1; // if ratio is too far away, set it to max.
        if (ratio < minTolerance) ratio = 0; // if ratio isn't far enough away, set it to min.

        // blend color components
        data[byte] = (UInt8)round(ratio*r)+(UInt8)round((1.0-ratio)*destination255Components[0]);
        data[byte+1] = (UInt8)round(ratio*g)+(UInt8)round((1.0-ratio)*destination255Components[1]);
        data[byte+2] = (UInt8)round(ratio*b)+(UInt8)round((1.0-ratio)*destination255Components[2]);

    }

    // get image from context
    CGImageRef img = CGBitmapContextCreateImage(ctx);

    // clean up
    CGContextRelease(ctx);
    CGColorSpaceRelease(colorSpace);
    free(data);
    free(source255Components);
    free(destination255Components);

    UIImage* returnImage = [UIImage imageWithCGImage:img];
    CGImageRelease(img);

    return returnImage;
}

@end

Usage:

UIImage* colaImage = [UIImage imageNamed:@"cola1.png"];
UIImage* blackColaImage = [colaImage imageByReplacingColor:[UIColor colorWithRed:1 green:0 blue:0 alpha:1] withMinTolerance:0.5 withMaxTolerance:0.6 withColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:1]];

The minTolerance is the point at which pixels will start to blend with the replacement colour (rather than being replaced). The maxTolerance is the point at which the pixels will stop being blended.

Before:

enter image description here

After:

enter image description here

The result is a little aliased, but bear in mind that your original image was fairly small. This will work much better with a higher resolution image. You can also play about with the tolerances to get even better results!

like image 122
Hamish Avatar answered Oct 10 '22 20:10

Hamish