How to make one color transparent on a UIImage?
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?
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;
}
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;
}