5

I'm attempting to create a colored pattern using CGPattern in Swift. Apple provides a nice Objective-C example in the Quartz 2D Programming Guide in their section on Painting Colored Patterns. But getting all of that syntax converted from Objective-C is not straight forward. Plus I'd like to make use of the info parameter in the drawing callback and there is no example of doing that.

Here's my first attempt:

class SomeShape {
    func createPattern() -> CGPattern? {
        let bounds = CGRect(x: 0, y: 0, width: someWidth, height: someHeight)
        let matrix = CGAffineTransform.identity
        var callbacks = CGPatternCallbacks(version: 0, drawPattern: nil, releaseInfo: nil)

        let res = CGPattern(info: nil, bounds: bounds, matrix: matrix, xStep: bounds.width, yStep: bounds.height, tiling: .noDistortion, isColored: true, callbacks: &callbacks)

        return res
    }
}

Clearly this needs a proper value for the drawPattern parameter to CGPatternCallbacks and I need to pass self as the info parameter to the CGPattern initializer.

What is the proper syntax to complete this?

Hamish
  • 78,605
  • 19
  • 187
  • 280
rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • Sharing an alternate approach - not proposing as an answer because it doesn't address how to work with CGPattern in Swift, but I decided to bypass CGPattern and just use NSImage facilities to do brush-like drawing and region filling, viz.: "Brush-like drawing in Swift without CGPattern" https://blog.mathaesthetics.com/2019/11/dev-notebook-brush-like-drawing-in.html – Corbell Dec 01 '19 at 02:43

2 Answers2

9

As you say in your answer, CGPatternDrawPatternCallback is defined as:

typealias CGPatternDrawPatternCallback =
                               @convention(c) (UnsafeMutableRawPointer?, CGContext) -> Void

The @convention(c) attribute (which only appears to show up in the generated header) means that the function value used must be compatible with C, and therefore cannot capture any context (because C function values are nothing more than raw pointers to a function, and don't store an additional context object).

So if you want to have context available in the function, you need to pass your own UnsafeMutableRawPointer? to the info: parameter of CGPattern's initialiser. This will then be passed as the first parameter of the given draw pattern function upon being called.

In order to pass self to this parameter, you can use Unmanaged. This allows you to convert between references and opaque pointers and, unlike unsafeBitCast, also lets you control the memory management of the reference when doing so.

Given that we have no guarantee that the caller of createPattern() will keep self retained, we cannot just pass it off to the info: parameter without retaining it ourselves. If it were passed without retaining (for example with unsafeBitCast), and was then deallocated before drawing the pattern – you would get undefined behaviour when attempting to use a dangling pointer in the drawing callback.

With Unmanaged:

  • You can pass a reference as a +1 retained opaque pointer with passRetained(_:).toOpaque()

  • You can get back a reference from this pointer with fromOpaque(_:).takeUnretainedValue() (and the instance will stay retained)

  • You can then consume the +1 retain with fromOpaque(_:).release(). You'll want to do this when the CGPattern is freed.

For example:

class SomeShape {
    // the bounds of the shape to draw
    let bounds = CGRect(x: 0, y: 0, width: 40, height: 40)

    func createPattern() -> CGPattern? {

        var callbacks = CGPatternCallbacks(version: 0, drawPattern: { info, ctx in

            // cast the opaque pointer back to a SomeShape reference.
            let shape = Unmanaged<SomeShape>.fromOpaque(info!).takeUnretainedValue()

            // The code to draw a single tile of the pattern into "ctx"...
            // (in this case, two vertical strips)
            ctx.saveGState()
            ctx.setFillColor(UIColor.red.cgColor)
            ctx.fill(CGRect(x: 0, y: 0,
                            width: shape.bounds.width / 2, height: shape.bounds.height))

            ctx.setFillColor(UIColor.blue.cgColor)
            ctx.fill(CGRect(x: 20, y: 0,
                            width: shape.bounds.width / 2, height: shape.bounds.height))
            ctx.restoreGState()

        }, releaseInfo: { info in
            // when the CGPattern is freed, release the info reference,
            // consuming the +1 retain when we originally passed it to the CGPattern.
            Unmanaged<SomeShape>.fromOpaque(info!).release()
        })

        // retain self before passing it off to the info: parameter as an opaque pointer.
        let unsafeSelf = Unmanaged.passRetained(self).toOpaque()

        return CGPattern(info: unsafeSelf, bounds: bounds, matrix: .identity,
                         xStep: bounds.width, yStep: bounds.height,
                         tiling: .noDistortion, isColored: true, callbacks: &callbacks)
    }
}

Alternatively, a better solution if you want value semantics for SomeShape, you could make it a struct. Then when creating the pattern, you can just wrap it up in a Context heap-allocated box before passing it off to the info: parameter:

struct SomeShape {

    // the bounds of the shape to draw
    let bounds = CGRect(x: 0, y: 0, width: 40, height: 40)

    func createPattern() -> CGPattern? {

        final class Context {
            let shape: SomeShape
            init(_ shape: SomeShape) { self.shape = shape }
        }

        var callbacks = CGPatternCallbacks(version: 0, drawPattern: { info, ctx in

            // cast the opaque pointer back to a Context reference,
            // and get the wrapped shape instance.
            let shape = Unmanaged<Context>.fromOpaque(info!).takeUnretainedValue().shape

            // ...

        }, releaseInfo: { info in
            // when the CGPattern is freed, release the info reference,
            // consuming the +1 retain when we originally passed it to the CGPattern.
            Unmanaged<Context>.fromOpaque(info!).release()
        })

        // wrap self in our Context box before passing it off to the info: parameter as a
        // +1 retained opaque pointer.
        let unsafeSelf = Unmanaged.passRetained(Context(self)).toOpaque()

        return CGPattern(info: unsafeSelf, bounds: bounds, matrix: .identity,
                         xStep: bounds.width, yStep: bounds.height,
                         tiling: .noDistortion, isColored: true, callbacks: &callbacks)
    }
}

This now also takes care of any retain cycle concerns.

Hamish
  • 78,605
  • 19
  • 187
  • 280
  • Thanks for the info. This is the first I've dealt with "pointers" in Swift. I came across the use of `unsafeBitCast` while searching around. It works in my case but it is good to know there is a better way. In my real code, I'm actually passing an instance of `CALayer` as the `info` parameter. This is a local variable created inside the `createPattern` method. It works just fine in my code using `unsafeBitCast`. What would be the safer way to wrap a local variable of type `CALayer`? – rmaddy May 27 '17 at 14:23
  • @rmaddy I assume you're adding the `CALayer` instance local variable to a layer hierarchy which is then keeping it retained (or some other thing is retaining it)? That's the only way I can think of why it would be working with `unsafeBitCast`. Unless there are retain cycle concerns (i.e that `CALayer` instance keeps a reference to the `CGPattern`), I would still advise retaining the `CALayer` instance with `Unmanaged` (exactly like in the first example) before passing it off to the `info:` parameter – just be defensive against it being deallocated before the pattern is drawn. – Hamish May 27 '17 at 14:38
  • If there are retain cycle concerns, you could always make a `Weak` class wrapper which holds a weak reference to your given layer, and then pass the wrapper retained. However of course if the given `CALayer` is always the one and only owner of the given `CGPattern`, then you shouldn't do any extra retaining (effectively making it `unowned`). – Hamish May 27 '17 at 14:39
  • Although even if you aren't doing the extra retain, I would still advise using the `Unmanaged` API over `unsafeBitCast` in this case, as it comes with stronger static guarantees (e.g ensuring you're working with references and making you unwrap optionals) – to convert without any retains/releases would just be `passUnretained(_:).toOpaque()` & `fromOpaque(_:).takeUnretainedValue()`. [Martin's convenience functions here](https://stackoverflow.com/a/33310021/2976878) can come in handy :) – Hamish May 27 '17 at 14:39
  • Actually, there is no other reference to the `CALayer` referenced by the local variable. It is not added to any layer hierarchy and as far as I can tell, there is no retain cycle on the layer. But to be safe I switched over to using `Unmanaged` and it is still working. Thanks. – rmaddy May 27 '17 at 14:54
  • @rmaddy Huh, that is interesting – just tried it and getting the same results. It *looks like* the given `CALayer` instance gets added to a system auto-release pool which drains at the end of the current run-loop. If you subclass `CALayer` and add a `deinit`, you will see it get deallocated after the drawing (but this is unrelated to the lifetime of the `CGPattern`). So it's only really working because of chance – adding the retain will prevent any nasty surprises later down the line. – Hamish May 27 '17 at 15:16
  • @rmaddy It also looks like `CGPattern` caches the drawn pattern, therefore meaning that *technically* you shouldn't need the argument passed to the `info:` parameter to persist after the first draw. But really that's a pure implementation detail (it doesn't appear to be documented) and shouldn't be relied upon. – Hamish May 27 '17 at 15:26
5

Let's start by looking at CGPatternDrawPatternCallback. It is defined as:

typealias CGPatternDrawPatternCallback = (UnsafeMutableRawPointer?, CGContext) -> Void

So it is a closure that takes two parameters - the info and the drawing context.

With that info you can create the CGPatternCallback as follows:

var callbacks = CGPatternCallbacks(version: 0, drawPattern: { (info, ctx) in
    // Drawing code here
}, releaseInfo: { (info) in {
    // Cleanup code here
})

But there is something important to note here. The body of these closures can't capture anything outside of the block. If you attempt to do so you will get the following error:

A C function point cannot be formed from a closure that captures context

And this is why the info parameter needs to be used. You can pass self or some other object as the info parameter when creating the pattern and use that inside the drawing callback. But that's not a simple task because you can't simply pass self as the info parameter. You need to make it into the required UnsafeMutableRawPointer and then convert it back from the pointer inside the drawing callback.

Here's the complete code with all of that setup:

class SomeShape {
    func createPattern() -> CGPattern? {
        let bounds = CGRect(x: 0, y: 0, width: someWidth, height: someHeight) // The size of each tile in the pattern
        let matrix = CGAffineTransform.identity // adjust as needed
        var callbacks = CGPatternCallbacks(version: 0, drawPattern: { (info, ctx) in
            let shape = unsafeBitCast(info, to: SomeShape.self)

            // The needed drawing code to draw one tile of the pattern into "ctx"
        }, releaseInfo: { (info) in 
            // Any cleanup if needed
        })

        let unsafeSelf = unsafeBitCast(self, to: UnsafeMutableRawPointer.self)

        let res = CGPattern(info: unsafeSelf, bounds: bounds, matrix: matrix, xStep: bounds.width, yStep: bounds.height, tiling: .noDistortion, isColored: true, callbacks: &callbacks)

        return res
    }
}

And to make use of the CGPattern, you can do something like this:

func draw(_ ctx: CGContext) {
    // Any other needed setup

    let path = CGPath(....) // some path

    // Code to fill a path with the pattern
    ctx.saveGState()
    ctx.addPath(path) // The path to fill

    // Setup the pattern color space for the colored pattern
    if let cs = CGColorSpace(patternBaseSpace: nil) {
        ctx.setFillColorSpace(cs)
    }

    // Create and apply the pattern and its opacity
    if let fillPattern = someShapeInstance.createPattern() {
        var fillOpacity = CGFloat(1.0)
        ctx.setFillPattern(fillPattern, colorComponents: &strokeOpacity)
    }

    ctx.fillPath(using: theDesiredFillRule)
    ctx.restoreGState()

    // Any other drawing
}

When using a colored pattern (versus a stencil, non-colored pattern) you must set the fill color space before setting the fill pattern.

You can also use a pattern to stroke a path. Just use setStrokeColorSpace and setStrokePattern.

Hamish
  • 78,605
  • 19
  • 187
  • 280
rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • maddy, do you have an idea, why Apple declared CGPatternDrawPatternCallback first parameter as a mutable version of pointer? – user3441734 May 27 '17 at 02:28
  • @user3441734 In Objective-C `info` is a plain old `void *`. I really have no prior experience to `Unsafe[Mutable][Raw]Pointer` in Swift so I can't say for sure. But my guess is that by making it `UnsafeMutableRawPointer` instead of `UnsafeRawPointer`, it allows you more options on what you use the `info` parameter for and how you use it. I'd suggest posting a separate question on this topic if there isn't one already. – rmaddy May 27 '17 at 02:44
  • Worth noting that there are two main opportunities for undefined behaviour here. First if `self` is deallocated before the pattern is drawn, you'll get undefined behaviour upon attempting to use a dangling pointer in the drawing callback. Secondly (less importantly, as you have control over it) if you pass `nil` to the `info:` parameter, you'll get undefined behaviour upon attempting to use `nil` as a non-optional reference. Using `Unmanaged`, such as shown [in my answer](https://stackoverflow.com/a/44215903/2976878), to convert between reference and opaque pointer prevents both of these. – Hamish May 27 '17 at 13:34
  • (and a third opportunity for UB if a non-pointer-sized instance was passed into the `unsafeBitCast`, such as would likely be the case if `SomeShape` was accidently changed to a `struct` in a refactor – `Unmanaged` statically enforces that the thing you're converting is a reference). Although that being said, #2 and #3 of these points are really just nitpicks :) – Hamish May 27 '17 at 13:46
  • @Hamish 1) In my case it is guaranteed that `self` can't get deallocated before the pattern is drawn. 2) There is no reason I would pass `nil` to the `info` parameter and then attempt to use it in the `drawPattern` callback. 3) This is all part of a class hierarchy so no chance of it getting refactored into a struct. These are all good things to point out though. Thanks. – rmaddy May 27 '17 at 14:07