7

Given:

  1. a CGContextRef (ctx) with frame {0,0,100,100}
  2. and a rect (r), with frame {25,25,50,50}

It's easy to clip the context to that rect:

CGContextClipToRect(ctx, r);

to mask out the red area below (red == mask):

enter image description here

But I want to invert this clipping rect to convert it into a clipping mask. The desired outcome is to mask the red portion below (red == mask):

enter image description here

I want to do this programmatically at runtime.

I do not want to manually prepare a bitmap image to ship statically with my app.

Given ctx and r, how can this be done at runtime most easily/straightforwardly?

Todd Ditchendorf
  • 11,217
  • 14
  • 69
  • 123

4 Answers4

18

Read about fill rules in the “Filling a Path” section of the Quartz 2D Programming Guide.

In your case, the easiest thing to do is use the even-odd fill rule. Create a path consisting of your small rectangle, and a much larger rectangle:

CGContextBeginPath(ctx);
CGContextAddRect(ctx, CGRectMake(25,25,50,50));
CGContextAddRect(ctx, CGRectInfinite);

Then, intersect this path into the clipping path using the even-odd fill rule:

CGContextEOClip(ctx);
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
3

You could clip the context with CGContextClipToRects() by passing rects that make up the red frame you've wanted.

lbrndnr
  • 3,361
  • 2
  • 24
  • 34
  • Yeah, I'm afraid this may be the simplest solution. But I was hoping there was something more elegant/simple that I was overlooking. For the rectangle case, your suggestion works fairly easily. But given a path, your solution wouldn't work. That made me think there must be a better solution available in Quartz2D somewhere… – Todd Ditchendorf Jan 03 '13 at 19:36
  • Then you could use CGContextClipToMask() and draw your border/frame in an UIImage. – lbrndnr Jan 03 '13 at 19:39
1

Can you just do all your painting as normal, and then do:

CGContextClearRect(ctx, r);

after everything has been done?

Dave DeLong
  • 242,470
  • 58
  • 448
  • 498
  • It seems like that should work, but `CGContextClearRect()` always just does a "fill with black" in the given rect (which I've never understood). So, no I don't think this works. – Todd Ditchendorf Jan 03 '13 at 19:33
  • 2
    `CGContextClearRect` sets all components of each pixel to zero. If your context has an alpha channel, each pixel becomes clear. Otherwise, each pixel becomes black. – rob mayoff Jan 03 '13 at 19:45
0

Here is a helpful extension for implementing rob's answer

extension UIBezierPath {
    func addClipInverse() {
        let paths = UIBezierPath()
        paths.append(self)
        paths.append(.init(rect: .infinite))
        paths.usesEvenOddFillRule = true
        paths.addClip()
    }
}
garafajon
  • 1,278
  • 13
  • 15