Skip to content

Instantly share code, notes, and snippets.

@iamjason
Last active December 10, 2019 21:59
Show Gist options
  • Star 21 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save iamjason/a0a92845094f5b210cf8 to your computer and use it in GitHub Desktop.
Save iamjason/a0a92845094f5b210cf8 to your computer and use it in GitHub Desktop.
Swift UIImage extension for tinting images
/**
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
}
}
@JeanMeche
Copy link

Nice extension, I would suggest to add retina images support ! Using :

 let scale = UIScreen.mainScreen().scale
 var size = self.size
 size.height *= scale
 size.width  *= scale

@DroidFxGit
Copy link

needs an update in kCGBlendModeMultiply, now it's CGBlendMode.Multiply

@ismetanin
Copy link

If you want add retina images support, there is a simpler solution:
UIGraphicsBeginImageContextWithOptions(self.size, false, UIScreen.mainScreen().scale)

@98chimp
Copy link

98chimp commented Jul 5, 2017

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
}

@markph0204
Copy link

markph0204 commented Jul 16, 2017

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
    }
}

@noordawod
Copy link

noordawod commented Aug 21, 2017

I would add the following just before the guard:

defer { UIGraphicsEndImageContext() }

Obviously, remove it from the end of the method.

@Silvaire
Copy link

Silvaire commented Sep 9, 2017

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:

original texture

Using this gist with texture.tint(with: UIColor(red:0, green:0, blue:1, alpha:1)) gives the following fully blue texture:

texture with this gist

This other gist gives the following, correct according to me:
texture with other gist

@Silvaire
Copy link

Silvaire commented Sep 9, 2017

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
    }

}

original grayscale image

after texture.tint(UIColor(red:0, green:0, blue:1, alpha:1)) it becomes:

tinted image

@MojtabaHs
Copy link

MojtabaHs commented Jan 27, 2018

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
        
    }
    
}

@SammyVimes
Copy link

SammyVimes commented Apr 5, 2018

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)
    }

}

@Blackjacx
Copy link

Blackjacx commented Dec 3, 2018

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
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment