0

I'm trying out this code to tint color a UIImage but it seems to be inverting the colors - coloring the background and turning the black stroke to white.

How do I have it color the black stroke lines of an image and leave the white background alone? Here's a playground sample where I try one technique as well as another one suggested by the first answer.

Playground Code

 extension UIImage {

    func tint(color: UIColor, blendMode: CGBlendMode = CGBlendMode.Normal) -> UIImage
    {
        UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale);
        let context = UIGraphicsGetCurrentContext();
        CGContextTranslateCTM(context, 0, self.size.height);
        CGContextScaleCTM(context, 1.0, -1.0);
        CGContextSetBlendMode(context, blendMode);
        let rect = CGRectMake(0, 0, self.size.width, self.size.height);
        CGContextClipToMask(context, rect, self.CGImage);
        color.setFill()
        CGContextFillRect(context, rect);
        let newImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        return newImage;
    }

    func tint2(color: UIColor, blendMode: CGBlendMode) -> UIImage
    {
        let drawRect = CGRectMake(0.0, 0.0, size.width, size.height)
        UIGraphicsBeginImageContextWithOptions(size, false, scale)
        let context = UIGraphicsGetCurrentContext()
        CGContextClipToMask(context, drawRect, CGImage)
        color.setFill()
        UIRectFill(drawRect)
        drawInRect(drawRect, blendMode: blendMode, alpha: 1.0)
        let tintedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return tintedImage
    }

}

let stringUrl = "https://s3.amazonaws.com/neighbor-chat-assets/icons/avatar1000.png"

let url = NSURL(string: stringUrl)

let data = NSData(contentsOfURL: url!)

var originalimage = UIImage(data: data!)

var image = originalimage!.imageWithRenderingMode(.AlwaysTemplate).tint(UIColor.blueColor(), blendMode:.Normal)
var image2 = originalimage!.imageWithRenderingMode(.AlwaysTemplate).tint2(UIColor.blueColor(), blendMode:.Normal)
MonkeyBonkey
  • 46,433
  • 78
  • 254
  • 460

1 Answers1

1

This should work, I just tested it with a png and transparent background

func tint(color: UIColor, blendMode: CGBlendMode = CGBlendMode.Normal) -> UIImage
{
    UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale);
    let context = UIGraphicsGetCurrentContext();
    CGContextTranslateCTM(context, 0, self.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);
    CGContextSetBlendMode(context, blendMode);
    let rect = CGRectMake(0, 0, self.size.width, self.size.height);
    CGContextClipToMask(context, rect, self.CGImage);
    color.setFill()
    CGContextFillRect(context, rect);
    let newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return newImage;
}

Also see this post for further information. The alternative would be to wrap your image in a UIImageView with UIImageRenderingMode.AlwaysTemplate rendering mode, and then set the tintColor property.

EDIT

So this is the function you can use to first mask out certain colors if you don't have an alpha channel in an image

func tint(color: UIColor, blendMode: CGBlendMode = CGBlendMode.Normal, colorToMask: UIColor = UIColor.blackColor()) -> UIImage
{
    var fRed : CGFloat = 0
    var fGreen : CGFloat = 0
    var fBlue : CGFloat = 0
    var fAlpha: CGFloat = 0
    colorToMask.getRed(&fRed, green: &fGreen, blue: &fBlue, alpha: &fAlpha)

    let rect = CGRectMake(0, 0, self.size.width, self.size.height);
    var colorMasking : [CGFloat] = [fRed,fRed,fGreen,fGreen,fBlue,fBlue]
    let newImageRef = CGImageCreateWithMaskingColors(self.CGImage!, &colorMasking)

    UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale);
    let context = UIGraphicsGetCurrentContext();

    CGContextTranslateCTM(context, 0, self.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);
    CGContextSetBlendMode(context, blendMode);
    CGContextClipToMask(context, rect, newImageRef!);
    color.setFill()
    CGContextFillRect(context, rect);

    let newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return newImage;
}

This function first creates a mask depending on the specified color, and clips the context to this mask.

EDIT #2

As stated in the comments, I also didn't manage to mask out the white color, I tried UInt8 values (i.e. 255) and float values (i.e. 1.0), but it still didn't work. So the only solution I could come up with was to invert the image. Here is the function:

func negativeImage() -> UIImage {

    let coreImage = MYImage(CGImage: self.CGImage!)
    let filter = CIFilter(name: "CIColorInvert", withInputParameters: ["inputImage" : coreImage])!
    let result = filter.outputImage!
    let context = CIContext(options:nil)
    let cgimg : CGImageRef = context.createCGImage(result, fromRect: result.extent)

    let bmpcontext = CGBitmapContextCreate(nil, Int(self.size.width), Int(self.size.height), 8, Int(self.size.width)*4, CGColorSpaceCreateDeviceRGB(),CGImageAlphaInfo.NoneSkipLast.rawValue)
    CGContextDrawImage(bmpcontext, CGRectMake(0, 0, self.size.width, self.size.height), cgimg)
    let newImgRef = CGBitmapContextCreateImage(bmpcontext)!
    return UIImage(CGImage: newImgRef)
}

You can use it like this

var image : UIImage = ...
image = image.negativeImage()
image = image.tint(UIColor.redColor())

Here are some general notes

  • CGImageCreateWithMaskingColors requires a CGImage with NO alpha channel, that's why I use the flag CGImageAlphaInfo.NoneSkipLast in the negativeImage function in order to remove the alpha channel again
  • There is absolutely no error checking included in this, you should at least check whether there is an alpha channel included within the tint function using CGImageGetAlphaInfo
  • You can either have the masked out parts (white in our example) transparent (by setting false in the UIGraphicsBeginImageContextWithOptions command within the tint function), or a specific color by setting true, which is black by default (you would need to paint before clipping to the mask).
Community
  • 1
  • 1
peacer212
  • 935
  • 9
  • 15
  • if I wrapped it in an imageview and then pulled the uiimage from it, would it lose the tint? I need it as an uiimage in the end unfortunately. – MonkeyBonkey Sep 22 '15 at 23:42
  • just tried it on this photo http://i300.photobucket.com/albums/nn31/wvufan223/Celtic-Knot-Meanings.png and unfortunately only got a lightly colored background with white lines – MonkeyBonkey Sep 23 '15 at 00:03
  • this one seems to invert the image which I don't want, and it doesn't tint the black stroke colors – MonkeyBonkey Sep 23 '15 at 00:22
  • That's because you don't have an alpha channel in that image, i.e. the function with color the whole image instead of just the black strokes. – peacer212 Sep 23 '15 at 08:08
  • just tried with the 2nd technique with colorToMask being black - which colors the background and turns the stroke to white.. however using color to mask white just turns the whole image to blue.. – MonkeyBonkey Sep 23 '15 at 11:31
  • Check the updated answer, I tried masking out the white color but it didn't work, so I created a function to invert the image. – peacer212 Sep 23 '15 at 12:19