iOS: Cropping around a drawn path? - ios

I'm using ACEDrawingView to draw within a view.
How would I detect the width and height of the drawing, so that I can crop around it, something like this:
Update: After #Duncan pointed me in the right direction, I was able to look through the source code and found the following:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
// save all the touches in the path
UITouch *touch = [touches anyObject];
previousPoint2 = previousPoint1;
previousPoint1 = [touch previousLocationInView:self];
currentPoint = [touch locationInView:self];
if ([self.currentTool isKindOfClass:[ACEDrawingPenTool class]]) {
CGRect bounds = [(ACEDrawingPenTool*)self.currentTool addPathPreviousPreviousPoint:previousPoint2 withPreviousPoint:previousPoint1 withCurrentPoint:currentPoint];
CGRect drawBox = bounds;
drawBox.origin.x -= self.lineWidth * 2.0;
drawBox.origin.y -= self.lineWidth * 2.0;
drawBox.size.width += self.lineWidth * 4.0;
drawBox.size.height += self.lineWidth * 4.0;
self.drawingBounds = bounds; // I added this property to allow me to extract the bounds and use it in my view controller
[self setNeedsDisplayInRect:drawBox];
}
else if ([self.currentTool isKindOfClass:[ACEDrawingTextTool class]]) {
[self resizeTextViewFrame: currentPoint];
}
else {
[self.currentTool moveFromPoint:previousPoint1 toPoint:currentPoint];
[self setNeedsDisplay];
}
}
However I get this when I test the bounds:
I'm going to keep trying to figure it out, but if anyone could help that would be great!
Update 3: Using CGContextGetPathBoundingBox I was finally able to achieve it.

Every time you get a touchesMoved, record the location of the point you are now drawing. When you are all done, you have all the points. Now look at the largest x value and the smallest x value and the largest y value and the smallest y value in all of those points. That's the bounding box of the drawing.
Another approach (which you've already discovered) is to save the CGPath and then call CGContextGetPathBoundingBox. Basically that does exactly the same thing.
Note that a path has no thickness, whereas your stroke does. You will need to inset the bounding box negatively to allow for this (my screencast doesn't do that).

I'm not familiar with the AceDrawingView class. I can tell you how to do it with iOS frameworks though:
Create your path as a UIBezierPath.
Interrogate the bounds property of the path.

Related

Define custom touch area in custom UIControl object

I am creating a custom UIControl object as detailed here. It is all working well except for the touch area.
I want to find a way to limit the touch area to only part of the control, in the example above I want it to be restricted to the black circumference only rather than the whole control area.
Any idea?
Cheers
You can override UIView's pointInside:withEvent: to reject unwanted touches.
Here's a method that checks if the touch occurred in a ring around the center of the view:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
UITouch *touch = [[event touchesForView:self] anyObject];
if (touch == nil)
return NO;
CGPoint touchPoint = [touch locationInView:self];
CGRect bounds = self.bounds;
CGPoint center = { CGRectGetMidX(bounds), CGRectGetMidY(bounds) };
CGVector delta = { touchPoint.x - center.x, touchPoint.y - center.y };
CGFloat squareDistance = delta.dx * delta.dx + delta.dy * delta.dy;
CGFloat outerRadius = bounds.size.width * 0.5;
if (squareDistance > outerRadius * outerRadius)
return NO;
CGFloat innerRadius = outerRadius * 0.5;
if (squareDistance < innerRadius * innerRadius)
return NO;
return YES;
}
To detect other hits on more complex shapes you can use a CGPath to describe the shape and test using CGPathContainsPoint. Another way is to use an image of the control and test the pixel's alpha value.
All that depends on how you build your control.

touchesMoved, how do I make my view catch up - and stay under the finger?

I have written some code that restricts the movement of a box (UIView) to a grid.
When moving the box, the movement is locked to the grid and the box starts getting behind your finger if you drag diagonally or really fast.
So what is the best way to write a method that makes the box catch up and get back under the finger - it must move on the same path as your finger - and it must also not move through other boxes, so it needs collision detection - so I just can't do an Animate to new center point.
Any suggestions?
This is current code in use:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.superview];
lastLocation = location;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.superview];
CGPoint offset = CGPointMake(self.center.x + location.x - lastLocation.x, self.center.y + location.y - lastLocation.y);
CGPoint closestCenter = [self closestCenter:offset];
CGRect rect = CGRectMake(offset.x - (self.size.width / 2), offset.y - (self.size.height / 2), self.size.width, self.size.height);
if (fabsf(closestCenter.x - offset.x) < fabsf(closestCenter.y - offset.y)) {
offset.x = closestCenter.x;
}
else {
offset.y = closestCenter.y;
}
// Do collision detection - removed for clarity
lastLocation = location;
self.center = offset;
}
Don't use a relative offset movement. Instead, use the actual touch location as the desired position and then bound (modify) it based on the grid restrictions. In this way you won't get any lag behind the touch.
From your collision detection I guess there is a path that must be followed and a naive implementation will 'jump' the view to the touch across boundaries. A simple solution to this is to limit jumping to a maximum of half a grid square (so the user must bring the touch back to the view if they drop it).

ccDrawline till half of the touch location cocos2d

My Problem is i want to draw a line which is exactly half of the touch location
i.e, i am drawing a line on cctouchesmoved and it is drawing a line from first location to my touch location but my problem is i need to display the line only till half of the touch location
here is my code
-(void)draw{
glEnable(GL_LINE_SMOOTH);
glLineWidth(3.0f); // set line width
glColor4f(0.8, 1.0, 0.76, 1.0); // set line color.
ccDrawLine(point1,Point2);
}
-(void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch* touch = [touches anyObject];
CGPoint location = [touch locationInView: [touch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
point1 = location;
Point2=CGPointMake(size.width/2, size.height/2);
}
any help will be greatly appreciated.
give two points, a and b, where a is the 'origin' :
ccpMidPoint(a,b);
or, more generally, you could get an arbitrary distance along the line by subtracting a from b, multiplying by the desired factor, then re-adding a:
float percentageOfDistanceAlongLine = 0.5f;
CGPoint pointAlongLine = ccpMult( ccpSub(b, a), percentageOfDistanceAlongLine);
pointAlongLine = ccpAdd(a, pointAlongLine);
so, in your case, point2 = a, point1 = b

Resize line from endpoints

I am drawing annotations on a view. The line annotation is causing a problem;
I have a parent class of Shape (extended from UIView). All the annotations are subclass of shape. In each annotation class i have override the drawRect method. DrawingCanvas class (extended from UIView and is a subview of my viewController's parent view) handles the pan gesture. Here is a piece of code
-(void) panGestureRecognizer: (UIPanGestureRecognizer *) panGesture {
static CGPoint initialPoint;
CGPoint translation = [panGesture translationInView:panGesture.view];
static Shape *shape;
if(panGesture.state == UIGestureRecognizerStateBegan) {
initialPoint = [panGesture locationInView:panGesture.view];
if(selectedShape == nil) {
if([selectedOption isEqualToString:#"line"]) {
shape = [[Line alloc] initWithFrame:CGRectMake(initialPoint.x, initialPoint.y, 10, 10) isArrowLine:NO];
((Line *)shape).pointStart = [panGesture.view convertPoint:initialPoint toView:shape];
}
[panGesture.view addSubview:shape];
}
[shape setNeedsDisplay];
}
else if(panGesture.state == UIGestureRecognizerStateChanged) {
if([shape isKindOfClass:[Line class]]) {
CGRect newRect = shape.frame;
if (translation.x < 0) {
newRect.origin.x = initialPoint.x + translation.x - LINE_RECT_OFFSET;
newRect.size.width = fabsf(translation.x) + LINE_RECT_OFFSET * 2;
}
else {
newRect.size.width = translation.x + LINE_RECT_OFFSET * 2;
}
if (translation.y < 0) {
newRect.origin.y = initialPoint.y + translation.y - LINE_RECT_OFFSET;
newRect.size.height = fabsf(translation.y) + LINE_RECT_OFFSET * 2;
}
else {
newRect.size.height = translation.y + LINE_RECT_OFFSET * 2;
}
shape.frame = newRect;
CGPoint endPoint = CGPointMake(initialPoint.x + translation.x, initialPoint.y + translation.y);
((Line *)shape).pointStart = [panGesture.view convertPoint:initialPoint toView:shape];
((Line *)shape).pointEnd = [panGesture.view convertPoint:endPoint toView:shape];
[shape setNeedsDisplay];
}
}
}
Line drawRect contains
-(void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, rect);
CGContextSetStrokeColorWithColor(context, self.color.CGColor);
CGContextSetFillColorWithColor(context, self.color.CGColor);
CGContextMoveToPoint(context, pointStart.x, pointStart.y);
CGContextAddLineToPoint(context, pointEnd.x, pointEnd.y);
CGContextSetLineWidth(context, 2.f);
CGContextStrokePath(context);
}
For moving and resizing i am handling touchEvents
For moving the annotation i am doing this in touchesMoved
UITouch *touch = [[event allTouches] anyObject];
CGPoint newPoint = [touch locationInView:self];
CGPoint previousPoint = [touch previousLocationInView:self];
CGRect rect = self.frame;
rect.origin.x += newPoint.x - previousPoint.x;
rect.origin.y += newPoint.y - previousPoint.y;
self.frame = rect;
[self setNeedsDisplay];
It's all good till here, but now for resizing the line by dragging the endpoints of it creates the confusion to me. I have placed to imageViews at the end point. On touches began i am detecting the end point like this
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchPoint = [touch locationInView:self];
if(CGRectContainsPoint(imageView1.frame, touchPoint) || CGRectContainsPoint(imageView2.frame, touchPoint)) {
isEndPoint = YES;
}
if isEndPoint is YES then i have to resize the line. I need to know on which direction am i dragging. how to update the points (i have taken to class variables of CGPoint; pointStart, pointEnd) and the frame.
Kindly suggest the resizing trick to update the points and the frame.
For determining the line's direction:
You can do the following:
Line starting from Start Point:
a) Greater 'X' than the Start Point's X Line is approaching towards right side
b) Less 'X' than the Start Point's X line is approaching to the left side.
c) Greater Y'' than the Start point's 'Y' Line is going downwards.
d) Less 'Y' than the Start point's 'Y' Line is going Upwards.
Line starting from End Point:
a) Greater 'X' than the End Point's X Line is approaching towards right side
b) Less 'X' than the End Point's X line is approaching to the left side.
c) Greater Y'' than the End point's 'Y' Line is going downwards.
d) Less 'Y' than the End point's 'Y' Line is going Upwards.
If the line is being drawn from the End point then keep the Start Point unchanged, else if the line is starting from Start Point, keep the Start Point unchanged.
After drawing the line, you just need to update the Rect of the Line.
For that you need to add 2 more CGPoint properties to the Line's Class.
1. Initial point, 2. Last Point.
Initially set the initial point equivalent to the Start Point and initially set the Last Point equivalent to the End Point.
Afterwards on every piece of line added to the Current Line, just update these 2 properties with proper comparison to the Start and End Points. I am giving you a hint of proper comparisons: For example:
Compare all the 4 points that is Initial Point, Last Point , Start Point and End Point.
Updating Initial Point and Last Point:
Set the Initial Point's 'X' and 'Y' equivalent to the Minimum 'X' and Minimum 'Y' among all these 4 points.Set the Last Point's 'X' and 'Y' equivalent to the Maximum 'X' and Maximum 'Y' among these 4 points.
Make the Line's Rect according to these 2 points Initial Point and Last Point.
I hope this solves your problem.

Touchpoint coordinates in iOS are not mapped properly

I'm trying create an application that allows the user to move a frame over an image, so that I can apply some effects on a selected region.
I need to allow the user to precisely drag and scale the masked-frame on the image. I need this to be exact, just like any other photo app does.
My strategy is to get the touch points of the user, on a touch-moved event, and scale my frame accordingly. That was pretty intuitive. I coded the following stuff for handling the touch moved event :
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:[self view]];
float currX = touchPoint.x;
float currY = touchPoint.y;
/*proceed with other operations on currX and currY,
which is coming out quite well*/
}
But the only problem is, the coordinates of the currX and currY variables are not quite where they are supposed to be. There is a parallax error, which keeps shifting from device to device. I also think the x and y coordinates gets swapped in the case of an iPad.
Could you please help me to figure out how to get the exact touch coordinates?
My background image is in one view (imageBG) and the masked frame is in a separate one (maskBG). I have tried out :
CGPoint touchPoint = [touch locationInView:[maskBG view]];
and
CGPoint touchPoint = [touch locationInView:[imageBG view]];
...but the same problem persists. I have also noticed the error on touch being worse on an iPad than on an iPhone or iPod.
image.center = [[[event allTouches] anyObject] locationInView:self.view];
Hi your issue is the image and the iPhone screen are not necessarily in same aspect ratio.Your touch point might not translate correctly to your actual image.
- (UIImage*) getCroppedImage {
CGRect rect = self.movingView.frame;
CGPoint a;
a.x=rect.origin.x-self.imageView.frame.origin.x;
a.y=rect.origin.y-self.imageView.frame.origin.y;
a.x=a.x*(self.imageView.image.size.width/self.imageView.frame.size.width);
a.y=a.y*(self.imageView.image.size.height/self.imageView.frame.size.height);
rect.origin=a;
rect.size.width=rect.size.width*(self.imageView.image.size.width/self.imageView.frame.size.width);
rect.size.height=rect.size.height*(self.imageView.image.size.height/self.imageView.frame.size.height);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
// translated rectangle for drawing sub image
CGRect drawRect = CGRectMake(-rect.origin.x, -rect.origin.y, self.imageView.image.size.width, self.imageView.image.size.height);
// clip to the bounds of the image context
// not strictly necessary as it will get clipped anyway?
CGContextClipToRect(context, CGRectMake(0, 0, rect.size.width, rect.size.height));
// draw image
[self.imageView.image drawInRect:drawRect];
// grab image
UIImage* croppedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return croppedImage;
}
This is what i did to crop my moving view is the rect which i pass for cropping see how its being translated to reflect correctly on image.Make sure the image view on which the user sees image is aspectfit content mode.
Note:- I make the rect of image view fit the aspectFit image
use this to do it
- (CGSize)makeSize:(CGSize)originalSize fitInSize:(CGSize)boxSize
{
widthScale = 0;
heightScale = 0;
widthScale = boxSize.width/originalSize.width;
heightScale = boxSize.height/originalSize.height;
float scale = MIN(widthScale, heightScale);
CGSize newSize = CGSizeMake(originalSize.width * scale, originalSize.height * scale);
return newSize;
}
have you tried these:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:selectedImageView];
float currX = (touchPoint.x)/selectedImageView.frame.size.width;
float currY = (touchPoint.y)/selectedImageView.frame.size.height;
/*proceed with other operations on currX and currY,
which is coming out quite well*/
}
or you can also use UIPanGestureRecognizer..

Resources