0

friends, I am working with UIBezierPaths, for free hand drawing , and everything works fine, for this I am storing my paths in a path array, everything works fine, while rendering, I am looping the whole array and rendering the paths,but soon as the array count increases, I see a lag while drawing, below is my drawRect code. please help me out in finding where I am going wrong

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{

    UITouch *mytouch=[[touches allObjects] objectAtIndex:0];

    m_previousPoint2 = m_previousPoint1;
    m_previousPoint1 = [mytouch previousLocationInView:self];
    m_currentPoint = [mytouch locationInView:self];

    CGPoint mid1    = midPoint(m_previousPoint1, m_previousPoint2); 
    CGPoint mid2    = midPoint(m_currentPoint, m_previousPoint1);  

    testpath = CGPathCreateMutable();
    CGPathMoveToPoint(testpath, NULL, mid1.x, mid1.y);

    CGPathAddQuadCurveToPoint(testpath, NULL, m_previousPoint1.x, m_previousPoint1.y, mid2.x, mid2.y);      


    CGRect bounds = CGPathGetBoundingBox(testpath);   

    CGPathRelease(testpath);


    CGRect drawBox = bounds;

    //Pad our values so the bounding box respects our line width
    drawBox.origin.x        -= self.lineWidth * 2;
    drawBox.origin.y        -= self.lineWidth * 2;
    drawBox.size.width      += self.lineWidth * 4;
    drawBox.size.height     += self.lineWidth * 4;


    CGContextRef context = UIGraphicsGetCurrentContext();
    context = CGLayerGetContext(myLayerRef);


    [self.layer renderInContext:UIGraphicsGetCurrentContext()];      

    [self setNeedsDisplayInRect:drawBox];    

}


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

    if(myLayerRef == nil)
    {            
        myLayerRef = CGLayerCreateWithContext(context, self.bounds.size, NULL);
    }

    [self.layer renderInContext:context];   


    CGPoint mid1 = midPoint(m_previousPoint1, m_previousPoint2); 
    CGPoint mid2 = midPoint(m_currentPoint, m_previousPoint1);


    CGContextMoveToPoint(context, mid1.x, mid1.y);
    CGContextAddQuadCurveToPoint(context, m_previousPoint1.x, m_previousPoint1.y, mid2.x, mid2.y); 
    CGContextSetLineCap(context, kCGLineCapRound);
    CGContextSetLineWidth(context, self.lineWidth);
    CGContextSetStrokeColorWithColor(context, self.lineColor.CGColor);

    CGContextSetFlatness(context, 0.1);

    CGContextSetAllowsAntialiasing(context, true);

    CGContextStrokePath(context);

    [super drawRect:rect];  

 }

Code updated according to the below discussion by @borrrden

Regards Ranjit

Ranjit
  • 4,576
  • 11
  • 62
  • 121
  • Check my comments again. The CGLayer should be a member variable, not a local variable. It should only be created once (i.e. if mLayer == NULL). Also, you need to get the context inside touches via CGLayerGetContext(). – borrrden Jul 04 '12 at 11:32
  • hey @borrrden, I have updated the code for calculating the rect in touches moved, please have a look. – Ranjit Jul 05 '12 at 07:20
  • The rectangle calculations look fine (in fact that comment is straight from the git hub project in my linked answer right?). However, this is getting way off the original question. You need to start a new one for your new problem. – borrrden Jul 05 '12 at 08:00
  • ok thanks, but did you notice one thing, I have used "curImage", instead of CgLayer, is that correct? – Ranjit Jul 05 '12 at 09:06
  • It will work, but it won't be as efficient. If you don't notice, though, then it is fine (and you don't need CGLayer anymore) – borrrden Jul 05 '12 at 09:18
  • borrrden can you please join the chat? need to discuss few points – Ranjit Jul 05 '12 at 09:24
  • Hello @borrrden, I have updated the code for CGLayer, according to your suggestion, please verify it and let me know whether it is correct. thanks, waiting for your reply. – Ranjit Jul 05 '12 at 10:38
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/13458/discussion-between-ranjit-and-borrrden) – Ranjit Jul 05 '12 at 12:37

1 Answers1

2

There is an excellent session in the WWDC 2012 sessions about this exact problem. I think it is called iOS performance: Graphics or something similar. You should definitely watch it.

The summary is, DON'T store it in a path. There is no need to keep this path information around. Store a separate context (CGLayer is best) and draw into that (just like you add points to the path, add points to the context intead). Then in your drawRect, simply draw that layer into the current graphics context. Also be sure to only call setNeedsDisplay on the rectangle that changed, or you will most likely run into problems on a high resolution device.

borrrden
  • 33,256
  • 8
  • 74
  • 109
  • Hey borrrden , thanks for your reply,as I am new to the graphics part of iOS, I have few doubts, 1) how to use CGLayer and drawing into it. 2) how to draw the layer into current graphics context. As I have stored all the paths into array, can I take those paths from array and store into CGLayer? – Ranjit Jul 04 '12 at 06:27
  • Don't store your paths in an array! That is the main point. The more paths you have, obviously the longer it will take to draw them all. Well, I don't think I'll be able to tell you everything about CGLayers in just a small comment, but they are the same idea as a CGContext except that they are cached on the graphics hardware whenever possible. You draw to them the same way you draw to a graphics context. Actually, I was asking a similar question about a month ago -> http://stackoverflow.com/questions/10797037/smoother-freehand-drawing-experience-ios – borrrden Jul 04 '12 at 06:41
  • The main Core Graphics functions for you to study to accomplish this are -> `CGLayerCreateWithContext()` (Do this inside your drawRect, so you can get the UIView's context and model your CGLayer after it, but only the first time), `CGLayerGetContext()` (draw into this, doesn't have to be on the main thread), `CGContextMoveToPoint()` / `CGContextAddLineToPoint()` (Drawing commands like UIBezierPath), `CGContextDrawLayerInRect()` (Do this in drawRect), `UIGraphicsGetCurrentContext()` (Use this in drawRect to get your view's context to render to). – borrrden Jul 04 '12 at 06:45
  • You will find that the CGContext commands are identical to the UIBezierPath methods (and even take the same constants). – borrrden Jul 04 '12 at 06:46
  • Thanks once again, for drawing, I have used a CGMutablePath and then I have made all the computations of smoothing the path and then assigned that path to my bezeirpath.(bezeirPath.CgPath = myCgPath). So whether I should draw directly with cgpath, instead of BezeirPath?. And the reason for using array was, it will help me in undo and redo functionality. – Ranjit Jul 04 '12 at 06:49
  • I also need to think about undo and redo. I think a reasonable way to do it would be to store the paths separately (along with a flag indicating whether or not they were erasures) and then with undo, just stroke the path again but with the opposite effect. It is impossible to simply render all those lines again and again. You need to render them immediately. Keep the mutable path, but also in addition to adding the lines to the path, add them to the CGLayer's context and render them right then and there. This will keep render times down because you only need to render a short line. – borrrden Jul 04 '12 at 07:08
  • Hey I have added my both touch events, can you please go through it and tell me how should I do it. I mean using CGLayers.? I am not getting it – Ranjit Jul 04 '12 at 07:17
  • The CGLayer will be another instance variable. In the beginning of drawRect, create it (if it is NULL), and then render it into UIGraphicsGetCurrentContext() at the end of drawRect. Everywhere else, whenever you add lines / curves / whatever to your path, add them to the CGLayer's context as well (CGContextAddQuadCurve, etc) then call setNeedsDisplay on the rectangle containing your touch points. This is all I'm going to say for now. I also had to learn this by studying the docs and by the comments made on the question I linked to above. – borrrden Jul 04 '12 at 07:26
  • Give me sometime, I will come up with my code, I request you to just verify it. thanks – Ranjit Jul 04 '12 at 07:29
  • Hey whether I should keep my previous code of drawRectMethod? – Ranjit Jul 04 '12 at 07:33
  • Hey borrrden, I have updated my code, with your suggestions, I request you to please verify it. thanks – Ranjit Jul 04 '12 at 10:22
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/13405/discussion-between-ranjit-and-borrrden) – Ranjit Jul 04 '12 at 11:26
  • @borrden:can you please join the chat. – Ranjit Jul 05 '12 at 07:14