-
-
Save iamjason/a0a92845094f5b210cf8 to your computer and use it in GitHub Desktop.
/** | |
Usage: | |
let originalImage = UIImage(named: "cat") | |
let tintedImage = originalImage.tintWithColor(UIColor(red: 0.9, green: 0.7, blue: 0.4, alpha: 1.0)) | |
*/ | |
extension UIImage { | |
func tintWithColor(color:UIColor)->UIImage { | |
UIGraphicsBeginImageContext(self.size) | |
let context = UIGraphicsGetCurrentContext() | |
// flip the image | |
CGContextScaleCTM(context, 1.0, -1.0) | |
CGContextTranslateCTM(context, 0.0, -self.size.height) | |
// multiply blend mode | |
CGContextSetBlendMode(context, kCGBlendModeMultiply) | |
let rect = CGRectMake(0, 0, self.size.width, self.size.height) | |
CGContextClipToMask(context, rect, self.CGImage) | |
color.setFill() | |
CGContextFillRect(context, rect) | |
// create uiimage | |
let newImage = UIGraphicsGetImageFromCurrentImageContext() | |
UIGraphicsEndImageContext() | |
return newImage | |
} | |
} | |
needs an update in kCGBlendModeMultiply, now it's CGBlendMode.Multiply
If you want add retina images support, there is a simpler solution:
UIGraphicsBeginImageContextWithOptions(self.size, false, UIScreen.mainScreen().scale)
I have this updated version for Swift 3
working well on my end:
func tint(with color: UIColor) -> UIImage
{
UIGraphicsBeginImageContext(self.size)
guard let context = UIGraphicsGetCurrentContext() else { return self }
// flip the image
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: 0.0, y: -self.size.height)
// multiply blend mode
context.setBlendMode(.multiply)
let rect = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)
context.clip(to: rect, mask: self.cgImage!)
color.setFill()
context.fill(rect)
// create UIImage
guard let newImage = UIGraphicsGetImageFromCurrentImageContext() else { return self }
UIGraphicsEndImageContext()
return newImage
}
Here is everything with Retina, updates for Swift 3.1 and inclusion of proper import:
// Usage:
// let originalImage = UIImage(named: "cat")
// let tintedImage = originalImage.tintWithColor(UIColor(red: 0.9, green: 0.7, blue: 0.4, alpha: 1.0))
// reference: https://gist.github.com/iamjason/a0a92845094f5b210cf8
// modified to include retina
// Updated and tested for Swift 3.1
import UIKit
extension UIImage {
func tint(with color: UIColor) -> UIImage
{
UIGraphicsBeginImageContextWithOptions(self.size, false, UIScreen.main.scale)
guard let context = UIGraphicsGetCurrentContext() else { return self }
// flip the image
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: 0.0, y: -self.size.height)
// multiply blend mode
context.setBlendMode(.multiply)
let rect = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)
context.clip(to: rect, mask: self.cgImage!)
color.setFill()
context.fill(rect)
// create UIImage
guard let newImage = UIGraphicsGetImageFromCurrentImageContext() else { return self }
UIGraphicsEndImageContext()
return newImage
}
}
I would add the following just before the guard:
defer { UIGraphicsEndImageContext() }
Obviously, remove it from the end of the method.
Maybe there's something I do not get, but using this gist doesn't seem to produce the expected result for me... Trying to tint the following texture:
Using this gist with texture.tint(with: UIColor(red:0, green:0, blue:1, alpha:1))
gives the following fully blue texture:
This other gist gives the following, correct according to me:
My bad, this does indeed seem to work (but in a different way, I guess it depends on your use case) when using grayscale images as the source.
However, here is the gist rewritten using the other one's structure. It seems to be quite a lot more memory efficient:
// Usage:
// let originalImage = UIImage(named: "cat")
// let tintedImage = originalImage.tint(UIColor(red: 0.9, green: 0.7, blue: 0.4, alpha: 1.0))
// reference: https://gist.github.com/iamjason/a0a92845094f5b210cf8
// modified to include retina
// Updated and tested for Swift 3.1
// refactored for memory purpose according to https://gist.github.com/lynfogeek/4b6ce0117fb0acdabe229f6d8759a139
import UIKit
extension UIImage {
func tint(_ tintColor: UIColor?) -> UIImage {
guard let tintColor = tintColor else { return self }
return modifiedImage { context, rect in
context.setBlendMode(.multiply)
context.clip(to: rect, mask: self.cgImage!)
tintColor.setFill()
context.fill(rect)
}
}
private func modifiedImage( draw: (CGContext, CGRect) -> ()) -> UIImage {
// using scale correctly preserves retina images
UIGraphicsBeginImageContextWithOptions(size, false, scale)
defer { UIGraphicsEndImageContext() }
guard let context = UIGraphicsGetCurrentContext() else { return self }
// correctly rotate image
context.translateBy(x: 0, y: size.height)
context.scaleBy(x: 1.0, y: -1.0)
let rect = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)
draw(context, rect)
guard let newImage = UIGraphicsGetImageFromCurrentImageContext() else { return self }
return newImage
}
}
after texture.tint(UIColor(red:0, green:0, blue:1, alpha:1))
it becomes:
Swift 4:
extension UIImage {
func tinted(color: UIColor) -> UIImage {
UIGraphicsBeginImageContext(self.size)
guard let context = UIGraphicsGetCurrentContext() else { return self }
guard let cgImage = cgImage else { return self }
// flip the image
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: 0.0, y: -size.height)
// multiply blend mode
context.setBlendMode(.multiply)
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
context.clip(to: rect, mask: cgImage)
color.setFill()
context.fill(rect)
// create uiimage
guard let newImage = UIGraphicsGetImageFromCurrentImageContext() else { return self }
UIGraphicsEndImageContext()
return newImage
}
}
Swift 3 with cap insets from original image (image will resize the way you set insets):
extension UIImage {
func tint(with color: UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.size, false, UIScreen.main.scale)
guard let context = UIGraphicsGetCurrentContext() else { return self }
// flip the image
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: 0.0, y: -self.size.height)
// multiply blend mode
context.setBlendMode(.multiply)
let rect = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)
context.clip(to: rect, mask: self.cgImage!)
color.setFill()
context.fill(rect)
// create UIImage
guard let newImage = UIGraphicsGetImageFromCurrentImageContext() else { return self }
UIGraphicsEndImageContext()
return newImage.resizableImage(withCapInsets: self.capInsets, resizingMode: self.resizingMode)
}
}
Omit self
when you don't need it:
func setTint(_ color: UIColor) -> UIImage {
defer { UIGraphicsEndImageContext() }
UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale)
guard let context = UIGraphicsGetCurrentContext() else { return self }
// flip the image
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: 0.0, y: -size.height)
// multiply blend mode
context.setBlendMode(.multiply)
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
context.clip(to: rect, mask: cgImage!)
color.setFill()
context.fill(rect)
// create UIImage
guard let newImage = UIGraphicsGetImageFromCurrentImageContext() else { return self }
return newImage
}
Nice extension, I would suggest to add retina images support ! Using :