2

I am doing a drawing app and I am trying to smooth the line which I draw with finger.For this purpose I am using "quadCurveToPoint" function. But, I am not getting it right.

Below is my code:

- (void) drawRect:(CGRect)rect
{   
    [path stroke];
}

- (id) initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame])
        self.multipleTouchEnabled = NO;

    path = [UIBezierPath bezierPath];
    path.lineWidth = IS_IPAD? 2.0f : 1.0f;

    return self;
}



- (void) touchesBegan:(NSSet *) touches withEvent:(UIEvent *) event
{   
    UITouch *touch = [touches anyObject];

    m_previousPoint1 = [touch locationInView:self];
    m_previousPoint2 = [touch locationInView:self];
    m_currentPoint  = [touch locationInView:self];
}

//Find the midpoint
CGPoint midPoint(CGPoint p1, CGPoint p2)
{
    return CGPointMake((p1.x + p2.x) * 0.5, (p1.y + p2.y) * 0.5);
}


- (void) touchesMoved:(NSSet *) touches withEvent:(UIEvent *) event
{
    UITouch *touch = [touches anyObject];

    m_previousPoint2 = m_previousPoint1;
    m_previousPoint1 = m_currentPoint;

    m_currentPoint = [touch locationInView:self];

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



    [path setFlatness:1.0f];
    [path setLineCapStyle:kCGLineCapRound];
    [path setLineJoinStyle:kCGLineJoinRound];
    [path moveToPoint:m_previousPoint1];
    [path addLineToPoint:mid1];

    [path addQuadCurveToPoint:mid2 controlPoint:m_currentPoint];
    [self setNeedsDisplay];
}

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

}

When I draw with my finger, I am geting a path like this below:

enter image description here

I am not understanding what I am missing. So I need help in this regard.

EDIT: Image posted w.r.t. to the below answer.

enter image description here

Thanks Ranjit

Unheilig
  • 16,196
  • 193
  • 68
  • 98
Ranjit
  • 4,576
  • 11
  • 62
  • 121

1 Answers1

5

Your code looks good. But to fix the problem you mentioned, change the current code to the following:

Change:

[path moveToPoint:m_previousPoint1];
[path addLineToPoint:mid1];

To:

[path moveToPoint:mid1];
[path addLineToPoint:m_previousPoint1];

And Change this:

[path addQuadCurveToPoint:mid2 controlPoint:m_currentPoint];

To:

[path addQuadCurveToPoint:m_currentPoint controlPoint:mid2];

Tested.


Addendum (WWDC Algorithm):

Background:

The idea is this according to WWDC:

1.) Instead of using the current point, we use the mid points as the start point and end point.

2.) As a result, we use the actual touch points as control points.

Analysis / Correction for your code:

So here is a simplified version of code I made making use the idea that was introduced in WWDC.

You got the idea. Almost. Given the above, we need to make changes to your code in touchesMoved to the following:

1.)

If we are using the mid point as the ToPoint value, we need to take care the first case when there is only one current point, because with only one current point, we cannot derive a mid point from it - we need 2 points.

So, we would need to "read" one point past the current point initially to calculate the mid point. The following does that:

UITouch *touch = [touches anyObject];

m_previousPoint1 = m_currentPoint;
m_currentPoint = [touch locationInView:self];
mid1 = midPoint(m_currentPoint, m_previousPoint1);

if(counter == 1)
{
    [path moveToPoint:m_currentPoint];
    [path addLineToPoint:mid1];
    [self setNeedsDisplay];
}

The variable counter is initially set to 0. So, nothing gets drawn until the second pass when counter is 1. And when it is, we will have 2 points to calculate the mid point.

Then here comes the rest of the touches:

2.)

Once the first case is taken care of, we move forward to the rest of the curve and derive appropriately the points with which we connect the segments:

else if(counter > 1)
{
    [path addQuadCurveToPoint:mid1 controlPoint:m_previousPoint1];
    [self setNeedsDisplay];
}
counter++;

Here is the else if right after the first if above. We enter here when only the first case is handled, for that I use a simple counter and increment it every time touchesMoved gets called.

What happens here is that we are connecting from the previous mid point to mid1 using the previous point as control point. So, what about the current point? We are using it until the next pass.

3.) And finally, we take care the last segment of the curve in touchesEnded:

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [path addLineToPoint:[[touches anyObject] locationInView:self]];
    [self setNeedsDisplay];
}

This simply draws a line from your mid point to the last point.

And finally in touchesBegan, I set counter = 0;, so the next curve will start the above process again.

I tested the above using both simulator and device, and here is a screen shot:

screenshot

And here is the complete source:

- (void) touchesBegan:(NSSet *) touches withEvent:(UIEvent *) event
{
    UITouch *touch = [touches anyObject];
    counter = 0;
    m_previousPoint1 = [touch locationInView:self];
    m_currentPoint  = [touch locationInView:self];
}

//Find the midpoint
CGPoint midPoint(CGPoint p1, CGPoint p2)
{
    return CGPointMake((p1.x + p2.x) * 0.5, (p1.y + p2.y) * 0.5);
}


- (void) touchesMoved:(NSSet *) touches withEvent:(UIEvent *) event
{
    UITouch *touch = [touches anyObject];

    m_previousPoint1 = m_currentPoint;
    m_currentPoint = [touch locationInView:self];

    mid1 = midPoint(m_currentPoint, m_previousPoint1);

    [path setFlatness:1.0f];
    [path setLineCapStyle:kCGLineCapRound];
    [path setLineJoinStyle:kCGLineJoinRound];

    if(counter == 1)
    {
        [path moveToPoint:m_currentPoint];
        [path addLineToPoint:mid1];
        [self setNeedsDisplay];
    }
    else if(counter > 1)
    {
        [path addQuadCurveToPoint:mid1 controlPoint:m_previousPoint1];
        [self setNeedsDisplay];
    }
    counter++;
}

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [path addLineToPoint:[[touches anyObject] locationInView:self]];
    [self setNeedsDisplay];
}
Unheilig
  • 16,196
  • 193
  • 68
  • 98
  • Hey, thanks for your answer, I am travelling so cannot check right now, but I will check asap when I am back. I want to know how did you figure out this problem, whether it was from trial& error or correct understanding of the concepts? If it is correct understanding of the concepts, what secret tip, you want to give me so that I can be better at understanding the concepts. Thanks – Ranjit Jan 18 '14 at 13:47
  • Hello, thanks , I tried your code, it works, but I dont get smooth curves. Can you suggest something on this. – Ranjit Jan 20 '14 at 06:35
  • Check image above how I am getting the curve. – Ranjit Jan 20 '14 at 06:50
  • Hey also check WWDC 2012 video session 233 - building Advanced Gesture Recognizers from time 38:31, they have discussed about curve smoothing, Here i am not understanding how they are creating the "LocationSamples" array – Ranjit Jan 20 '14 at 13:48
  • Hello @Unheilig, did you go through it. – Ranjit Jan 21 '14 at 12:42
  • Hello, thanks for your support and cooperation. I have a doubt, as you have already watched WWDC session. What is that locationsamples array about, and if you look at the else condition in the video, he first adds line to first midPoint, then he runs the for loop and get current and next location and again gets a midPoint and then calls quadcurve function and lastly he again adds line to last point. I am not understanding this behaviour. Can you please throw some light on it. – Ranjit Jan 22 '14 at 06:50
  • @Ranjit this is what that is about. I did watch the video you asked me to. Am just not using array. And I ran it both on device and simulator. Please read my detailed explanation above for any doubt. – Unheilig Jan 22 '14 at 06:54
  • ok.. I am reading it once again, hope I will not have any doubts. if any I will just ping you. – Ranjit Jan 22 '14 at 07:15
  • Hello @Unheilig. Please have a look at this http://stackoverflow.com/questions/21281278/draw-a-path-with-variable-width-in-ios. I need your help – Ranjit Jan 23 '14 at 11:30
  • Hello @Unheilig, I need your help regarding this http://stackoverflow.com/questions/21438586/undo-redo-for-drawing-in-ios – Ranjit Jan 31 '14 at 07:26
  • @Ranjit First, did you manage to sort out the line width? Second: a quick glance at your code there - it doesn't seem to be the actual code you have. If it's not the actual code, it would be hard to assist. – Unheilig Jan 31 '14 at 07:36
  • what line width are you talking about?.it is the actual code, I have. If I remove the varying width code, then everything works fine. Please tell me what doubt you have? – Ranjit Jan 31 '14 at 07:43
  • Hello @Unheilig, please help me out http://stackoverflow.com/questions/21545923/multitouch-tracking-issue – Ranjit Feb 09 '14 at 14:41
  • Hello @Unheilig please look at this http://stackoverflow.com/questions/21935064/undo-with-multitouch-drawing-in-ios – Ranjit Feb 21 '14 at 12:59