6

I'm currently working on a iPad project where I need the functionality to let the user write on a piece of paper with a stylus.

I've tested a couple of styluses and found out that the bamboo was the best. They also have a free application which you can use to write.

The problem I'm facing is that the method I use does not delivers smooth curves. The bamboo paper app provides perfect looking lines. This is the code I have so far:

- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    UIGraphicsBeginImageContext(self.frame.size);

    // draw accumulated lines
    if ([self.lines count] > 0) {
        for (Line *tempLine in self.lines){
            CGContextSetAlpha(context, tempLine.opacity);
            CGContextSetStrokeColorWithColor(context, tempLine.lineColor.CGColor);
            CGContextSetLineWidth(context, tempLine.lineWidth);
            CGContextSetLineCap(context, kCGLineCapRound);
            CGContextSetLineJoin(context, kCGLineJoinRound);
            CGContextAddPath(context, tempLine.linePath);
            CGContextStrokePath(context);

            }
    }

    //draw current line
    CGContextSetAlpha(context, self.currentLine.opacity);


    CGContextSetStrokeColorWithColor(context, self.currentLine.lineColor.CGColor);
    CGContextSetLineWidth(context, self.currentLine.lineWidth);
    CGContextSetLineCap(context, kCGLineCapRound);
    CGContextSetLineJoin(context, kCGLineJoinRound);
    CGContextBeginPath(context);
    CGContextAddPath(context, self.currentLine.linePath);
    CGContextStrokePath(context);


    UIGraphicsEndImageContext();


}

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

    CGPathMoveToPoint(self.currentLine.linePath, NULL, cPoint.x, cPoint.y);

    [self setNeedsDisplay];
}

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

    CGPathAddLineToPoint(self.currentLine.linePath, NULL, currentPoint.x, currentPoint.y);

    [self setNeedsDisplay];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {  
    UITouch *touch = [touches anyObject];
    CGPoint cPoint = [touch locationInView:self];
    CGPathAddLineToPoint(self.currentLine.linePath, NULL, cPoint.x, cPoint.y);

    [self setNeedsDisplay];
    [self.lines addObject:self.currentLine];
    Line *nextLine = [[Line alloc] initWithOptions:self.currentLine.lineWidth color:self.currentLine.lineColor opacity:self.currentLine.opacity];
    self.currentLine = nextLine;
    [nextLine release];
}

Here are the images that makes it clear what problem I'm facing. This is the image what gets generated when writing with the code provided above:

enter image description here

This is the image if I write the same thing on the Mamboo paper app:

enter image description here

Does anyone have an idea how to get the nice writing like in the mamboo app?

genpfault
  • 51,148
  • 11
  • 85
  • 139
CyberK
  • 1,568
  • 3
  • 31
  • 44
  • possible duplicate of [iPhone smooth sketch drawing algorithm](http://stackoverflow.com/questions/5076622/iphone-smooth-sketch-drawing-algorithm) – Brad Larson Sep 19 '11 at 21:22
  • See also [how can i trace the finger movement on touch for drawing smooth curves?](http://stackoverflow.com/questions/1052119/how-can-i-trace-the-finger-movement-on-touch-for-drawing-smooth-curves) . – Brad Larson Sep 19 '11 at 21:23
  • see sample https://github.com/yusenhan/Smooth-Line-View – Yu-Sen Han Jul 03 '12 at 03:44

1 Answers1

7

Instead of joining the points using straight lines (CGPathAddLineToPoint) you should try using bezier curves: CGPathAddCurveToPoint or CGPathAddQuadCurveToPoint.

This is what will do the smoothing.


If you are not familliar with bézier curves, you will probably find the wikipedia page about Bézier curves interesting (not specifically the math equations, but look at the sketches and animated images). It will give your the general idea of how control points affect the smoothing of the lines around the key points of your line.

For your case, Quadratic curves (only one control point for each subsegment between two of your key points) should be sufficient.

Construction of a quadratic Bézier curve - Source: Wikipedia (One line from P0 to P1, smoothed using control point P1)


One example (out of the box, only a suggestion out of my mind, never tested, adapt the coefficients to your needs) to compute this control point C between each of your keypoints A and B is to make the AC being, say, 2/3rd of the length of AB, and (AB,AC) making an angle of 30 degrees or sthg similar.

You may even propose in the settings of your app to adjust the smoothing strengh with a slider, which will impact the values you choose to compute each control points and see which coefficients fits best.

AliSoftware
  • 32,623
  • 6
  • 82
  • 77
  • Ok, I never used bezier curves in an iOS application (I did use them a couple of years ago for a java game if I remember it correct) I will search the internet for some good examples. If you have some good examples, please let me know :D Thanks for your answer! (the linePath object I now use is a CGMutablePathRef) – CyberK Sep 19 '11 at 08:55
  • Not sure you've seen it but I edited my answer since the first reply with some suggestions ;) – AliSoftware Sep 19 '11 at 09:00
  • The thing I don't understand is how to use it in the touch event handler. Because there I always have the x and y value, and the method asks for at least 4 point if I see it correct... – CyberK Sep 19 '11 at 09:06
  • [`CGPathAddQuadCurveToPoint`](http://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/CGPath/Reference/reference.html#//apple_ref/c/func/CGPathAddQuadCurveToPoint), that is used to draw quadratic curves as suggested, only needs two points: the destination point (x,y), exactly as in `CGPathAddLineToPoint`, plus a control point (cx,cy) that will control the smoothing. You should compute this control point (which is the only additional point you have compared to the `CGPathAddLineToPoint` case) as suggested in my answer, and in the drawing method (and not the touch events) – AliSoftware Sep 19 '11 at 09:10
  • Note that this way, while moving your finger over the screen, the drawing you perform in your touch event handlers will still draw straight lines, but when you release your finger and build the `Line` object to add it to your `self.lines` then askes `setNeedsDisplay`, that's the iteration you have in your `drawRect` method that will compute the control points (for lines in `self.lines`) and smooth the drawing. So even if while drawing you will have straight lines until you release your finger, the overall drawing will be smoothed as soon as you have finished drawing and trigger `touchesEnded`. – AliSoftware Sep 19 '11 at 09:15
  • So the touch event methods stay the same or? (sorry but this is really new to me and I'm trying to understand what you said) – CyberK Sep 19 '11 at 09:17
  • that's also strange, why does the Mamboo Paper app shows the correct lines directly (even before releasing the stylus)? – CyberK Sep 19 '11 at 09:20
  • Probably because they recompute the smoothing each time. You may be able to do it too in the future (by also computing the ctrl pts for in your event handlers), but first try to compute the smoothing only in `drawRect` when you iterate thru your `Line` objets to build your CGPath. Once you have this, you will be on the right way (but there will still be a lot of things to do, like optimizing the computation of the CGPath to avoid recomputing it each time, extract the code that compute the control points so that it will be reusable for smoothing while drawing...) but don't try to go too fast ;) – AliSoftware Sep 19 '11 at 09:27
  • _(so yes, for now the event handlers methods may stay the same... but actually in the end you shouldn't even perform any drawing outside of `drawRect`, and the code in the event handlers should only built model objets like your `Line` objects and call `setNeedsDisplay`, no more no less)_ – AliSoftware Sep 19 '11 at 09:30
  • Ok so if I understand it correct, I have to add the CGContextAddQueadCurveToPoint method in the for loop when I loop trough my lines? (just after the CGContextAddPath method?) – CyberK Sep 19 '11 at 09:39
  • Actually I'm a bit confused with your code, what does your `Line` objet holds and represent (a path, a set of two points, ...). What you should do is have a `CGMutablePathRef`, only keep track of the current point, and each time you want to add a (final) point to your path/drawing, add it using `CGPathAddQuadCurveToPoint`. `touchesBegan` should create a new `CGMutablePathRef`, `touchesMoved` add the points to this path using bezier curves and `CGPathAddQuadCurveToPoint`, and `touchesEnded` store this finished path in an array containing all your finished lines. – AliSoftware Sep 19 '11 at 09:47
  • `drawRect` will then draw each finished CGPathRefs in this given array... **and** the CGMutablePathRef being constructed by moving your finger. – AliSoftware Sep 19 '11 at 09:48
  • https://github.com/oflannabhra/Paint-App that is the example I used. Here you can see the complete Line object... It does use an CGMutablePathRef. If you could help me adjust the code that would be highly appreciated... Thanks again for helping me out! – CyberK Sep 19 '11 at 09:54
  • No more time for this today sorry (already have spent too much on SO ;)) will probably only be avail' by the end of the week. Hope you could find your way until then with all my suggestions. – AliSoftware Sep 19 '11 at 10:00
  • OK no problem.. I'll try to figure it out! Thanks for your help! I really appreciate it! – CyberK Sep 19 '11 at 10:01
  • Still havn't found any solution to perfect writing... Can you please help me because I don't figure out the bezier thing with the given points when writing... – CyberK Nov 09 '11 at 11:13
  • Hello @AliSoftware, I need your expert advise on this http://stackoverflow.com/questions/20881721/calculate-controlpoints-while-drawing-in-ios. Please help me out. I am expecting your answer. – Ranjit Jan 02 '14 at 13:21