iOS How to draw a stroke with an outline - ios

I am looking for an output as in this image Expected Result
I need an outline to my stroke. My code is as follows
- (void)awakeFromNib {
self.strokeArray = [NSMutableArray array];
self.layerIndex = 0;
self.isSolid = false;
self.path = [[UIBezierPath alloc] init];
self.innerPath = [[UIBezierPath alloc] init];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
[self.path moveToPoint:p];
[self.innerPath moveToPoint:p];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
[self.path addLineToPoint:p];
[self.innerPath addLineToPoint:p];
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
[self.path addLineToPoint:p];
[self.innerPath addLineToPoint:p];
[self drawBitmap];
[self setNeedsDisplay];
[self.path removeAllPoints];
[self.innerPath removeAllPoints];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesEnded:touches withEvent:event];
}
- (void)drawRect:(CGRect)rect
{
[self.incrementalImage drawInRect:rect];
[self.brushColor setStroke];
self.path.lineWidth = self.brushWidth;
if(self.isEraser)
[self.path strokeWithBlendMode:kCGBlendModeClear alpha:0.0];
else
[self.path stroke];
self.innerPath.lineWidth = self.brushWidth - 10;
[[UIColor clearColor] setStroke];
[self.innerPath strokeWithBlendMode:kCGBlendModeClear alpha:1.0];
}
- (void)drawBitmap
{
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
if (!self.incrementalImage)
{
CGContextClearRect(context, CGRectMake(0,0,self.bounds.size.width,self.bounds.size.height));
}
[self.incrementalImage drawAtPoint:CGPointZero];
[self.brushColor setStroke];
self.path.lineWidth = self.brushWidth;
if(self.isEraser)
[self.path strokeWithBlendMode:kCGBlendModeClear alpha:0.0];
else
[self.path stroke];
self.innerPath.lineWidth = self.brushWidth - 10;
[[UIColor clearColor] setStroke];
[self.innerPath strokeWithBlendMode:kCGBlendModeClear alpha:1.0];
self.incrementalImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
On screen what i get is as per this image, Actual Result
I understand that blend mode 'clear' gives an eraser effect. What i want is that the stroke should have a solid outline on the sides and be clear in the middle. It should not blend into the path right below it.The path below it should still be visible.
How can i achieve this result?

Have a look at this question: Generate a CGPath from another path's line width outline
I guess that's what you need: creating a polygon of the line and stroke that instead of the line. Use CGPathCreateCopyByStrokingPath to get a path of the polygon and stroke it.
OK, since CGPathCreateCopyByStrokingPath seems to be a bit buggy, you could create your own implementation. Something like the following algorithm, which assumes you have a NSArray of CGPoint packed in NSValue objects.
It works pretty well until you start adding short overlapping lines which could of course easily happen while drawing. You could reduce this effect by only adding points in touchesMoved that have a larger distance (lineWidth/2?) to the previous added point. Another drawback is that the lines are simply straight (no kCGLineJoinRound or kCGLineCapRound) but maybe you could adjust the algorithm to do that.
+ (UIBezierPath*)polygonForLine:(NSArray *)points withLineWidth:(CGFloat)lineWidth{
UIBezierPath *path = [UIBezierPath bezierPath];
//stores the starting point to close the path
CGPoint startPoint;
//get the points to a c-array for easier access
CGPoint cpoints[points.count];
int numPoints = 0;
for(NSValue *v in points){
cpoints[numPoints++] = [v CGPointValue];
}
//store the last intersection to apply it for the next segement
BOOL hasIntersection = NO;
CGPoint intersectionPoint;
for (int i=0;i<numPoints-1;i++){
//get the current line segment
CGPoint p1 = cpoints[i];
CGPoint p2 = cpoints[i+1];
CGPoint l1p1,l1p2;
getOffsetLineSegmentForPoints(p1,p2,lineWidth,&l1p1,&l1p2);
//if there had been an intersection with the previous segement, start here to get a nice outline
if(hasIntersection){
l1p1 = intersectionPoint;
}
//is there a next segment?
if(i+2<numPoints){
//get the next line segment
p1 = cpoints[i+1];
p2 = cpoints[i+2];
CGPoint l2p1,l2p2;
getOffsetLineSegmentForPoints(p1,p2,lineWidth,&l2p1,&l2p2);
//calculate the intersection point with the current line segment
hasIntersection = getLineIntersection(l1p1, l1p2, l2p1, l2p2, &intersectionPoint);
//if they intersect, the current linesegment has to end here to get a nice outline
if(hasIntersection){
l1p2 = intersectionPoint;
}
}
//write the current linesegment to the path
if(i==0){
//first point, move to it and store it for closing the path later on
startPoint = l1p1;
[path moveToPoint:startPoint];
}else{
[path addLineToPoint:l1p1];
}
[path addLineToPoint:l1p2];
}
//now do the same for the other side of the future polygon
hasIntersection = NO;//reset intersections
for (int i=numPoints-1;i>0;i--){
//get the current line segment
CGPoint p1 = cpoints[i];
CGPoint p2 = cpoints[i-1];
CGPoint l1p1,l1p2;
getOffsetLineSegmentForPoints(p1,p2,lineWidth,&l1p1,&l1p2);
//if there had been an intersection with the previous segement, start here to get a nice outline
if(hasIntersection){
l1p1 = intersectionPoint;
}
//is there a next segment?
if(i-2>=0){
//get the next line segment
p1 = cpoints[i-1];
p2 = cpoints[i-2];
CGPoint l2p1,l2p2;
getOffsetLineSegmentForPoints(p1,p2,lineWidth,&l2p1,&l2p2);
//calculate the intersection point with the current line segment
hasIntersection = getLineIntersection(l1p1, l1p2, l2p1, l2p2, &intersectionPoint);
//if they intersect, the current linesegment has to end here to get a nice outline
if(hasIntersection){
l1p2 = intersectionPoint;
}
}
//write the current linesegment to the path
[path addLineToPoint:l1p1];
[path addLineToPoint:l1p2];
}
//close the path
[path addLineToPoint:startPoint];
//we're done
return path;
}
void getOffsetLineSegmentForPoints(CGPoint p1, CGPoint p2, CGFloat lineWidth, CGPoint *linep1, CGPoint *linep2){
CGPoint offset = CGPointSub(p2, p1);
offset = CGPointNorm(offset);
offset = CGPointOrthogonal(offset);
offset = CGPointMultiply(offset, lineWidth/2);
(*linep1) = CGPointAdd(p1, offset);
(*linep2) = CGPointAdd(p2, offset);
}
CGPoint CGPointSub(CGPoint p1, CGPoint p2){
return CGPointMake(p1.x-p2.x, p1.y-p2.y);
}
CGPoint CGPointAdd(CGPoint p1, CGPoint p2){
return CGPointMake(p1.x+p2.x, p1.y+p2.y);
}
CGFloat CGPointLength(CGPoint p){
return sqrtf(powf(p.x,2)+powf(p.y,2));
}
CGPoint CGPointNorm(CGPoint p){
CGFloat length = CGPointLength(p);
if(length==0)
return CGPointZero;
return CGPointMultiply(p, 1/length);
}
CGPoint CGPointMultiply(CGPoint p, CGFloat f){
return CGPointMake(p.x*f, p.y*f);
}
CGPoint CGPointOrthogonal(CGPoint p){
return CGPointMake(p.y, -p.x);
}
BOOL getLineIntersection(CGPoint l1p1, CGPoint l1p2, CGPoint l2p1,
CGPoint l2p2, CGPoint *intersection)
{
CGPoint s1 = CGPointSub(l1p2, l1p1);
CGPoint s2 = CGPointSub(l2p2, l2p1);
float determinant = (-s2.x * s1.y + s1.x * s2.y);
if(determinant==0)
return NO;
CGPoint l2p1l1p1 = CGPointSub(l1p1, l2p1);
float s, t;
s = (-s1.y * l2p1l1p1.x + s1.x * l2p1l1p1.y) / determinant;
t = ( s2.x * l2p1l1p1.y - s2.y * l2p1l1p1.x) / determinant;
if (s >= 0 && s <= 1 && t >= 0 && t <= 1){
if (intersection != NULL){
(*intersection).x = l1p1.x + (t * s1.x);
(*intersection).y = l1p1.y + (t * s1.y);
}
return YES;
}
return NO;
}

Related

How to get all the coordinate points (X and Y) in ios

I am writing an app which allows the user to draw what ever they want in the view. While they drawing I am sending the coordinate values simultaneously to web using Json and draw the same diagram in web. When I draw slowly I am getting all the coordinates values like this.
{"XY":["92,240","94,240","96,240","97,240","98,240","99,240","100,240","102,240","103,240","104,240","104,240","105,240","106,240","107,240","108,240","108,240","110,240","110,240","112,240","112,240","114,240","115,240","116,240","117,240","118,240","120,240","120,240","120,240","122,240","122,240","124,240","124,240","126,240"]}
But when I draw quickly I am getting the desired drawing but missing lots of coordinate values.
{"XY":["96,320","117,302","170,262","252,208"]}
The following code that I used to implement this.
#implementation PJRSignatureView
{
UIBezierPath *beizerPath;
UIImage *incrImage;
CGPoint points[10];
uint control;
}
- (void)drawRect:(CGRect)rect
{
[incrImage drawInRect:rect];
[beizerPath stroke];
// Set initial color for drawing
UIColor *fillColor = INITIAL_COLOR;
[fillColor setFill];
UIColor *strokeColor = INITIAL_COLOR;
[strokeColor setStroke];
[beizerPath stroke];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if ([lblSignature superview]){
[lblSignature removeFromSuperview];
}
rectArray = [[NSMutableArray alloc] init];
control = 0;
UITouch *touch = [touches anyObject];
points[0] = [touch locationInView:self];
CGPoint startPoint = points[0];
CGPoint endPoint = CGPointMake(startPoint.x + 1.5, startPoint.y
+ 2);
[beizerPath moveToPoint:startPoint];
NSLog(#"myPoint = %#", [NSValue valueWithCGPoint:endPoint]);
NSLog(#"beizerPath :%#",beizerPath);
[beizerPath addLineToPoint:endPoint];
NSLog(#"beizerPath end:%#",beizerPath);
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self];
control++;
points[control] = touchPoint;
if (control == 4)
{
points[3] = CGPointMake((points[2].x + points[4].x)/2.0, (points[2].y + points[4].y)/2.0);
[beizerPath moveToPoint:points[0]];
[beizerPath addCurveToPoint:points[3] controlPoint1:points[1] controlPoint2:points[2]];
[self setNeedsDisplay];
points[0] = points[3];
points[1] = points[4];
control = 1;
}
NSLog(#"beizerPathmove:%#",beizerPath);
NSString *rect_xy = [NSString stringWithFormat:#"%.f,%.f",touchPoint.x,touchPoint.y];
[rectArray addObject:rect_xy];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self drawBitmapImage];
[self setNeedsDisplay];
[beizerPath removeAllPoints];
NSMutableDictionary *rectDict = [[NSMutableDictionary alloc]init];
[rectDict setValue:rectArray forKey:#"XY"];
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:rectDict options:0 error:nil];
// Checking the format
NSLog(#"%#",[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]);
control = 0;
}
How to find all the coordinate values even when I draw quickly?
The Apple products sends the touch events at a certain intervals. So, If you draw/write anything slowly its possible to get all of the coordinate values, if you draw/write fast you will less some of them. And apply the same kind of function in the web that you have used. Hope this will be fixed now.
The system sends touch events at a certain interval. If you move slow you get more, but going fast you get less of them. You can't get more if you move fast. But you also don't need more probably. You just have to draw the line between points, no matter if the distance between them is small or bigger.

How to 'record' a UIBezierPath to be able to playback a stroked drawing path

In my current iOS app, the user is able to draw with their finger by using a UIBezierPath with smoothing the path; this is pretty simple however. What I would like to know, is if it's possible to record the path, the dots, and the color associated for the path and dots for when the user lifts up their finger and changes pencil colors. My goal then is that a play button would then playback everything they just created in real time, and would be sped up with an animation in case they took several minutes drawing.
I appreciate your responses. Here's the code I'm currently using for drawing (not the best code):
#property (nonatomic, strong) UIBezierPath *path;
#property uint ctr;
#end
#implementation DrawViewController
{
CGPoint pts[4];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
self.ctr = 0;
UITouch *touch = [touches anyObject];
pts[0] = [touch locationInView:self.drawImage];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self.drawImage];
self.ctr++;
pts[self.ctr] = p;
if (self.ctr == 3)
{
pts[2] = CGPointMake((pts[1].x + pts[3].x)/2.0, (pts[1].y + pts[3].y)/2.0);
[self.path moveToPoint:pts[0]];
[self.path addQuadCurveToPoint:pts[2] controlPoint:pts[1]];
//[self.drawImage setNeedsDisplay];
pts[0] = pts[2];
pts[1] = pts[3];
self.ctr = 1;
dispatch_async(dispatch_get_main_queue(),
^{
UIGraphicsBeginImageContextWithOptions(self.drawImage.bounds.size, NO, 0.0);
[self.drawImage.image drawAtPoint:CGPointZero];
[[UIColor colorWithRed:self.red green:self.green blue:self.blue alpha:1.0] setStroke];
[self.path stroke];
self.drawImage.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.path removeAllPoints];
self.ctr = 0;
});
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if (self.ctr == 0)
{
[self.path moveToPoint:pts[0]];
[self.path addLineToPoint:pts[0]];
}
else if (self.ctr == 1)
{
[self.path moveToPoint:pts[0]];
[self.path addLineToPoint:pts[1]];
}
else if (self.ctr == 2)
{
[self.path moveToPoint:pts[0]];
[self.path addQuadCurveToPoint:pts[2] controlPoint:pts[1]];
}
self.ctr = 0;
dispatch_async(dispatch_get_main_queue(),
^{
UIGraphicsBeginImageContextWithOptions(self.drawImage.bounds.size, NO, 0.0);
[self.drawImage.image drawAtPoint:CGPointZero];
[[UIColor colorWithRed:self.red green:self.green blue:self.blue alpha:1.0] setStroke];
[self.path stroke];
self.drawImage.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.path removeAllPoints];
self.ctr = 0;
});
}
What I would do would be to create a custom object that represents a single "segment" of your drawing. (Let's call it a "BezierSegment".) From a quick glance, it looks like you're using quadratic Bezier segments. So create an object that saves the 3 control points for the bezier and the color used to draw it. Each time you draw a new "segment", create one of these objects and add it to a mutable array of segment objects.
Then you could loop through your array of BezierSegment objects, create BezierPath objects out of each one, and draw it to the screen in order to recreate it.
You could also save things like line thickness, optional closed paths with a separate pen color, etc.

how to space apart circle dots after drawing

user touch and drag on top of image
i had this code that will give me circle after drawing but how do i space apart them ?
//UIView CircleView
- (void) touchesBegan:(NSSet *) touches withEvent:(UIEvent *) event
{
NSLog(#"started");
// Initialize a new path for the user gesture
self.path = [UIBezierPath bezierPath];
self.path.lineWidth = 4.0f;
self.whiteCircleArray = [[NSMutableArray alloc]init];
UITouch *touch = [touches anyObject];
[self.path moveToPoint:[touch locationInView:self]];
self.touchedEnded = FALSE;
}
- (void) touchesMoved:(NSSet *) touches withEvent:(UIEvent *) event
{
// Add new points to the path
NSLog(#"Moved");
UITouch *touch = [touches anyObject];
//NSLog(#"[touch locationInView:self] %#",NSStringFromCGPoint([touch locationInView:self]));
[self.whiteCircleArray addObject: NSStringFromCGPoint([touch locationInView:self])];
[self.path addLineToPoint:[touch locationInView:self]];
//double startAngle = RADIANS(0);
//double endAngle = RADIANS(360);
// if ([touch locationInView:self].x <=200 )
// {
// [self.path addArcWithCenter:[touch locationInView:self] radius:LARGE_RADIUS startAngle:startAngle endAngle:endAngle clockwise:NO];
// }
// else if ([touch locationInView:self].x >=120 )
// {
// [self.path addArcWithCenter:[touch locationInView:self] radius:SMALL_RADIUS startAngle:startAngle endAngle:endAngle clockwise:NO];
// }
//[self.path addArcWithCenter:[touch locationInView:self] radius:BIG_RADIUS startAngle:startAngle endAngle:endAngle clockwise:NO];
//[self.path addArcWithCenter:[touch locationInView:self] radius:MIDDIUM_RADIUS startAngle:startAngle endAngle:endAngle clockwise:NO];
[self setNeedsDisplay];
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"ended");
UITouch *touch = [touches anyObject];
//[self.path addLineToPoint:[touch locationInView:self]];
self.locationOfTouch =[touch locationInView:self];
self.touchedEnded = TRUE;
//NSLog(#"white circle pos %#",self.whiteCircleArray);
for(NSString *p in self.whiteCircleArray)
{
CGPoint point = CGPointFromString(p);
NSLog(#"X=%0.1f Y=%0.1f",point.x,point.x);
}
[self setNeedsDisplay];
}
- (void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"cancelled");
[self touchesEnded:touches withEvent:event];
}
-(void)drawTouchCircle
{
CGContextRef ctx= UIGraphicsGetCurrentContext();
CGContextSaveGState(ctx);
CGContextSetLineWidth(ctx,1);
CGContextSetRGBFillColor(ctx, 55.0/255, 255.0/255.0, 5.0/255.0, 1.0);
CGContextSetRGBStrokeColor(ctx,0.0/255,0.0/255,0.0/255,1.0);
for(NSString *p in self.whiteCircleArray)
{
CGPoint point = CGPointFromString(p);
NSLog(#"X=%0.1f Y=%0.1f",point.x,point.x);
CGContextAddArc(ctx, point.x, point.y, LARGE_RADIUS, 0, M_PI*2, YES);
CGContextFillPath(ctx);
CGContextStrokePath(ctx);
}
}
- (void) drawRect:(CGRect)rect
{
// Draw the path
NSLog(#"drawRect");
if (self.touchedEnded)
{
[self drawTouchCircle];
}
else
{
UIColor *strokeColor = [UIColor redColor];
[strokeColor setStroke];
[self.path stroke];
}
// [self.path fill];
//[self.path closePath];
}
Put a distance check in your algo... CGPoint diff = {p2.x - p1.x, p2.y - p1.y} If the diff is greater than , add a new point.

How can I create a movable and resizable polygon with over 1000 sides/lines?

Right now I'm using UIBezierPath and moveToPoint/addLineToPoint inside a view's drawRect.
This same view receives touchesMoved from the viewController. It modifies the posx and posy variables that are used when I draw the polygon, like this:
[path addLineToPoint:CGPointMake([p.getx floatValue]+posx, [p.gety floatValue]+posy)]
Unfortunately the performance is horrible and the polygon leaves a trail whenever I move it.
What's the best way to achieve what I'm trying to do?
edit: the drawRect. polys is a NSMutableArray with poly objects. Each poly is one x/y point.
- (void)drawRect:(CGRect)rect{
UIBezierPath* path;
UIColor* fillColor;
path = [UIBezierPath bezierPath];
for (int i = 0; i < [polys count]; i++){
poly *p = [polys objectAtIndex:i];
if (i == 0){
[path moveToPoint:CGPointMake([p.getx floatValue]+posx, [p.gety floatValue]+posy)];
}else{
[path addLineToPoint:CGPointMake([p.getx floatValue]+posx, [p.gety floatValue]+posy)];
fillColor = [UIColor blueColor]; // plan to use a random color here
}
}
[path closePath];
[fillColor setFill];
[path fill];
}
Haven't figured out your problem. My guess is that you want to draw the polygon with user's finger. I have this small class that works perfectly, probably it can help:
#implementation View {
NSMutableArray* _points;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
self.backgroundColor = [UIColor whiteColor];
_points = [NSMutableArray array];
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// Clear old path
[_points removeAllObjects];
UITouch* touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
[_points addObject:[NSValue valueWithCGPoint:p]];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch* touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
[_points addObject:[NSValue valueWithCGPoint:p]];
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
UIBezierPath* path = [UIBezierPath bezierPath];
for (int i = 0; i < _points.count; i++){
CGPoint p = [_points[i] CGPointValue];
if (i == 0){
[path moveToPoint:p];
}
else {
[path addLineToPoint:p];
}
}
[path closePath];
UIColor* color = [UIColor blueColor];
[color setFill];
[path fill];
}
#end
Just add the view somewhere in your app, maybe make it full screen.

How can I draw a dot on the screen on touchesEnded using UIBezierpath

Can someone here please show me how to draw a single dot using UIBezierpath? I am able to draw a line using the UIBezierpath but if I remove my finger and put it back and then remove nothing get drawn on the screen.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
[pPath moveToPoint:p];
[pPath stroke];
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
[pPath stroke];
}
Your path doesn't include any line or curve segments to be stroked.
Try this instead:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint p = [touches.anyObject locationInView:self];
static CGFloat const kRadius = 3;
CGRect rect = CGRectMake(p.x - kRadius, p.y - kRadius, 2 * kRadius, 2 * kRadius);
pPath = [UIBezierPath bezierPathWithOvalInRect:rect];
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
[[UIColor blackColor] setFill];
[pPath fill];
}
I used this code:
-(void)handleTap:(UITapGestureRecognizer*)singleTap {
//draw dot on screen
CGPoint tapPoint = [singleTap locationInView:self];
[bezierPath_ moveToPoint:tapPoint];
[bezierPath_ addLineToPoint:tapPoint];
[self setNeedsDisplay];
}

Resources