Render a CGImage (rather than UIImage) directly? - ios

I'm making a CGImage
func otf() -> CGImage {
which is a bezier mask on a gradient. So,
// the path
let p = UIBezierPath()
p.moveTo etc
// the mask
let m = CAShapeLayer()
set size etc
m.path = p.cgPath
// the layer
let l = CAGradientLayer()
set colors etc
l.mask = m
it's done. So then render a UIImage in the usual way ...
UIGraphicsBeginImageContextWithOptions(sz.size, false, UIScreen.main.scale)
l.render(in: UIGraphicsGetCurrentContext()!)
let r = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
But. Then to return it, of course you have to convert to a CGImage
return r.cgImage!
(Same deal if you use something like ..
UIGraphicsImageRenderer(bounds: b).image { (c) in v.layer.render(in: c) }
.. you get a UIImage, not a CGImage.)
Seems like there should be a better / more elegant way - is there some way to more directly "build a CGImage", rather than "building a UIImage, and then converting"?

You need to create CGContext to generate direct CGImage
Note:- Use this code to create direct CGImage
func createImage() -> CGImage? {
// the path
var p = UIBezierPath()
p = UIBezierPath(arcCenter: CGPoint(x: 100,y: 100), radius: CGFloat(200), startAngle: CGFloat(0), endAngle:CGFloat(Double.pi * 2), clockwise: true)
// the mask
let m = CAShapeLayer()
m.path = p.cgPath
// the layer
let layer = CAGradientLayer()
layer.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
layer.colors = [UIColor.red.cgColor, UIColor.black.cgColor]
layer.mask = m
let imageSize = CGSize(width: 200, height: 200)
let colorSpace = CGColorSpace(name: CGColorSpace.sRGB)!
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
guard let context = CGContext(data: nil, width: Int(imageSize.width), height: Int(imageSize.height), bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) else { return nil }
layer.render(in: context)
let img = context.makeImage()
return img
}

Related

Saving a UIImage cropped to a CGPath

I am trying to mask a UIImage and then save the masked image. So far, I have got this working when displayed in a UIImageView preview as follows:
let maskLayer = CAShapeLayer()
let maskPath = shape.cgPath
maskLayer.path = maskPath.resized(to: imageView.frame)
maskLayer.fillRule = .evenOdd
imageView.layer.mask = maskLayer
let picture = UIImage(named: "1")!
imageView.contentMode = .scaleAspectFit
imageView.image = picture
where shape is a UIBezierPath().
The resized function is:
extension CGPath {
func resized(to rect: CGRect) -> CGPath {
let boundingBox = self.boundingBox
let boundingBoxAspectRatio = boundingBox.width / boundingBox.height
let viewAspectRatio = rect.width / rect.height
let scaleFactor = boundingBoxAspectRatio > viewAspectRatio ?
rect.width / boundingBox.width :
rect.height / boundingBox.height
let scaledSize = boundingBox.size.applying(CGAffineTransform(scaleX: scaleFactor, y: scaleFactor))
let centerOffset = CGSize(
width: (rect.width - scaledSize.width) / (scaleFactor * 2),
height: (rect.height - scaledSize.height) / (scaleFactor * 2)
)
var transform = CGAffineTransform.identity
.scaledBy(x: scaleFactor, y: scaleFactor)
.translatedBy(x: -boundingBox.minX + centerOffset.width, y: -boundingBox.minY + centerOffset.height)
return copy(using: &transform)!
}
}
So this works in terms of previewing the outcome I'd like. I'd now like to save this modified UIImage to the user's photo album, in it's original size (so basically generate it again but don't resize the image to fit a UIImageView - keep it as-is and apply the mask over it).
I have tried this, but it just saves the original image - no path/mask applied:
func getMaskedImage(path: CGPath) {
let picture = UIImage(named: "1")!
UIGraphicsBeginImageContext(picture.size)
if let context = UIGraphicsGetCurrentContext() {
let pathNew = path.resized(to: CGRect(x: 0, y: 0, width: picture.size.width, height: picture.size.height))
context.addPath(pathNew)
context.clip()
picture.draw(in: CGRect(x: 0.0, y: 0.0, width: picture.size.width, height: picture.size.height))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
UIImageWriteToSavedPhotosAlbum(newImage!, nil, nil, nil)
}
}
What am I doing wrong? Thanks.
Don't throw away your layer-based approach! You can still use that when drawing in a graphics context.
Example:
func getMaskedImage(path: CGPath) -> UIImage? {
let picture = UIImage(named: "my_image")!
let imageLayer = CALayer()
imageLayer.frame = CGRect(origin: .zero, size: picture.size)
imageLayer.contents = picture.cgImage
let maskLayer = CAShapeLayer()
let maskPath = path.resized(to: CGRect(origin: .zero, size: picture.size))
maskLayer.path = maskPath
maskLayer.fillRule = .evenOdd
imageLayer.mask = maskLayer
UIGraphicsBeginImageContext(picture.size)
defer { UIGraphicsEndImageContext() }
if let context = UIGraphicsGetCurrentContext() {
imageLayer.render(in: context)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
return newImage
}
return nil
}
Note that this doesn't actually resize the image. If you want the image resized, you should get the boundingBox of the resized path. Then do this instead:
// create a context as big as the bounding box
UIGraphicsBeginImageContext(boundingBox.size)
defer { UIGraphicsEndImageContext() }
if let context = UIGraphicsGetCurrentContext() {
// move the context to the top left of the path
context.translateBy(x: -boundingBox.origin.x, y: -boundingBox.origin.y)
imageLayer.render(in: context)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
return newImage
}

mask two UIImages by using cgImage.mask not working, but works for imageview.layer.mask

I have two images, which both have alpha channel. I want to comine them together. It do works for UIImageView, but I want to do it by using cgimage, which without creat a UIImageView.
I tried cgimage like this is not working:
func combine3(_ bg: UIImage, cover: UIImage) -> UIImage?{
let size = CGSize(width: self.view.frame.width, height: self.view.frame.height)
UIGraphicsBeginImageContext(size)
let maskRef = cover.cgImage!
let mask = CGImage.init(maskWidth: maskRef.width, height: maskRef.height, bitsPerComponent: maskRef.bitsPerComponent, bitsPerPixel: maskRef.bitsPerPixel, bytesPerRow: maskRef.bytesPerRow, provider: maskRef.dataProvider!, decode: nil, shouldInterpolate: false)
let masked = bg.cgImage?.masking(mask!)
let outPutImage = UIImage(cgImage: masked!)
UIGraphicsEndImageContext()
return outPutImage
}
but for UIImageView, it works pretty good:
let bg = creatBGImageFinal()!
bgIV.image = bg
let cover = createCoverImageFinal(progress: 0)!
let layer = CALayer()
layer.contents = cover.cgImage
layer.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
maskIV.layer.mask = layer
bg:
cover,which is a picture with an white circle in the middle, the others is Transparent:
result:
First, reverse the mask:
The rule for cgImage masking is Dst = 1 - Src.
Then draw the masked image onto image context with opacity:
func combine3(_ bg: UIImage, cover: UIImage, size: CGSize) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, true, 1)
defer {
UIGraphicsEndImageContext()
}
let context = UIGraphicsGetCurrentContext()!
let maskRef = cover.cgImage!
let mask = CGImage.init(maskWidth: maskRef.width, height: maskRef.height, bitsPerComponent: maskRef.bitsPerComponent, bitsPerPixel: maskRef.bitsPerPixel, bytesPerRow: maskRef.bytesPerRow, provider: maskRef.dataProvider!, decode: nil, shouldInterpolate: false)!
let masked = bg.cgImage!.masking(mask)!
// adjust for lower-left-origin CG coordinates
context.translateBy(x: 0, y: size.height)
context.scaleBy(x: 1, y: -1)
context.draw(masked, in: CGRect(origin: .zero, size: size))
return UIGraphicsGetImageFromCurrentImageContext()
}
Result:

UIImage masking doesn't work (Swift, iOS 10)

Trying to mask an image with my custom mask. I think I follow the ideas correctly, but for some reason, image isn't get masked. Instead, masked image, created after masking, contains original cropped image as the mask wasn't applied.
Here's the Swift playground code which one can use in order to test my code (image and mask are attached, just drop them to the resources folder):
import UIKit
extension UIImage {
static func resizeImage(image: UIImage, width: CGFloat) -> UIImage {
let scale = width / image.size.width
let newHeight = round(image.size.height * scale)
UIGraphicsBeginImageContextWithOptions(CGSize(width:width, height:newHeight), false, image.scale)
image.draw(in: CGRect(origin: CGPoint(x:0, y:0), size: CGSize(width: width, height: newHeight)))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
static func resizeImage(image: UIImage, height: CGFloat) -> UIImage {
let scale = height / image.size.height
let newWidth = round(image.size.width * scale)
UIGraphicsBeginImageContextWithOptions(CGSize(width:newWidth, height:height), false, image.scale)
image.draw(in: CGRect(origin: CGPoint(x:0, y:0), size: CGSize(width: newWidth, height: height)))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
}
let image = UIImage(named: "image.jpg")!
var mask = UIImage(named: "mask.jpg")!
let k1 = image.size.width / image.size.height
let k2 = mask.size.width / mask.size.height
if k1 >= k2
{
mask = UIImage.resizeImage(image: mask, height: image.size.height)
}
else
{
mask = UIImage.resizeImage(image: mask, width: image.size.width)
}
image
mask
let center = CGPoint(x: image.size.width/2, y: image.size.height/2)
let croppingRect = CGRect(x: abs(image.size.width-mask.size.width)/2*image.scale,
y: abs(image.size.height-mask.size.height)/2*image.scale,
width: mask.size.width*image.scale,
height: mask.size.height*image.scale).integral
let maskReference = mask.cgImage!
let imageReference = image.cgImage!.cropping(to: croppingRect)!
let imageMask = CGImage(maskWidth: maskReference.width,
height: maskReference.height,
bitsPerComponent: maskReference.bitsPerComponent,
bitsPerPixel: maskReference.bitsPerPixel,
bytesPerRow: maskReference.bytesPerRow,
provider: maskReference.dataProvider!, decode: nil, shouldInterpolate: true)
imageMask?.colorSpace
imageMask?.alphaInfo
let maskedReference = imageReference.masking(imageMask!)
let maskedImage = UIImage(cgImage:maskedReference!, scale: image.scale, orientation: image.imageOrientation)
Swift 4+
let icon = UIImageView(image: YOURIMAGE)
icon.frame = CGRect(x:100, y: 100, width: 100, height: 100)
icon.layer.masksToBounds = true
let maskView = UIImageView()
maskView.image = YOURMASKIMAGE
maskView.frame = icon.bounds
icon.mask = maskView
icon.contentMode = .scaleToFill
icon.clipsToBounds = true
view.addSubview(icon)

Change color of kCGImageAlphaOnly rendered CGImage

I'm trying to take some huge 32bit PNGs that are actually just black with an alpha channel and present them in an iOS app in a memory-friendly way.
To do that I've tried to re-render the images in an "alpha-only" CGContext:
extension UIImage {
func toLayer() -> CALayer? {
let cgImage = self.cgImage!
let height = Int(self.size.height)
let width = Int(self.size.width)
let colorSpace = CGColorSpaceCreateDeviceGray()
let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: width, space: colorSpace, bitmapInfo: CGImageAlphaInfo.alphaOnly.rawValue)!
context.draw(cgImage, in: CGRect(origin: .zero, size: self.size))
let image = context.makeImage()!
let layer = CALayer()
layer.contents = image
layer.contentsScale = self.scale
return layer
}
}
This is awesome! It takes memory usage down from 180MB to about 18MB, which is actually better than I expected.
The issue is, the black (or, now, opaque) parts of the image are no longer black, but are white instead.
It seems like it should be an easy fix to change the coloration of the opaque bits but I can't find any information about it online. Do you have an idea?
I've managed to answer my own question. By setting the alpha-only image as the contents of the output layer's mask, we can set the background colour of the layer to anything we want (including non-greyscale values), and still keep the memory benefits!
I've included the final code because I'm surely not the only one interested in this method:
extension UIImage {
func to8BitLayer(color: UIColor = .black) -> CALayer? {
guard let cgImage = self.cgImage else { return nil }
let height = Int(self.size.height * scale)
let width = Int(self.size.width * scale)
let colorSpace = CGColorSpaceCreateDeviceGray()
guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: width, space: colorSpace, bitmapInfo: CGImageAlphaInfo.alphaOnly.rawValue) else {
print("Couldn't create CGContext")
return nil
}
context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))
guard let image = context.makeImage() else {
print("Couldn't create image from context")
return nil
}
// Note that self.size corresponds to the non-scaled (retina) dimensions, so is not the same size as the context
let frame = CGRect(origin: .zero, size: self.size)
let mask = CALayer()
mask.contents = image
mask.contentsScale = scale
mask.frame = frame
let layer = CALayer()
layer.backgroundColor = color.cgColor
layer.mask = mask
layer.contentsScale = scale
layer.frame = frame
return layer
}
}

How can I color a UIImage in Swift?

I have an image called arrowWhite. I want to colour this image to black.
func attachDropDownArrow() -> NSMutableAttributedString {
let image:UIImage = UIImage(named: "arrowWhite.png")!
let attachment = NSTextAttachment()
attachment.image = image
attachment.bounds = CGRectMake(2.25, 2, attachment.image!.size.width - 2.25, attachment.image!.size.height - 2.25)
let attachmentString = NSAttributedString(attachment: attachment)
let myString = NSMutableAttributedString(string: NSString(format: "%#", self.privacyOptions[selectedPickerRow]) as String)
myString.appendAttributedString(attachmentString)
return myString
}
I want to get this image in blackColour.
tintColor is not working...
Swift 4 and 5
extension UIImageView {
func setImageColor(color: UIColor) {
let templateImage = self.image?.withRenderingMode(.alwaysTemplate)
self.image = templateImage
self.tintColor = color
}
}
Call like this:
let imageView = UIImageView(image: UIImage(named: "your_image_name"))
imageView.setImageColor(color: UIColor.purple)
Alternativ
For Swift 3, 4 or 5
extension UIImage {
func maskWithColor(color: UIColor) -> UIImage? {
let maskImage = cgImage!
let width = size.width
let height = size.height
let bounds = CGRect(x: 0, y: 0, width: width, height: height)
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
let context = CGContext(data: nil, width: Int(width), height: Int(height), bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)!
context.clip(to: bounds, mask: maskImage)
context.setFillColor(color.cgColor)
context.fill(bounds)
if let cgImage = context.makeImage() {
let coloredImage = UIImage(cgImage: cgImage)
return coloredImage
} else {
return nil
}
}
}
For Swift 2.3
extension UIImage {
func maskWithColor(color: UIColor) -> UIImage? {
let maskImage = self.CGImage
let width = self.size.width
let height = self.size.height
let bounds = CGRectMake(0, 0, width, height)
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedLast.rawValue)
let bitmapContext = CGBitmapContextCreate(nil, Int(width), Int(height), 8, 0, colorSpace, bitmapInfo.rawValue) //needs rawValue of bitmapInfo
CGContextClipToMask(bitmapContext, bounds, maskImage)
CGContextSetFillColorWithColor(bitmapContext, color.CGColor)
CGContextFillRect(bitmapContext, bounds)
//is it nil?
if let cImage = CGBitmapContextCreateImage(bitmapContext) {
let coloredImage = UIImage(CGImage: cImage)
return coloredImage
} else {
return nil
}
}
}
Call like this:
let image = UIImage(named: "your_image_name")
testImage.image = image?.maskWithColor(color: UIColor.blue)
There's a built in method to obtain a UIImage that is automatically rendered in template mode. This uses a view's tintColor to color the image:
let templateImage = originalImage.imageWithRenderingMode(UIImageRenderingModeAlwaysTemplate)
myImageView.image = templateImage
myImageView.tintColor = UIColor.orangeColor()
First you have to change the rendering property of the image to "Template Image" in the .xcassets folder.
You can then just change the tint color property of the instance of your UIImageView like so:
imageView.tintColor = UIColor.whiteColor()
I ended up with this because other answers either lose resolution or work with UIImageView, not UIImage, or contain unnecessary actions:
Swift 3
extension UIImage {
public func mask(with color: UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
let context = UIGraphicsGetCurrentContext()!
let rect = CGRect(origin: CGPoint.zero, size: size)
color.setFill()
self.draw(in: rect)
context.setBlendMode(.sourceIn)
context.fill(rect)
let resultImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return resultImage
}
}
This function uses core graphics to achieve this.
func overlayImage(color: UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.size, false, UIScreen.main.scale)
let context = UIGraphicsGetCurrentContext()
color.setFill()
context!.translateBy(x: 0, y: self.size.height)
context!.scaleBy(x: 1.0, y: -1.0)
context!.setBlendMode(CGBlendMode.colorBurn)
let rect = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)
context!.draw(self.cgImage!, in: rect)
context!.setBlendMode(CGBlendMode.sourceIn)
context!.addRect(rect)
context!.drawPath(using: CGPathDrawingMode.fill)
let coloredImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return coloredImage
}
For swift 4.2 to change UIImage color as you want (solid color)
extension UIImage {
func imageWithColor(color: UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
color.setFill()
let context = UIGraphicsGetCurrentContext()
context?.translateBy(x: 0, y: self.size.height)
context?.scaleBy(x: 1.0, y: -1.0)
context?.setBlendMode(CGBlendMode.normal)
let rect = CGRect(origin: .zero, size: CGSize(width: self.size.width, height: self.size.height))
context?.clip(to: rect, mask: self.cgImage!)
context?.fill(rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
}
How to use
self.imgVw.image = UIImage(named: "testImage")?.imageWithColor(UIColor.red)
I found the solution by H R to be most helpful but adapted it slightly for Swift 3
extension UIImage {
func maskWithColor( color:UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.size, false, UIScreen.main.scale)
let context = UIGraphicsGetCurrentContext()!
color.setFill()
context.translateBy(x: 0, y: self.size.height)
context.scaleBy(x: 1.0, y: -1.0)
let rect = CGRect(x: 0.0, y: 0.0, width: self.size.width, height: self.size.height)
context.draw(self.cgImage!, in: rect)
context.setBlendMode(CGBlendMode.sourceIn)
context.addRect(rect)
context.drawPath(using: CGPathDrawingMode.fill)
let coloredImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return coloredImage!
}
}
This takes into consideration scale and also does not produce a lower res image like some other solutions.
Usage :
image = image.maskWithColor(color: .green )
Create an extension on UIImage:
/// UIImage Extensions
extension UIImage {
func maskWithColor(color: UIColor) -> UIImage {
var maskImage = self.CGImage
let width = self.size.width
let height = self.size.height
let bounds = CGRectMake(0, 0, width, height)
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(CGImageAlphaInfo.PremultipliedLast.rawValue)
let bitmapContext = CGBitmapContextCreate(nil, Int(width), Int(height), 8, 0, colorSpace, bitmapInfo)
CGContextClipToMask(bitmapContext, bounds, maskImage)
CGContextSetFillColorWithColor(bitmapContext, color.CGColor)
CGContextFillRect(bitmapContext, bounds)
let cImage = CGBitmapContextCreateImage(bitmapContext)
let coloredImage = UIImage(CGImage: cImage)
return coloredImage!
}
}
Then you can use it like that:
image.maskWithColor(UIColor.redColor())
For iOS13+ there are withTintColor(__:) and withTintColor(_:renderingMode:) methods.
Example usage:
let newImage = oldImage.withTintColor(.red)
or
let newImage = oldImage.withTintColor(.red, renderingMode: .alwaysTemplate)
Swift 3 extension wrapper from #Nikolai Ruhe answer.
extension UIImageView {
func maskWith(color: UIColor) {
guard let tempImage = image?.withRenderingMode(.alwaysTemplate) else { return }
image = tempImage
tintColor = color
}
}
It can be use for UIButton as well, e.g:
button.imageView?.maskWith(color: .blue)
Add this extension in your code and change image color in storyboard itself.
Swift 4 & 5:
extension UIImageView {
#IBInspectable
var changeColor: UIColor? {
get {
let color = UIColor(cgColor: layer.borderColor!);
return color
}
set {
let templateImage = self.image?.withRenderingMode(.alwaysTemplate)
self.image = templateImage
self.tintColor = newValue
}
}
}
Storyboard Preview:
Swift 4
let image: UIImage? = #imageLiteral(resourceName: "logo-1").withRenderingMode(.alwaysTemplate)
topLogo.image = image
topLogo.tintColor = UIColor.white
Simpleminded way:
yourIcon.image = yourIcon.image?.withRenderingMode(.alwaysTemplate)
yourIcon.tintColor = .someColor
BTW it's more fun on Android!
yourIcon.setColorFilter(getColor(R.color.someColor), PorterDuff.Mode.MULTIPLY);
Add extension Function:
extension UIImageView {
func setImage(named: String, color: UIColor) {
self.image = #imageLiteral(resourceName: named).withRenderingMode(.alwaysTemplate)
self.tintColor = color
}
}
Use like:
anyImageView.setImage(named: "image_name", color: .red)
Post iOS 13 you can use it something like this
arrowWhiteImage.withTintColor(.black, renderingMode: .alwaysTemplate)
Swift 3
21 June 2017
I use CALayer to mask the given image with Alpha Channel
import Foundation
extension UIImage {
func maskWithColor(color: UIColor) -> UIImage? {
let maskLayer = CALayer()
maskLayer.bounds = CGRect(x: 0, y: 0, width: size.width, height: size.height)
maskLayer.backgroundColor = color.cgColor
maskLayer.doMask(by: self)
let maskImage = maskLayer.toImage()
return maskImage
}
}
extension CALayer {
func doMask(by imageMask: UIImage) {
let maskLayer = CAShapeLayer()
maskLayer.bounds = CGRect(x: 0, y: 0, width: imageMask.size.width, height: imageMask.size.height)
bounds = maskLayer.bounds
maskLayer.contents = imageMask.cgImage
maskLayer.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height)
mask = maskLayer
}
func toImage() -> UIImage?
{
UIGraphicsBeginImageContextWithOptions(bounds.size,
isOpaque,
UIScreen.main.scale)
guard let context = UIGraphicsGetCurrentContext() else {
UIGraphicsEndImageContext()
return nil
}
render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
Swift 3 version with scale and Orientation from #kuzdu answer
extension UIImage {
func mask(_ color: UIColor) -> UIImage? {
let maskImage = cgImage!
let width = (cgImage?.width)!
let height = (cgImage?.height)!
let bounds = CGRect(x: 0, y: 0, width: width, height: height)
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
let context = CGContext(data: nil, width: Int(width), height: Int(height), bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)!
context.clip(to: bounds, mask: maskImage)
context.setFillColor(color.cgColor)
context.fill(bounds)
if let cgImage = context.makeImage() {
let coloredImage = UIImage.init(cgImage: cgImage, scale: scale, orientation: imageOrientation)
return coloredImage
} else {
return nil
}
}
}
Swift 4.
Use this extension to create a solid colored image
extension UIImage {
public func coloredImage(color: UIColor) -> UIImage? {
return coloredImage(color: color, size: CGSize(width: 1, height: 1))
}
public func coloredImage(color: UIColor, size: CGSize) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, 0)
color.setFill()
UIRectFill(CGRect(origin: CGPoint(), size: size))
guard let image = UIGraphicsGetImageFromCurrentImageContext() else { return nil }
UIGraphicsEndImageContext()
return image
}
}
Here is swift 3 version of H R's solution.
func overlayImage(color: UIColor) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(self.size, false, UIScreen.main.scale)
let context = UIGraphicsGetCurrentContext()
color.setFill()
context!.translateBy(x: 0, y: self.size.height)
context!.scaleBy(x: 1.0, y: -1.0)
context!.setBlendMode(CGBlendMode.colorBurn)
let rect = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)
context!.draw(self.cgImage!, in: rect)
context!.setBlendMode(CGBlendMode.sourceIn)
context!.addRect(rect)
context!.drawPath(using: CGPathDrawingMode.fill)
let coloredImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return coloredImage
}
Since I found Darko's answer very helpful in colorizing custom pins for mapView annotations, but had to do some conversions for Swift 3, thought I'd share the updated code along with my recommendation for his answer:
extension UIImage {
func maskWithColor(color: UIColor) -> UIImage {
var maskImage = self.CGImage
let width = self.size.width
let height = self.size.height
let bounds = CGRect(x: 0, y: 0, width: width, height: height)
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
let bitmapContext = CGContext(data: nil, width: Int(width), height: Int(height), bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)
bitmapContext!.clip(to: bounds, mask: maskImage!)
bitmapContext!.setFillColor(color.cgColor)
bitmapContext!.fill(bounds)
let cImage = bitmapContext!.makeImage()
let coloredImage = UIImage(CGImage: cImage)
return coloredImage!
}
}
I have modified the extension found here: Github Gist, for Swift 3 which I have tested in the context of an extension for UIImage.
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
}

Resources