How to save a UIBezierPath to an existing UIImageView? - iphone

I can't figure out how i can save my drawn path to an UIImage?
I know that I have to use UIGraphicsBeginImageContext and something like [drawImage drawInRect:CGRectMake(0, 0, drawImage.size.width, drawImage.size.height)];
But I don't know where to put it in my touch functions.
I need to save every path to the image, because I want to be able to change the color and alpha values after each drawn line.
With this code, all the lines will be adjusted in color and width at the same time when I change the values.
- (id) initWithFrame:(CGRect)frame andImage: (UIImageView*) image
{
...
paths = [[NSMutableArray alloc]init];
drawImage = image;
self.backgroundColor=[UIColor clearColor];
[self addSubview:drawImage];
}
- (void)drawRect:(CGRect)rect
{
[brushPattern setStroke];
[myPath strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
// Drawing code
//[myPath stroke];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
myPath=[[UIBezierPath alloc]init];
[myPath moveToPoint:[mytouch locationInView:self]];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
[myPath addLineToPoint:[mytouch locationInView:self]];
[self setNeedsDisplay];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[paths addObject:myPath];
[myPath release];
}

Based on your earlier question, I am assuming that the image view is a subview of this custom view object. You will need to maintain a mutable array of paths in this class. Initialize it at touchesBegan:withEvent: and close the path at touchesEnded:withEvent:. Push the path into the array. At every touch event (began, moved, ended), call a method that updates the image view's image after adding the touch point to the path.
Updating the image will involve creating an empty image context and then iterating over all paths in your array and the current path while stroking them. Once you're done drawing, generate the image object from the context and set it to the image. Pretty straightforward until we notice that each path might differ in attributes. To deal with this you should create a container class for the path which will hold the attributes about the path. Push an object of this class for every path into the array rather than the path itself. This way you can maintain state for your view.

There is one way out and very simple infact , each time you draw a path object you can save it in an increamental image , just create it at a place where needed and then show this image in draw rect before drawing something else.
here is the sample code:
- (void)drawBitmap
{
UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, 0.0);
[strokeColor setStroke];
if (!incrementalImage) // first time draw; paint background white by ...
{
UIBezierPath *rectpath = [UIBezierPath bezierPathWithRect:self.bounds]; // enclosing bitmap by a rectangle defined by another UIBezierPath object
[[UIColor colorWithPatternImage:[UIImage imageNamed:#"two.jpg"]] setFill];
//[[UIColor whiteColor] setFill];
[rectpath fill]; // filling it with white
NSLog(#"========== ... drawBitmap .. CALLED=======");
}
[incrementalImage drawAtPoint:CGPointZero];
for(NSDictionary *_pathDict in pathArray)
{
[((UIColor *)[_pathDict valueForKey:#"color"]) setStroke]; // this method will choose the color from the receiver color object (in this case this object is :strokeColor)
[[_pathDict valueForKey:#"path"] strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
}
incrementalImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
After that just call this increamental image in drawRect: , so that user has a illusion of continuous drawing and you can then save this image point of time:
- (void)drawRect:(CGRect)rect
{
[incrementalImage drawInRect:rect];
[[dict_path objectForKey:#"color"] setStroke]; // this method will choose the color from the receiver color object (in this case this object is :strokeColor)
[[dict_path objectForKey:#"path"] strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
}
May be you need to modify some code according to your own need.Hope this help someone

Related

how can i move a UIBezierPath object with touchesMoved?

After drawing lines and circles from bezier path objects i want to now move these objects across the screen. First as i touch on the path object it should get selected which i have done using containsPoint: method.
Now i want that this selected object should get move as i drag my fingure. I am wondering how can i move a stroked bezierpath object to a new location ?
here is my code in touches began:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
startPoint = [touch locationInView:self];
//NSLog(#"start point:- %f, %f", startPoint.x, startPoint.y);
isHitInPath = NO;
if(!isTextMode)
{
for (NSDictionary *testDict in pathArray)
{
if([((UIBezierPath *)[testDict objectForKey:#"path"]) containsPoint:startPoint])// if starting touch is on an object of bezierpath
{
NSLog(#"touch point is in path: %# >>>>>>>>>>>>>>", [testDict objectForKey:#"path"]);
isHitInPath = YES;
currentSelectedPath = ((UIBezierPath *)[testDict objectForKey:#"path"]);
CAShapeLayer *centerline = [CAShapeLayer layer];
centerline.path = currentSelectedPath.CGPath;
centerline.strokeColor = [UIColor whiteColor].CGColor;
centerline.fillColor = [UIColor clearColor].CGColor;
centerline.lineWidth = 1.0;
centerline.lineDashPattern = [NSArray arrayWithObjects:[NSNumber numberWithInt:6], [NSNumber numberWithInt:6], nil];
[self.layer addSublayer:centerline];
// showing animation on line
CABasicAnimation *dashAnimation;
dashAnimation = [CABasicAnimation animationWithKeyPath:#"lineDashPhase"];
[dashAnimation setFromValue:[NSNumber numberWithFloat:0.0f]];
[dashAnimation setToValue:[NSNumber numberWithFloat:45.0f]];
[dashAnimation setDuration:1.0f];
[dashAnimation setRepeatCount:10000];
[centerline addAnimation:dashAnimation forKey:#"linePhase"];
break;
}
}
}
What should be the correct way of moving (or may be removing old path object and then creating new one of the same size and figure and then move it ) a path object in touches moved.
Sorry for late reply , i solved it. Here is the code from my App:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
endPoint = [touch locationInView:self];
if(isAnyShapeSelected) // shape is selected and getting moved
{
[self deselectShape];// deselect the selected shape
// then create a new bezier path object with a new location (transformed bezierpath object)
CGPathRef _path = currentSelectedPath.CGPath;// currentSelectedPath is a path which we have selected in touches begin (or while moving)
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformTranslate(transform, endPoint.x-startPoint.x, endPoint.y-startPoint.y);
_path = (CGPathCreateMutableCopyByTransformingPath(_path, &transform));
currentSelectedPath.CGPath = _path;
// UIBezierPath *uiPath = [UIBezierPath bezierPathWithCGPath:_path];
// uiPath.lineWidth = currentSelectedPath.lineWidth;
startPoint = endPoint;
[self selectShape:currentSelectedPath];// select the shape again
[dict_path removeAllObjects];//remove all object from current drawing dictionary
// NSLog(#"pathArray: %#", pathArray);
// Add values to dictionary
if([[pathArray objectAtIndex:selectedShapeIndex] objectForKey:#"shape"])// if key 'shape' exist for the selected shape, if filled arrow is selected
[dict_path setValue:#"arrow" forKey:#"shape"]; // then there must be no affect on it while moving it
[dict_path setValue:currentSelectedPath forKey:#"path"];
[dict_path setValue:[[pathArray objectAtIndex:selectedShapeIndex] objectForKey:#"color"] forKey:#"color"];
[pathArray removeObjectAtIndex:selectedShapeIndex];// REMOVING object frm pathArray
NSDictionary *tempDict = [NSDictionary dictionaryWithDictionary:dict_path];
[pathArray insertObject:tempDict atIndex:selectedShapeIndex];// addding selected shape at every pixel movement
[dict_path removeAllObjects];
CGPathRelease(_path);
[self setNeedsDisplay];
}
}
// here is my draw rect method
- (void)drawRect:(CGRect)rect
{
for(NSDictionary *_pathDict in pathArray)
{
if([_pathDict objectForKey:#"shape"])// if shape is arrow then fill it(shape key is taken for arrow)
{
[((UIColor *)[_pathDict valueForKey:#"color"]) setFill]; // this method will choose the color from the receiver color object (in this case this object is :strokeColor)
[[_pathDict valueForKey:#"path"] fill];
}
[((UIColor *)[_pathDict valueForKey:#"color"]) setStroke]; // this method will choose the color from the receiver color object (in this case this object is :strokeColor)
[[_pathDict valueForKey:#"path"] strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
}
if([dict_path objectForKey:#"shape"])
{
[[dict_path objectForKey:#"color"] setFill]; // this method will choose the color from the receiver color object (in this case this object is :strokeColor)
[[dict_path objectForKey:#"path"] fill];
}
[[dict_path objectForKey:#"color"] setStroke]; // this method will choose the color from the receiver color object (in this case this object is :strokeColor)
[[dict_path objectForKey:#"path"] strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
}
-(void)deselectShape // deselect shape
{
[centerline removeFromSuperlayer];
isAnyShapeSelected = NO;
[btnResize removeFromSuperview];
// show hide delete button
[self.delegate showDeleteButton:isAnyShapeSelected];//delegate to active and inactive delete button
}
For how to select a shape on touchesBegin see my anwer at How do I detect a touch on a UIBezierPath and move a ball along that?

UIImageView draw in rect with size of image and not image view

I want to draw on UIImageView, following is my code for drawing. My image view mode is aspect fit. When i draw using the following code, since i am using drawinrect and giving the rect of UIImageView so all the images are scaled down to the size to the `imageview. I want to draw the image of size of image and not image view.Since i am detecting the touch point on the imageview so the point of tap and actual drawing on image is different. Can any body tell me how to draw on image directly. and how to calculate this shift or difference in the point of touch on image rather than image view.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
lastTouch = [touch locationInView:self];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint currentTouch = [[touches anyObject] locationInView:self];
UIGraphicsBeginImageContext(self.image.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[self.image drawInRect:CGRectMake(0, 0, self.image.size.width, self.bounds.size.height)];
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, _brushSize);
CGContextBeginPath(context) ;
CGContextMoveToPoint(context, lastTouch.x, lastTouch.y);
CGContextAddLineToPoint(context,currentTouch.x,currentTouch.y) ;
CGContextSetAlpha( context , 1 );
CGContextSetStrokeColorWithColor(context, [_brushColor CGColor]);
CGContextStrokePath(context) ;
self.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
lastTouch = currentTouch;
}
You shouldn't really be us classing UIImageView. It isn't intended to be subclassed.
A better approach to this would be to have a UIView instead of the UIImageView.
Then two possible ways is to either...
Draw the UIImage directly into the UIView inside drawRect but this will give you the same problem that you are currently having.
Or...
In the UIView have a UIImageView. Resize this so that it is the correct size/shape for the UIImage. (Ie do the scaling yourself) then over the UIImageView put a second UIView (and subclass this) this second one will be capped DrawingView or something. You can now do all your drawing inside here. And all your touch detection will be in here too. So you no longer need to convert the touch points into different drawing points.

How to smooth marker stroke for iPhone?

Hi I am working on an application where i have to draw something. When I draw with marker stroke it makes some edges, how i can avoid these edges?
Code for marker stroke
- (void)drawRect:(CGRect)rect
{
// KidsPhotoBookAppDelegate *appDelegate = (KidsPhotoBookAppDelegate*)[[UIApplication sharedApplication]delegate];
// brushPattern = (UIColor*)appDelegate.kidsSelectedColor;
DBManager *dbMgr = [DBManager sharedManager];
[dbMgr.c setStroke];
for (UIBezierPath *_path in pathArray)
[_path strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
KidsPhotoBookAppDelegate *appDelegate = (KidsPhotoBookAppDelegate*)[[UIApplication sharedApplication]delegate];
myPath=[[UIBezierPath alloc]init];
myPath.lineWidth = appDelegate.kidsBrushSize;
myPath.lineCapStyle = kCGLineCapRound;
brushPattern=appDelegate.kidsSelectedColor;
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
[myPath moveToPoint:[mytouch locationInView:self]];
[pathArray addObject:myPath];
appDelegate.viewContainsDrawing = YES;
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
[myPath addLineToPoint:[mytouch locationInView:self]];
[self setNeedsDisplay];
}
You should set line join to kCGLineJoinRound using CGContextSetLineJoin function. See the Quartz 2D Guide for more.
UPDATE
Since you are using UIBezierPath, you should try setting the lineJoinStyle of UIBezierPath to kCGLineJoinRound.
How are you drawing those curves? If you're drawing a sequence of short straight line segments, that's the problem. You should be drawing these sorts of curves with a bezier path. If you're using a bezier path already, you may need to tweak the flatness property.
I achieved this smoothness with the help of this code https://github.com/levinunnink/Smooth-Line-View

Stockapp kind of interaction - UIView x CALayer

I'm implementing some charts with some user interactions, like long-pressing to view the full data under the finger, two fingers to draw a line, and other stuff.
For a start, how would I implement the kind of interaction the iphone stocks app does in the landscape mode of the price chart?
Manipulate a CALayer for the yellow vertical line and the yellow dot? Are those coming from images or rendered? Use a UIView (or UIImageView) instead? Or render them in drawRect?
I think it's really just an implementation detail so it's up to you how to do it. I would guess that they're doing everything in -drawRect, however, you could implement it with Core Animation layers if you wanted to.
If you're already drawing the graph with -drawRect, then it would make sense to stick with that, however, if you just want to overlay layers for the yellow dot and the vertical yellow line you can just use regular CALayer s and change their position in response to -touchesMoved.
// Create a line layer with a 1px width and yellow bg
CALayer *lineLayer = [CALayer layer];
[lineLayer setBounds:CGRectMake(0.0f, 0.0f, 1.0f, parentViewHeight)];
[lineLayer setPosition:CGPointMake(currentX, parentViewHeight/2.0f)];
[lineLayer setBackgroundColor:[[UIColor yellowColor] CGColor]];
[[parentView layer] addSublayer:lineLayer];
// Create a dot layer with the contents set to a CGImageRef
UIImage *dotImage = [UIImage imageNamed:#"yellowdot.png"];
CALayer *dotLayer = [CALayer layer];
[dotLayer setBounds:CGRectMake(0.0f, 0.0f, [dotImage size].width,
[dotImage size].height)];
[dotLayer setPosition:[lineLayer position]];
[dotLayer setContents:[dotImage CGImage]];
[[parentView layer] addSublayer:dotLayer];
You'll need to make these layers ivars of your class so that when you receive your touch events, you can update their positions.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
{
CGPoint current = [[touches anyObject] locationInView:parentView];
[lineLayer setPosition:CGPointMake(current.x, [lineLayer position].y)];
[dotLayer setPosition:CGPointMake(current.x, [self yForStockPriceAtPoint:current])];
[super touchesMoved:touches withEvent:event];
}

how to get only pixels from inside a irregular shape from uiimageview in iphone?

i want to get only those pixels which are inside an irregular shape..using core graphics not openGL.here is an image where i have drawn an irregular shape.
and here is the drawing code
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if(drawLineClicked)
{
UITouch *touch = [touches anyObject];
if ([touch view] == EditImageView)
{
lastPoint = [touch locationInView:self.view];
[self drawLines:10.0 andColorWithRed:1.0 Green:0.0 Blue:0.0 Alpha:1.0];
[self.view setNeedsDisplay];
currentPoint = lastPoint;
}
}
}
-(void)drawLines:(CGFloat)withWidth andColorWithRed:(CGFloat)red Green:(CGFloat)green Blue:(CGFloat)blue Alpha:(CGFloat)alpha
{
UIGraphicsBeginImageContext(Image.size);
[EditImageView.image drawInRect:CGRectMake(0, 0, Image.size.width, Image.size.height)];
ctx = UIGraphicsGetCurrentContext();
CGContextSetLineCap(ctx, kCGLineCapRound);
CGContextSetAllowsAntialiasing(ctx,TRUE);
CGContextFlush(ctx);
//sets the line width for a graphic context
CGContextSetLineWidth(ctx,withWidth);
//set the line colour
CGContextSetRGBStrokeColor(ctx, red, green, blue, alpha);
CGContextMoveToPoint(ctx, currentPoint.x, currentPoint.y);
CGContextAddLineToPoint(ctx, lastPoint.x,lastPoint.y);
CGContextStrokePath(ctx);
EditImageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
Here's just tossing out an idea:
Instead of drawing the red line directly on the image, I'd duplicate the image, add it as a new layer on top of the original image, and give it a totally transparent mask (so the new layer is "invisible").
Then when the user draws the red line, use that to build a path as a mask on the invisible layer. When the path is finished, fill in the path with black (on the mask), to make that portion totally opaque. You could maybe then resize the top layer (that's been masked) to be the bounding rectangle of the drawn path.
The opaque pixels on the topmost layer will then be the ones that were bounded by the drawn path, and you can then do what you please with it (draw it to a new UIImage, etc).
Hope that makes sense...
Take a look at my old sample code project here: Cropped Image
It's for Cocoa, not Cocoa Touch, but the idea is the same. You can crop an image by compositing using the SourceIn compositing mode.