How can i improve arrowhead line drawing using CoreGraphics? - objective-c

In my app i have view where user can draw arrows.
It works almost perfect.
If i swipe slowly i have nice result.
Image
Bu if i try to swipe faster i got the arrowhead somewhere but not at the very end of line.
image
Sorry, due to my reputation i can't post images here.
How Can i improve arrowhead line drawing to draw however fast i swipe?
To do this i use next method -
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
mouseSwiped = YES;
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self.view];
UIGraphicsBeginImageContext(self.view.frame.size);
[self.tempDrawImage.image drawInRect:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
CGContextClearRect(UIGraphicsGetCurrentContext(),CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height));
double slopy, cosy, siny;
// Arrow size
double length = 10.0;
double width = 10.0;
slopy = atan2((firstPoint.y - lastMovePoint.y), (firstPoint.x - lastMovePoint.x));
cosy = cos(slopy);
siny = sin(slopy);
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), firstPoint.x, firstPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
//here is the tough part - actually drawing the arrows
//a total of 6 lines drawn to make the arrow shape
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastMovePoint.x, lastMovePoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(),
lastMovePoint.x + (length * cosy - ( width / 2.0 * siny )),
lastMovePoint.y + (length * siny + ( width / 2.0 * cosy )) );
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(),
lastMovePoint.x + (length * cosy + width / 2.0 * siny),
lastMovePoint.y - (width / 2.0 * cosy - length * siny) );
CGContextClosePath(UIGraphicsGetCurrentContext());
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), brush );
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), red, green, blue, 1.0);
CGContextSetBlendMode(UIGraphicsGetCurrentContext(),kCGBlendModeNormal);
CGContextStrokePath(UIGraphicsGetCurrentContext());
self.tempDrawImage.image = UIGraphicsGetImageFromCurrentImageContext();
[self.tempDrawImage setAlpha:opacity];
UIGraphicsEndImageContext();
lastMovePoint = currentPoint;

From my experience when drawing with core graphics, when swiping fast over a view, the distance between the points every time touchesMoved: gets called is larger, and I see that you are using a lastMovePoint which can be further than you'd like, giving you unwanted results. This does not happen when moving slowly and the distance between each point is minimal (sometimes even less than 1 point)
I suggest working only with the firstPoint and currentPoint, calculate the angle and draw the arrow using only that, because, when it comes to it, to draw a line you only need the first and last point, it doesn't matter where the user touched before :)

Related

Clear CGContext Lines

Once I drawn some lines over an UIImage, (like a brush application), then I want to clear them, so like a rubber. I searched a lot on this site and on Google, and I tried some codes, but I couldn't find a correct solution.
Here's the code I use to write:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if(isDrawing == YES) {
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:mainImageView.superview];
UIGraphicsBeginImageContext(mainImageView.frame.size);
[mainImageView.image drawInRect:CGRectMake(0, 0, mainImageView.frame.size.width, mainImageView.frame.size.height)];
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), dimension);
const CGFloat *components = CGColorGetComponents([color CGColor]);
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), components[0], components[1], components[2], components[3]);
CGContextBeginPath(UIGraphicsGetCurrentContext());
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
CGContextStrokePath(UIGraphicsGetCurrentContext());
mainImageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
lastPoint = currentPoint;
} else {
//Clear current line (like a rubber would do)
}
}
In this scenario [UIColor clearColor] won't work, use CGContextClearRect(context,rect); to clear the region!
Fill desired part of the context with the [UIColor clearColor].
The correct approach is using a Bezier path that:
records every drawings ( but actually does NOT draw.. in DrawRect we will draw.. using "stroke")
has thickness, color and so on...
draws effectively on view
Clearing it (using "removeAllPoints") and invoking setNeedsDisplay will make the background reappear.
If You want to rubber-clear the path you wrote using another tool, (say You use a pencil tool to write in blue.. and a Rubber tool to clear the blue lines and make background to reappear) you can use "containsPoint:" to remove some points form bezier path.
We did in such way.
using bezier path is NOT trivial but very exciting.. :)

Relationship Between Skew and Angle - CGAffineTransform

I am working on an iPad application that allows the user to draw a line with their finger, then using the beginning and end points of that line I calculate the angle of the line in relation to the x/y plane of the iPad.
My problem is that I want to use the angle of the drawn line to set the skew of an image that I am drawing into the current context.
This is the relavent code from my drawRect method:
CGSize size = CGSizeMake(1024, 768);
UIGraphicsBeginImageContext(size);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGAffineTransform skewIt = CGAffineTransformMake(1, 0, skewValue, 1, 0, 0);
CGContextConcatCTM(ctx, skewIt);
CGContextDrawImage(UIGraphicsGetCurrentContext(),
CGRectMake(0,0,size.width, size.height),
theImage.CGImage);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
I calculate the angle of the line in my touchEnded method, the point values of the drawn line are stored in an array named skewArray:
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
if (stepThree){
CGContextClearRect(skewContext, CGRectMake(0, 0, 1024, 668));
CGPoint pt1 = CGPointMake([[skewArray objectAtIndex:1]point3].x, [[skewArray objectAtIndex:1]point3].y);
CGPoint pt2 = CGPointMake([[skewArray objectAtIndex:[skewArray count]-1]point3].x, [[skewArray objectAtIndex:[skewArray count]-1]point3].y);
CGPoint pt3 = CGPointMake([[skewArray objectAtIndex:[skewArray count]-1]point3].x, [[skewArray objectAtIndex:1]point3].y);
CGFloat dis1 = sqrt((pow((pt2.x - pt1.x), 2) + pow((pt2.y - pt1.y), 2)));
CGFloat dis2 = sqrt((pow((pt3.x - pt2.x), 2) + pow((pt3.y - pt2.y), 2)));
CGFloat dis3 = sqrt((pow((pt1.x - pt3.x), 2) + pow((pt1.y - pt3.y), 2)));
angle = acos(((-1*pow(dis2, 2))+pow(dis1, 2)+pow(dis3, 2))/(2*dis1*dis3)) * 180/3.14;
//Do something with the angle to produce the appropriate skew value
[self setNeedsDisplay];
}
}
Thank you in advance for your help!
Check out this Wikipedia article on shear mapping. It looks like you'd be better off getting the slope and using 1.0 / m as you skew value.

Plotting out Compass Bearing on iPhone screen

Ok so here is my question,
Let's say you have a bearing of 80 degrees. I would like to plot that out on the iPhone screen with a simple 10x10 square. Let's figure the top of the iPhone in portrait mode is North.
Any ideas on how I would accomplish this?
BTW - I don't want to use the following method:
CGAffineTransform where = CGAffineTransformMakeRotation(degreesToRadians(x_rounded));
[self.compassContainer2 setTransform:where];
I would like to plot out on the screen manually via setting the X -Y cords on the iPhone screen.
- (void)drawRect:(CGRect)rect
{
float compass_bearing = 80.0; // 0 = North, 90 = East, etc.
CGContextRef theContext = UIGraphicsGetCurrentContext();
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 5.0, 5.0);
CGPathAddLineToPoint(path, NULL,
5.0 + 5.0 * cos((compass_bearing - 90.0) * M_PI / 180.0),
5.0 + 5.0 * sin((compass_bearing - 90.0) * M_PI / 180.0));
CGContextSetLineWidth(theContext, 2.0);
CGContextSetStrokeColorWithColor(theContext, [UIColor blackColor].CGColor);
CGContextAddPath(theContext, path);
CGContextStrokePath(theContext);
CGPathRelease(path);
}
So it sounds to me like what you want to accomplish should exist within the drawRect method of a custom view, and then this view would be added to your screen by whatever method you desired (i.e. storyboard or programmatically). Here is a possible implementation that you could use to draw a straight line from the center of the view based on some 'angle':
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
// Drawing code
CGFloat angle = 0.0 /180.0 * M_PI ;
//Set this to be the length from the center
CGFloat lineDist = 320.0;
CGContextSetLineWidth(context, 5.0);
//Set Color
[[UIColor redColor] setStroke];
//Draw the line
CGContextBeginPath(context);
//Move to center
CGContextMoveToPoint(context, self.frame.size.width/2, self.frame.size.height/2);
//Draw line based on unit circle
//Calculate point based on center starting point, and some movement from there based on the angle.
CGFloat xEndPoint = lineDist * sin(angle) + self.frame.size.width/2;
//Calculate point based on center starting point, and some movement from there based on the angle. (0 is the top of the view, so want to move up when your answer is negative)
CGFloat yEndPoint = -lineDist * cos(angle) + self.frame.size.height/2;
CGContextAddLineToPoint(context, xEndPoint, yEndPoint);
CGContextStrokePath(context);
}

Objective C: Touch Draw On UIScrollView

I am trying to make a drawing book app with different pages on it using a pageEnabled UIScrollView.
What am i gonna use?? touch events are not working.
i have this code before on touches moved:
touchSwiped = YES;
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self.view];
currentPoint.y -= 20;
UIGraphicsBeginImageContext(self.view.frame.size);
[drawView.image drawInRect:CGRectMake(0, 0, drawView.frame.size.width, drawView.frame.size.height)];
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 15.0);
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 0, 0, 0, 1.0);
CGContextBeginPath(UIGraphicsGetCurrentContext());
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
CGContextStrokePath(UIGraphicsGetCurrentContext());
drawView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
lastPoint = currentPoint;
touchMoved++;
if (touchMoved == 10) {
touchMoved = 0;
}
i don't have any idea what adjustments i need to do in order to implement this.
anyone??
thanks in advance.
If you are using a UIPanGestureRecognizer as mentioned in your comments, then you have set the minimumNumberOfTouches to something greater than 1, right? Otherwise, how does it tell the difference between drawing and scrolling?
My recommendations are:
Do not use a UIScrollView unless you first figure out a way to conclusively determine if a touch is for drawing or for scrolling as intended by the user. Use a subclass of UIView instead, and then maybe use buttons to move between pages.
Or you could have a button that toggles between drawing and scrolling (paging) between the pages of your scroll view.
Check out this website for some tips and actual code to do the drawing http://www.ifans.com/forums/showthread.php?t=132024

Draw and Erase Lines with Touch

I have a background image (ImageBG) and a foreground image (ImageFG) in separate UIImageViews. They are the same size and, as the name implies, ImageBG is behind ImageFG so you can't see ImageBG.
If the user starts to "draw" on them, instead of a line appearing where the user touches the screen, I want that portion of ImageFG to become transparent and reveal ImageBG.
The closest thing I can think of is a scratch-and-win.
I know how to do the drawing on a graphics context, but when I draw a transparent line (alpha = 0), well... I get a transparent line on top of whatever is currently in my graphics context, so basically nothing is drawn. This is what I use to set the stroke color.
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 1.0, 0.0, 0.0, 0.0)
What am I doing wrong? I know this is possible because there are apps out there that do it.
Any hints, tips or direction is appreciated.
So you have a layer that is some image like the grey stuff you want to scratch off, you write this image (in this example scratchImage which is a UIImageView and its UIImage .image )to the graphics context. You then stroke it with the blend mode set to kCGBlendModeClear then you save that image (with the cleared paths). This will reveal an image that you have underneath perhaps in another UIImageView. Note that in this instance, self is a UIView.
So outside touchesMoved create a variable to hold a CGPoint
CGPoint previousPoint;
CGPoint currentPoint;
Then in touchesBegan, set it to the previous point.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
previousPoint = [touch locationInView:self];
}
In touchesMoved, write the image to the context, stroke the image with the clear blend mode, save the image from the context, and set the previous point to the current point.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
currentPoint = [touch locationInView:self];
UIGraphicsBeginImageContext(self.frame.size);
//This may not be necessary if you are just erasing, in my case I am
//adding lines repeatedly to an image so you may want to experiment with moving
//this to touchesBegan or something. Which of course would also require the begin
//graphics context etc.
[scratchImage.image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
CGContextSaveGState(UIGraphicsGetCurrentContext());
CGContextSetShouldAntialias(UIGraphicsGetCurrentContext(), YES);
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 5.0);
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 0.25, 0.25, 0.25, 1.0);
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, nil, previousPoint.x, previousPoint.y);
CGPathAddLineToPoint(path, nil, currentPoint.x, currentPoint.y);
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeClear);
CGContextAddPath(UIGraphicsGetCurrentContext(), path);
CGContextStrokePath(UIGraphicsGetCurrentContext());
scratchImage.image = UIGraphicsGetImageFromCurrentImageContext();
CGContextRestoreGState(UIGraphicsGetCurrentContext());
UIGraphicsEndImageContext();
previousPoint = currentPoint;
}