36

On my iPhone app I have a UIImage instance. I want to get a derived a UIImage that is the result of the first UIImage where one of its colors (e.g. magenta) is made transparent. How can I do this?

Peter Hosey
  • 95,783
  • 15
  • 211
  • 370
devguy
  • 2,336
  • 5
  • 27
  • 31

11 Answers11

34

OK. Having tried I don't know how many versions of these solutions, I have my own customised version. What I've found is that the solution from @yubenyi works quite well, but if you want to take the output from his changeWhiteColorTransparent() function and pass it back in, it doesn't work.

My first step was to change his function to accept a specific colour and tolerance so that the caller could specify a range of colours to make transparent. This worked fine, almost unchanged, but I found that the output was not a valid image for passing through the same code with a second colour range.

After a lot of trial and error, I got this working by doing the colour replacement myself. I resisted this because it seemed like too much hard work when there are API's to do this stuff, but they don't always behave the way you want. Specifically, the output from CGImageCreateWithMaskingColors() can't be used as an input into another call to the same function. I haven't been able to work out why, but I think it's something to do with the alpha channel.

In any case, my solution is:

- (UIImage*) replaceColor:(UIColor*)color inImage:(UIImage*)image withTolerance:(float)tolerance {
    CGImageRef imageRef = [image CGImage];

    NSUInteger width = CGImageGetWidth(imageRef);
    NSUInteger height = CGImageGetHeight(imageRef);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

    NSUInteger bytesPerPixel = 4;
    NSUInteger bytesPerRow = bytesPerPixel * width;
    NSUInteger bitsPerComponent = 8;
    NSUInteger bitmapByteCount = bytesPerRow * height;

    unsigned char *rawData = (unsigned char*) calloc(bitmapByteCount, sizeof(unsigned char));

    CGContextRef context = CGBitmapContextCreate(rawData, width, height,
                                                 bitsPerComponent, bytesPerRow, colorSpace,
                                                 kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    CGColorSpaceRelease(colorSpace);

    CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);

    CGColorRef cgColor = [color CGColor];
    const CGFloat *components = CGColorGetComponents(cgColor);
    float r = components[0];
    float g = components[1];
    float b = components[2];
    //float a = components[3]; // not needed

    r = r * 255.0;
    g = g * 255.0;
    b = b * 255.0;

    const float redRange[2] = {
        MAX(r - (tolerance / 2.0), 0.0),
        MIN(r + (tolerance / 2.0), 255.0)
    };

    const float greenRange[2] = {
        MAX(g - (tolerance / 2.0), 0.0),
        MIN(g + (tolerance / 2.0), 255.0)
    };

    const float blueRange[2] = {
        MAX(b - (tolerance / 2.0), 0.0),
        MIN(b + (tolerance / 2.0), 255.0)
    };

    int byteIndex = 0;

    while (byteIndex < bitmapByteCount) {
        unsigned char red   = rawData[byteIndex];
        unsigned char green = rawData[byteIndex + 1];
        unsigned char blue  = rawData[byteIndex + 2];

        if (((red >= redRange[0]) && (red <= redRange[1])) &&
            ((green >= greenRange[0]) && (green <= greenRange[1])) &&
            ((blue >= blueRange[0]) && (blue <= blueRange[1]))) {
            // make the pixel transparent
            //
            rawData[byteIndex] = 0;
            rawData[byteIndex + 1] = 0;
            rawData[byteIndex + 2] = 0;
            rawData[byteIndex + 3] = 0;
        }

        byteIndex += 4;
    }

    CGImageRef imgref = CGBitmapContextCreateImage(context);
    UIImage *result = [UIImage imageWithCGImage:imgref];

    CGImageRelease(imgref);
    CGContextRelease(context);
    free(rawData);

    return result;
}
Sam Spencer
  • 8,492
  • 12
  • 76
  • 133
PKCLsoft
  • 1,359
  • 2
  • 26
  • 35
  • 3
    This is my favorite answer for me, as there can be tolerance. Nota : tolerance is between 0 and 255, so it has no effect below 1. A smooth version would now be perfect ! – Diwann Jun 21 '12 at 17:48
  • 16
    Great method! b.t.w. when you supply [UIColor whiteColor], it won't work, due to the nature that it's a special kind a subclass of UIColor. When you do [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f] instead it does work. Might save the next person some debugging time. – Toad Feb 12 '13 at 15:46
  • @Toad great spot! I'm sure that was quite annoying. When I wrote this, the color object was being constructed via sliders, so I didn't see this problem. – PKCLsoft Feb 13 '13 at 01:13
  • 1
    Useful answer (which I've already upvoted) but shouldn't there be a `CGImageRelease` for the `CGBitmapContextCreateImage`? e.g.: `CGImageRef imgref = CGBitmapContextCreateImage(context); UIImage *result = [UIImage imageWithCGImage:imgref]; CGImageRelease(imgref);` –  Oct 09 '14 at 14:05
  • One issue I found with this code is that if I provide for example [UIColor whiteColor], it actually becomes a Gray Scale color with White/Alpha components instead of RGBA, so when you retrieve the components you get a R(1), G(1) and B(0). That issue is described here http://stackoverflow.com/questions/4700168/get-rgb-value-from-uicolor-presets – manecosta Nov 12 '14 at 15:23
  • Right, I didn't see his comment I guess. Anyway, I do know that if I use an RGB initializer it works fine. But ideally the code would be able to work with any type of color. That issue is described in that link I provided, though the solution in my opinion is far from satisfactory. – manecosta Nov 13 '14 at 16:38
  • 1
    Superb answer, thanks! It really helped me out with my current struggle :) I also made some changes in it, if you would like to make white transparent on a black and white image, with changing alpha to a value of how white one pixel is. It can come in handy when you are using a black and white image for masking a layer for example. I posted my function below. – romeouald Jun 27 '16 at 11:09
17

This is a tweak of yubenyi's code that will work with multiple passes. It strips the alpha channel before processing by converting the image to an uncompressed jpeg. Also added some comments on how the color range selection works.

-(UIImage *)changeWhiteColorTransparent: (UIImage *)image
{
    //convert to uncompressed jpg to remove any alpha channels
    //this is a necessary first step when processing images that already have transparency
    image = [UIImage imageWithData:UIImageJPEGRepresentation(image, 1.0)];
    CGImageRef rawImageRef=image.CGImage;
    //RGB color range to mask (make transparent)  R-Low, R-High, G-Low, G-High, B-Low, B-High
    const double colorMasking[6] = {222, 255, 222, 255, 222, 255};

    UIGraphicsBeginImageContext(image.size);
    CGImageRef maskedImageRef=CGImageCreateWithMaskingColors(rawImageRef, colorMasking);

    //iPhone translation
    CGContextTranslateCTM(UIGraphicsGetCurrentContext(), 0.0, image.size.height);
    CGContextScaleCTM(UIGraphicsGetCurrentContext(), 1.0, -1.0); 

    CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, image.size.width, image.size.height), maskedImageRef);
    UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
    CGImageRelease(maskedImageRef);
    UIGraphicsEndImageContext();    
    return result;
}
Kyle
  • 17,317
  • 32
  • 140
  • 246
bgolson
  • 3,460
  • 5
  • 24
  • 41
14

After use of your functions, I found more simpler way of making transparent background for UIImage :)

For example, you have PNG image with black background and you want make this background transparent on screen.

You can try this:

UIImage * image = [UIImage imageNamed:@"image.png"];

const CGFloat colorMasking[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
image = [UIImage imageWithCGImage: CGImageCreateWithMaskingColors(image.CGImage, colorMasking)];

In Swift 5:

let colorMasking: [CGFloat] = [0, 0, 0, 0, 0, 0]
guard let cgImage = cgImage?.copy(maskingColorComponents: colorMasking) else { return }
image = UIImage(cgImage: cgImage)

You have got image with transparent background.

That's all :)

Laszlo
  • 2,803
  • 2
  • 28
  • 33
Vitaly
  • 141
  • 1
  • 3
  • Isn't this basically the same as a small part of the accepted answer, and I agree with @AshishMathur that it's not a complete solution. – PKCLsoft Feb 13 '13 at 01:14
12
-(void)changeColor
{
    UIImage *temp23=[UIImage imageNamed:@"leaf.png"];
    CGImageRef ref1=[self createMask:temp23];
    const float colorMasking[6] = {1.0, 2.0, 1.0, 1.0, 1.0, 1.0};
    CGImageRef New=CGImageCreateWithMaskingColors(ref1, colorMasking);
    UIImage *resultedimage=[UIImage imageWithCGImage:New];
}

-(CGImageRef)createMask:(UIImage*)temp
{
    CGImageRef ref=temp.CGImage;
    int mWidth=CGImageGetWidth(ref);
    int mHeight=CGImageGetHeight(ref);
    int count=mWidth*mHeight*4;
    void *bufferdata=malloc(count);

    CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
    CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;

    CGContextRef cgctx = CGBitmapContextCreate (bufferdata,mWidth,mHeight, 8,mWidth*4, colorSpaceRef, kCGImageAlphaPremultipliedFirst); 

    CGRect rect = {0,0,mWidth,mHeight};
    CGContextDrawImage(cgctx, rect, ref); 
    bufferdata = CGBitmapContextGetData (cgctx);

    CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, bufferdata, mWidth*mHeight*4, NULL);
    CGImageRef savedimageref = CGImageCreate(mWidth,mHeight, 8, 32, mWidth*4, colorSpaceRef, bitmapInfo,provider , NULL, NO, renderingIntent);
    CFRelease(colorSpaceRef);
    return savedimageref;
}   

The above code is tested and I changed the green color to red color by using mask

nhgrif
  • 61,578
  • 25
  • 134
  • 173
  • 2
    your code works great.but how do i change red to green.or any other color – Rahul Vyas Sep 24 '09 at 16:29
  • 2
    Thank you for good function. But I could not get true result until I did not make this change: kCGImageAlphaPremultipliedFirst -> kCGImageAlphaPremultipliedLast After that function gave me true result. :) Thank you. – Vitaly Jul 04 '10 at 15:22
  • how can I change green color to black color? – isarathg Aug 09 '11 at 14:02
  • Looks like there are memory leaks here. You need to release the bitmap context: `CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate(nil, size.width, size.height, 8, 0, colorSpace, kCGBitmapAlphaInfoMask); /* (Other code) */ CGContextRelease(context); CGColorSpaceRelease(colorSpace);` – Jonny Apr 03 '12 at 03:00
  • 1
    @nasif can you explain what is colorMasking[6] ? and colorMasking? i want to change color black in my image to another color for example red – tamtoum1987 Jul 15 '16 at 08:34
12

Swift 4: (for white masking)

extension UIImage { 
func imageByMakingWhiteBackgroundTransparent() -> UIImage? {

    let image = UIImage(data: UIImageJPEGRepresentation(self, 1.0)!)!
    let rawImageRef: CGImage = image.cgImage!

    let colorMasking: [CGFloat] = [222, 255, 222, 255, 222, 255]
    UIGraphicsBeginImageContext(image.size);

    let maskedImageRef = rawImageRef.copy(maskingColorComponents: colorMasking)
    UIGraphicsGetCurrentContext()?.translateBy(x: 0.0,y: image.size.height)
    UIGraphicsGetCurrentContext()?.scaleBy(x: 1.0, y: -1.0)
    UIGraphicsGetCurrentContext()?.draw(maskedImageRef!, in: CGRect.init(x: 0, y: 0, width: image.size.width, height: image.size.height))
    let result = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return result

    }
}
fegoulart
  • 1,706
  • 2
  • 12
  • 9
11

this function can work!

-(UIImage *)changeWhiteColorTransparent: (UIImage *)image
{
    CGImageRef rawImageRef=image.CGImage;

    const float colorMasking[6] = {222, 255, 222, 255, 222, 255};

    UIGraphicsBeginImageContext(image.size);
    CGImageRef maskedImageRef=CGImageCreateWithMaskingColors(rawImageRef, colorMasking);
    {
        //if in iphone
        CGContextTranslateCTM(UIGraphicsGetCurrentContext(), 0.0, image.size.height);
        CGContextScaleCTM(UIGraphicsGetCurrentContext(), 1.0, -1.0); 
    }

    CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, image.size.width, image.size.height), maskedImageRef);
    UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
    CGImageRelease(maskedImageRef);
    UIGraphicsEndImageContext();    
    return result;
}
Daniel Schneller
  • 13,728
  • 5
  • 43
  • 72
yubenyi
  • 620
  • 5
  • 10
4

Based on @PKCLsoft's superb answer, I made an edit where it only makes white color transparent, but it makes alpha based on how "white" the pixel is. This could come handy, when you try to make a mask from a black and white image, so it will give smooth edges on texts or shapes.

- (UIImage *)makeWhiteColorTransparentInImage:(UIImage *)image {
   CGImageRef imageRef = [image CGImage];

   NSUInteger width = CGImageGetWidth(imageRef);
   NSUInteger height = CGImageGetHeight(imageRef);
   CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

   NSUInteger bytesPerPixel = 4;
   NSUInteger bytesPerRow = bytesPerPixel * width;
   NSUInteger bitsPerComponent = 8;
   NSUInteger bitmapByteCount = bytesPerRow * height;

   unsigned char *rawData = (unsigned char*) calloc(bitmapByteCount, sizeof(unsigned char));

   CGContextRef context = CGBitmapContextCreate(rawData, width, height,
                                             bitsPerComponent, bytesPerRow, colorSpace,
                                             kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
   CGColorSpaceRelease(colorSpace);

   CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);

   int byteIndex = 0;

   while (byteIndex < bitmapByteCount) {
      unsigned char red   = rawData[byteIndex];
      unsigned char green = rawData[byteIndex + 1];
      unsigned char blue  = rawData[byteIndex + 2];
      unsigned char alpha  = rawData[byteIndex + 3];

      if (alpha == 0.0) {
          rawData[byteIndex + 3] = 0.0;
      } else {
          rawData[byteIndex + 3] = 255.0 - (red + green + blue) / 3.0;
      }

      byteIndex += 4;
    }

    CGImageRef imgref = CGBitmapContextCreateImage(context);
    UIImage *result = [UIImage imageWithCGImage:imgref];

    CGImageRelease(imgref);
    CGContextRelease(context);
    free(rawData);

    return result;
}

I hope it helps someone some day :)

Sam Spencer
  • 8,492
  • 12
  • 76
  • 133
romeouald
  • 176
  • 2
  • 4
2

UIImage extension in swift 5 with possibility to change any color to transparent. Based on @fegoulart answer.

extension UIImage {
func remove(color: UIColor) -> UIImage? {
    
    let cgColor = color.cgColor
    let components = cgColor.components
    var r = components?[0] ?? 0.0
    var g = components?[1] ?? 0.0
    var b = components?[2] ?? 0.0
    
    r = r * 255.0
    g = g * 255.0
    b = b * 255.0
    
    let colorMasking: [CGFloat] = [r, r, g, g, b, b]
    
    let image = UIImage(data: self.jpegData(compressionQuality: 1.0)!)!
    let rawImageRef: CGImage = image.cgImage!
    
    UIGraphicsBeginImageContext(image.size);
    
    let maskedImageRef = rawImageRef.copy(maskingColorComponents: colorMasking)
    UIGraphicsGetCurrentContext()?.translateBy(x: 0.0,y: image.size.height)
    UIGraphicsGetCurrentContext()?.scaleBy(x: 1.0, y: -1.0)
    UIGraphicsGetCurrentContext()?.draw(maskedImageRef!, in: CGRect.init(x: 0, y: 0, width: image.size.width, height: image.size.height))
    let result = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return result
}
}
sold
  • 314
  • 1
  • 3
  • 11
2

cleaned up version of answer above removing removing some unnecessary

 extension UIImage {
  func remove(color: UIColor, tolerance: CGFloat = 4) -> UIImage {
    let ciColor = CIColor(color: color)

    let maskComponents: [CGFloat] = [ciColor.red, ciColor.green, ciColor.blue].flatMap { value in
      [(value * 255) - tolerance, (value * 255) + tolerance]
    }

    guard let masked = cgImage?.copy(maskingColorComponents: maskComponents) else { return self }
    return UIImage(cgImage: masked)
  }
}
1

swift version for White background color to transparent color

extension UIImage {
    func imageByMakingWhiteBackgroundTransparent() -> UIImage? {

        let image = UIImage(data: self.jpegData(compressionQuality: 1.0)!)!
        let rawImageRef: CGImage = image.cgImage!

        let colorMasking: [CGFloat] = [222, 255, 222, 255, 222, 255]
        UIGraphicsBeginImageContext(image.size);

        let maskedImageRef = rawImageRef.copy(maskingColorComponents: colorMasking)
        UIGraphicsGetCurrentContext()?.translateBy(x: 0.0,y: image.size.height)
        UIGraphicsGetCurrentContext()?.scaleBy(x: 1.0, y: -1.0)
        UIGraphicsGetCurrentContext()?.draw(maskedImageRef!, in: CGRect.init(x: 0, y: 0, width: image.size.width, height: image.size.height))
        let result = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        return result

    }

}
Jagveer Singh
  • 2,258
  • 19
  • 34
0

I've never done anything like this myself, but it looks like CGImageCreateWithMaskingColors may be of use here. Using Core Graphics is beyond the scope of what I can explain in an answer on this site, though; take a look at your documentation and search for tutorials on the Web.

Becca Royal-Gordon
  • 17,541
  • 7
  • 56
  • 91