Lulli
Lulli

Reputation: 29

Use UIBezierPath to mask a background image

I want to draw a semicircular line that acts as a mask to a background image.

I have this image: Backgound image
and I want to obtain this: Mask

I have tried the following code, inside the draw method of a custom UIView, but I don't know how to mask only the stroked path, not the whole arc. What am I missing?

let context = UIGraphicsGetCurrentContext()! 
saveGState()

context.setLineWidth(4)

path = UIBezierPath()
path.addArc(withCenter: CGPoint(x: self.bounds.midX, y: self.bounds.height), radius: self.bounds.height-10, startAngle: -180 * CGFloat.pi/180, endAngle: 0 * CGFloat.pi/180, clockwise: true)

context.addPath(path.cgPath)
context.replacePathWithStrokedPath()
context.clip()

let gradient = CGGradient(colorsSpace: nil, colors: [UIColor.clear.cgColor, UIColor.clear.cgColor] as CFArray, locations: nil)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0, y: 0), end: CGPoint(x: 200, y: 0), options: [])

context.restoreGState()

context.saveGState()

let myLayer = CALayer()
let myImage = UIImage(named: "color")?.cgImage
myLayer.frame = self.bounds
myLayer.contents = myImage

let maskLayer = CAShapeLayer()
maskLayer.path = context.path

myLayer.mask = maskLayer

self.layer.addSublayer(myLayer)

context.restoreGState()

Upvotes: 1

Views: 995

Answers (1)

jrturton
jrturton

Reputation: 119242

There's a lot going on in that method, quite a lot of which shouldn't be in a drawRect - it's best to avoid drawRect if you can, as it shifts graphics work onto the CPU.

For simplicity I have shown how to mask an image using a context and a path - the problem in the code above is that the path is incredibly narrow and was probably outside the visible part of the rainbow image anyway, so I guess you were seeing nothing much?

It's helpful when writing code like this to do it in a playground, so you can see instant results and tweak what you're doing very easily. Stroking the path is also handy to see if its where you expect when nothing looks right. Adding your original rainbow into a playground, I did this:

import UIKit

let image = UIImage(named: "I6BbE.jpg")!

let renderer = UIGraphicsImageRenderer(size: image.size)
let maskedImage = renderer.image {
    context in
    let cgContext = context.cgContext
    let bounds = context.format.bounds
    cgContext.setLineWidth(50)

    let path = UIBezierPath()
    path.addArc(
        withCenter: CGPoint(x: bounds.midX, y: bounds.height), 
        radius: bounds.height-100, 
        startAngle: .pi, 
        endAngle: 0, 
        clockwise: true)

    cgContext.addPath(path.cgPath)
    cgContext.replacePathWithStrokedPath()
    cgContext.clip()
    image.draw(in: bounds)
}

maskedImage

Which gives me:

enter image description here

Upvotes: 2

Related Questions