简体   繁体   中英

How to cache CGContextRef

Unsatisfied with my previous results, I have been asked to create a freehand drawing view that will not blur when zoomed. The only way I can imagine this is possible is to use a CATiledLayer because otherwise it is just too slow when drawing a line when zoomed. Currently, I have it set up so that it will redraw every line every time, but I want to know if I can cache the results of the previous lines ( not as pixels because they need to scale well) in a context or something.

I thought about CGBitmapContext, but would that mean I would need to tear down and set up a new context after every zoom? The problem is that on a retina display, the line drawing is too slow (on iPad 2 it is so-so), especially when drawing while zoomed. There is an app in the App Store called GoodNotes which beautifully demonstrates that this is possible, and it is possible to do it smoothly, but I can't understand how they are doing it. Here is my code so far (result of most of today):

- (void)drawRect:(CGRect)rect
{   
    CGContextRef c = UIGraphicsGetCurrentContext();

    CGContextSetLineWidth(c, mLineWidth);
    CGContextSetAllowsAntialiasing(c, true);
    CGContextSetShouldAntialias(c, true);
    CGContextSetLineCap(c, kCGLineCapRound);
    CGContextSetLineJoin(c, kCGLineJoinRound);

    //Protect the local variables against the multithreaded nature of CATiledLayer
    [mLock lock]; 
    NSArray *pathsCopy = [mStrokes copy];
    for(UIBezierPath *path in pathsCopy) //**Would like to cache these**
    {
        CGContextAddPath(c, path.CGPath);
        CGContextStrokePath(c);
    }
    if(mCurPath)
    {
        CGContextAddPath(c, mCurPath.CGPath);
        CGContextStrokePath(c);
    }

    CGRect pathBounds = mCurPath.bounds;
    if(pathBounds.size.width > 32 || pathBounds.size.height > 32)
    {
        [mStrokes addObject:mCurPath];
        mCurPath = [[UIBezierPath alloc] init];
    }
   [mLock unlock];
}

Profiling shows the hottest function by far is GCSFillDRAM8by1

First, as the path stroking is the most expensive operation, you shouldn't lock around it as this prevent you from drawing tiles concurrently on different cores.

Secondly, I think you could avoid to call CGContextStrokePath several times by adding all the paths in the context and stroking them altogether.

[mLock lock]; 
for ( UIBezierPath *path in mStrokes ) {
    CGContextAddPath(c, path.CGPath);
}
if ( mCurPath ) {
    CGContextAddPath(c, mCurPath.CGPath);
}
CGRect pathBounds = mCurPath.bounds;
if ( pathBounds.size.width > 32 || pathBounds.size.height > 32 )
{
    [mStrokes addObject:mCurPath];
    mCurPath = [[UIBezierPath alloc] init];
}
[mLock unlock];
CGContextStrokePath(c);

The CGContextRef is just a canvas in which the drawing operations occur. You cannot cache it but you may the create a CGImageRef with a flattened bitmap image of your paths and reuse that image. This won't help with zooming (as you'd need to recreate the image when the level of detail changes) but can be useful to improve the performance when the user is drawing a really long path.

There is a really interesting WWDC 2012 Session Video on that subject: Optimizing 2D Graphics and Animation Performance .

The bottleneck was actually the way I was using CATiledLayer. I guess it is too much to update with freehand info. I set it up with levels of detail as I saw in the docs and tutorials online, but in the end I didn't need that much. I just hooked up the scrollview delegate, cleared the contents when it was done zooming and changed the contentScale of the layer to match the scroll view. The result was beautiful (it disappears and fades back in, but that can't be helped).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM