Changing UIImage color

I'm trying to change color of UIImage. My code:

-(UIImage *)coloredImage:(UIImage *)firstImage withColor:(UIColor *)color {
UIGraphicsBeginImageContext(firstImage.size);


CGContextRef context = UIGraphicsGetCurrentContext();
[color setFill];


CGContextTranslateCTM(context, 0, firstImage.size.height);
CGContextScaleCTM(context, 1.0, -1.0);


CGContextSetBlendMode(context, kCGBlendModeCopy);
CGRect rect = CGRectMake(0, 0, firstImage.size.width, firstImage.size.height);
CGContextDrawImage(context, rect, firstImage.CGImage);


CGContextClipToMask(context, rect, firstImage.CGImage);
CGContextAddRect(context, rect);
CGContextDrawPath(context,kCGPathElementMoveToPoint);


UIImage *coloredImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();


return coloredImg;
}

This code works, but obtained image is not so well as shoud be: bounds pixels of returned image are intermittent and not so smooth as in my first image. How can I resolve this problem?

112072 次浏览

Another way to tint an image is to simply multiply it by a constant color. Sometimes, this is preferable because it doesn't "lift" the color values in black areas; it keeps the relative intensities in the image the same. Using an overlay as a tint tends to flatten out the contrast.

This is the code I use:

UIImage *MultiplyImageByConstantColor( UIImage *image, UIColor *color ) {


CGSize backgroundSize = image.size;
UIGraphicsBeginImageContext(backgroundSize);


CGContextRef ctx = UIGraphicsGetCurrentContext();


CGRect backgroundRect;
backgroundRect.size = backgroundSize;
backgroundRect.origin.x = 0;
backgroundRect.origin.y = 0;


CGFloat r,g,b,a;
[color getRed:&r green:&g blue:&b alpha:&a];
CGContextSetRGBFillColor(ctx, r, g, b, a);
CGContextFillRect(ctx, backgroundRect);


CGRect imageRect;
imageRect.size = image.size;
imageRect.origin.x = (backgroundSize.width - image.size.width)/2;
imageRect.origin.y = (backgroundSize.height - image.size.height)/2;


// Unflip the image
CGContextTranslateCTM(ctx, 0, backgroundSize.height);
CGContextScaleCTM(ctx, 1.0, -1.0);


CGContextSetBlendMode(ctx, kCGBlendModeMultiply);
CGContextDrawImage(ctx, imageRect, image.CGImage);


UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();


UIGraphicsEndImageContext();


return newImage;
}

Swift version

extension UIImage{


static func multiplyImageByConstantColor(image:UIImage,color:UIColor)->UIImage{
let backgroundSize = image.size
UIGraphicsBeginImageContext(backgroundSize)


let ctx = UIGraphicsGetCurrentContext()


var backgroundRect=CGRect()
backgroundRect.size = backgroundSize
backgroundRect.origin.x = 0
backgroundRect.origin.y = 0


var r:CGFloat
var g:CGFloat
var b:CGFloat
var a:CGFloat
color.getRed(&r, green: &g, blue: &b, alpha: &a)
CGContextSetRGBFillColor(ctx, r, g, b, a)
CGContextFillRect(ctx, backgroundRect)


var imageRect=CGRect()
imageRect.size = image.size
imageRect.origin.x = (backgroundSize.width - image.size.width)/2
imageRect.origin.y = (backgroundSize.height - image.size.height)/2


// Unflip the image
CGContextTranslateCTM(ctx, 0, backgroundSize.height)
CGContextScaleCTM(ctx, 1.0, -1.0)


CGContextSetBlendMode(ctx, .Multiply)
CGContextDrawImage(ctx, imageRect, image.CGImage)


let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
}

This is pretty much the answer above, but slightly shortened. This only takes the image as a mask and does not actually "multiply" or color the image.

Objective C:

    UIColor *color = <# UIColor #>;
UIImage *image = <# UIImage #>;// Image to mask with
UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale);
CGContextRef context = UIGraphicsGetCurrentContext();
[color setFill];
CGContextTranslateCTM(context, 0, image.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextClipToMask(context, CGRectMake(0, 0, image.size.width, image.size.height), [image CGImage]);
CGContextFillRect(context, CGRectMake(0, 0, image.size.width, image.size.height));


UIImage *coloredImg = UIGraphicsGetImageFromCurrentImageContext();


UIGraphicsEndImageContext();

Swift:

    let color: UIColor = <# UIColor #>
let image: UIImage = <# UIImage #> // Image to mask with
UIGraphicsBeginImageContextWithOptions(image.size, false, image.scale)
let context = UIGraphicsGetCurrentContext()
color.setFill()
context?.translateBy(x: 0, y: image.size.height)
context?.scaleBy(x: 1.0, y: -1.0)
context?.clip(to: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height), mask: image.cgImage!)
context?.fill(CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height))
let coloredImg = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

Since iOS 7, this is the most simple way of doing it.

Objective-C:

theImageView.image = [theImageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[theImageView setTintColor:[UIColor redColor]];

Swift 2.0:

theImageView.image = theImageView.image?.imageWithRenderingMode(.AlwaysTemplate)
theImageView.tintColor = UIColor.magentaColor()

Swift 4.0:

theImageView.image = theImageView.image?.withRenderingMode(.alwaysTemplate)
theImageView.tintColor = .magenta

Storyboard:

First configure the image as template ( on right bar - Render as) in your assets. Then the color of the image would be the tint color applied. enter image description here

In Swift 3.0

imageView.image? = (imageView.image?.withRenderingMode(.alwaysTemplate))!
imageView.tintColor = UIColor.magenta

In Swift 2.0

yourImage.image? = (yourImage.image?.imageWithRenderingMode(.AlwaysTemplate))!
yourImage.tintColor = UIColor.magentaColor()

Enjoy you Swift pioneers

Base on @Anna's answer and I rewrite for swift 2.2 and handles image with alpha channel:

static func multiplyImageByConstantColor(image:UIImage,color:UIColor)->UIImage{
let backgroundSize = image.size
UIGraphicsBeginImageContext(backgroundSize)


let ctx = UIGraphicsGetCurrentContext()


var backgroundRect=CGRect()
backgroundRect.size = backgroundSize
backgroundRect.origin.x = 0
backgroundRect.origin.y = 0


var r:CGFloat = 0
var g:CGFloat = 0
var b:CGFloat = 0
var a:CGFloat = 0
color.getRed(&r, green: &g, blue: &b, alpha: &a)
CGContextSetRGBFillColor(ctx, r, g, b, a)


// Unflip the image
CGContextTranslateCTM(ctx, 0, backgroundSize.height)
CGContextScaleCTM(ctx, 1.0, -1.0)
CGContextClipToMask(ctx, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage);
CGContextFillRect(ctx, backgroundRect)


var imageRect=CGRect()
imageRect.size = image.size
imageRect.origin.x = (backgroundSize.width - image.size.width)/2
imageRect.origin.y = (backgroundSize.height - image.size.height)/2




CGContextSetBlendMode(ctx, .Multiply)
CGContextDrawImage(ctx, imageRect, image.CGImage)


let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}

Anna's code works well for copying a UIImage.image over a colored .image background by using kCGBlendModeNormal rather than kCGBlendModeMultiply. For instance, self.mainImage.image = [self NormalImageByConstantColor: self.mainImage.image withColor: yourColor]; will place the contents of mainImage.image over the tint yourColor while preserving the opacity of yourColor. This solved my problem of placing a background color with opacity behind an image to be saved to the Camera Roll.

Swift 3.0 version of Anna's marvelous code:

extension UIImage{


static func multiplyImageByConstantColor(image:UIImage,color:UIColor)-> UIImage {
let backgroundSize = image.size
UIGraphicsBeginImageContext(backgroundSize)


let ctx = UIGraphicsGetCurrentContext()!


var backgroundRect=CGRect()
backgroundRect.size = backgroundSize
backgroundRect.origin.x = 0
backgroundRect.origin.y = 0


let myFloatForR = 0
var r = CGFloat(myFloatForR)
let myFloatForG = 0
var g = CGFloat(myFloatForG)
let myFloatForB = 0
var b = CGFloat(myFloatForB)
let myFloatForA = 0
var a = CGFloat(myFloatForA)


color.getRed(&r, green: &g, blue: &b, alpha: &a)
ctx.setFillColor(red: r, green: g, blue: b, alpha: a)
ctx.fill(backgroundRect)


var imageRect=CGRect()
imageRect.size = image.size
imageRect.origin.x = (backgroundSize.width - image.size.width)/2
imageRect.origin.y = (backgroundSize.height - image.size.height)/2


// Unflip the image
ctx.translateBy(x: 0, y: backgroundSize.height)
ctx.scaleBy(x: 1.0, y: -1.0)


ctx.setBlendMode(.multiply)
ctx.draw(image.cgImage!, in: imageRect)


let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
}

If you don't have to do it programmatically, you can just do it using Xcode UI.

If you go to the image in your image assets folder, open the inspector on the right hand side and there is a "Render As" dropdown with the following options:

  1. Default
  2. Original
  3. Template

Once you've made the Template selection, you can change the tintColor of the image however you want - whether it is using the Xcode storyboard UI or programmatically.

enter image description here

See this image:

enter image description here

Swift 3:

extension UIImage{


static func multiplyImageByConstantColor(image:UIImage,color:UIColor) -> UIImage{


let backgroundSize = image.size
UIGraphicsBeginImageContext(backgroundSize)


guard let ctx = UIGraphicsGetCurrentContext() else {return image}


var backgroundRect=CGRect()
backgroundRect.size = backgroundSize
backgroundRect.origin.x = 0
backgroundRect.origin.y = 0


var r:CGFloat = 0
var g:CGFloat = 0
var b:CGFloat = 0
var a:CGFloat = 0
color.getRed(&r, green: &g, blue: &b, alpha: &a)
ctx.setFillColor(red: r, green: g, blue: b, alpha: a)


// Unflip the image
ctx.translateBy(x: 0, y: backgroundSize.height)
ctx.scaleBy(x: 1.0, y: -1.0)
ctx.clip(to: CGRect(0, 0, image.size.width, image.size.height), mask: image.cgImage!)
ctx.fill(backgroundRect)




var imageRect=CGRect()
imageRect.size = image.size
imageRect.origin.x = (backgroundSize.width - image.size.width)/2
imageRect.origin.y = (backgroundSize.height - image.size.height)/2




ctx.setBlendMode(.multiply)
ctx.draw(image.cgImage!, in: imageRect)


let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
}

Here's my adaptation of @Anna's answer. Two key points here:

  • Use destinationIn blending mode
  • Call UIGraphicsBeginImageContextWithOptions(backgroundSize, false, UIScreen.main.scale) to get smooth image

Code in in Swift 3:

extension UIImage {


static func coloredImage(image: UIImage?, color: UIColor) -> UIImage? {


guard let image = image else {
return nil
}


let backgroundSize = image.size
UIGraphicsBeginImageContextWithOptions(backgroundSize, false, UIScreen.main.scale)


let ctx = UIGraphicsGetCurrentContext()!


var backgroundRect=CGRect()
backgroundRect.size = backgroundSize
backgroundRect.origin.x = 0
backgroundRect.origin.y = 0


var r:CGFloat = 0
var g:CGFloat = 0
var b:CGFloat = 0
var a:CGFloat = 0
color.getRed(&r, green: &g, blue: &b, alpha: &a)
ctx.setFillColor(red: r, green: g, blue: b, alpha: a)
ctx.fill(backgroundRect)


var imageRect = CGRect()
imageRect.size = image.size
imageRect.origin.x = (backgroundSize.width - image.size.width) / 2
imageRect.origin.y = (backgroundSize.height - image.size.height) / 2


// Unflip the image
ctx.translateBy(x: 0, y: backgroundSize.height)
ctx.scaleBy(x: 1.0, y: -1.0)


ctx.setBlendMode(.destinationIn)
ctx.draw(image.cgImage!, in: imageRect)


let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
}

Swift 4.2 Solution

extension UIImage {
func withColor(_ color: UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, false, scale)
guard let ctx = UIGraphicsGetCurrentContext(), let cgImage = cgImage else { return self }
color.setFill()
ctx.translateBy(x: 0, y: size.height)
ctx.scaleBy(x: 1.0, y: -1.0)
ctx.clip(to: CGRect(x: 0, y: 0, width: size.width, height: size.height), mask: cgImage)
ctx.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height))
guard let colored = UIGraphicsGetImageFromCurrentImageContext() else { return self }
UIGraphicsEndImageContext()
return colored
}
}


// Usage:
// let redImage = UIImage().withColor(.red)

Starting from iOS 10 you can use UIGraphicsImageRenderer:

extension UIImage {


func colored(_ color: UIColor) -> UIImage {
let renderer = UIGraphicsImageRenderer(size: size)
return renderer.image { context in
color.setFill()
self.draw(at: .zero)
context.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height), blendMode: .sourceAtop)
}
}


}

For iOS 13 and newer:

let redImage = image.withTintColor(.red, renderingMode: .alwaysTemplate)