Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Efficiently draw CGPath on CATiledLayer

How would I efficiently draw a CGPath on a CATiledLayer? I'm currently checking if the bounding box of the tile intersects the bounding box of the path like this:

-(void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context {
    CGRect boundingBox = CGPathGetPathBoundingBox(drawPath);
    CGRect rect = CGContextGetClipBoundingBox(context);

    if( !CGRectIntersectsRect(boundingBox, rect) )
        return;

    // Draw path...
}

This is not very efficient as the drawLayer:inContext: is called multiple times from multiple threads and results in drawing the path many times.

Is there a better, more efficient way to do this?

like image 466
JWood Avatar asked Feb 03 '12 12:02

JWood


2 Answers

The simplest option is to draw your curve into a large image and then tile the image. But if you're tiling, it probably means the image would be too large, or you would have just drawn the path in the first place, right?

So you probably need to split your path up. The simplest approach is to split it up element by element using CGPathApply. For each element, you can check its bounding box and determine if that element falls in your bounds. If not, just keep track of the last end point. If so, then move to the last end point you saw and add the element to a new path for this tile. When you're done, each tile will draw its own path.

Technically you will "draw" things that go outside your bounds here (such as a line that extends beyond the tile), but this is much cheaper than it sounds. Core Graphics is going to clip single elements very easily. The goal is to avoid calculating elements that are not in your bounding box at all.

Be sure to cache the resulting path. You don't need to calculate the path for every tile; just the ones you're drawing. But avoid recalculating it every time the tile draws. Whenever the data changes, dump your cache. If there are a very large number of tiles, you can also use NSCache to optimize this even better.

like image 143
Rob Napier Avatar answered Nov 09 '22 12:11

Rob Napier


You don't show where the path gets created. If possible, you might try building the path up in the -drawLayer:inContext: method, only creating the portion of it needed for the tile being drawn.

As with all performance problems, you should use Instruments to profile your code and find out exactly where the bottlenecks are. Have you tried that already, and if so, what did you find?

As a side note, is there a reason you're using CGPath instead of UIBezierPath? From Apple's documentation:

For creating paths in iOS, it is recommended that you use UIBezierPath instead of CGPath functions unless you need some of the capabilities that only Core Graphics provides, such as adding ellipses to paths. For more on creating and rendering paths in UIKit, see “Drawing Shapes Using Bezier Paths.”

like image 33
Andrew Madsen Avatar answered Nov 09 '22 10:11

Andrew Madsen