friends, I am working with UIBezierPaths, for free hand drawing , and everything works fine, for this I am storing my paths in a path array, everything works fine, while rendering, I am looping the whole array and rendering the paths,but soon as the array count increases, I see a lag while drawing, below is my drawRect code. please help me out in finding where I am going wrong
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
m_previousPoint2 = m_previousPoint1;
m_previousPoint1 = [mytouch previousLocationInView:self];
m_currentPoint = [mytouch locationInView:self];
CGPoint mid1 = midPoint(m_previousPoint1, m_previousPoint2);
CGPoint mid2 = midPoint(m_currentPoint, m_previousPoint1);
testpath = CGPathCreateMutable();
CGPathMoveToPoint(testpath, NULL, mid1.x, mid1.y);
CGPathAddQuadCurveToPoint(testpath, NULL, m_previousPoint1.x, m_previousPoint1.y, mid2.x, mid2.y);
CGRect bounds = CGPathGetBoundingBox(testpath);
CGPathRelease(testpath);
CGRect drawBox = bounds;
//Pad our values so the bounding box respects our line width
drawBox.origin.x -= self.lineWidth * 2;
drawBox.origin.y -= self.lineWidth * 2;
drawBox.size.width += self.lineWidth * 4;
drawBox.size.height += self.lineWidth * 4;
CGContextRef context = UIGraphicsGetCurrentContext();
context = CGLayerGetContext(myLayerRef);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
[self setNeedsDisplayInRect:drawBox];
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
if(myLayerRef == nil)
{
myLayerRef = CGLayerCreateWithContext(context, self.bounds.size, NULL);
}
[self.layer renderInContext:context];
CGPoint mid1 = midPoint(m_previousPoint1, m_previousPoint2);
CGPoint mid2 = midPoint(m_currentPoint, m_previousPoint1);
CGContextMoveToPoint(context, mid1.x, mid1.y);
CGContextAddQuadCurveToPoint(context, m_previousPoint1.x, m_previousPoint1.y, mid2.x, mid2.y);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, self.lineWidth);
CGContextSetStrokeColorWithColor(context, self.lineColor.CGColor);
CGContextSetFlatness(context, 0.1);
CGContextSetAllowsAntialiasing(context, true);
CGContextStrokePath(context);
[super drawRect:rect];
}
Code updated according to the below discussion by #borrrden
Regards
Ranjit
There is an excellent session in the WWDC 2012 sessions about this exact problem. I think it is called iOS performance: Graphics or something similar. You should definitely watch it.
The summary is, DON'T store it in a path. There is no need to keep this path information around. Store a separate context (CGLayer is best) and draw into that (just like you add points to the path, add points to the context intead). Then in your drawRect, simply draw that layer into the current graphics context. Also be sure to only call setNeedsDisplay on the rectangle that changed, or you will most likely run into problems on a high resolution device.
Related
I want to change the width of the line with 3D-touch while drawing. I'm using the library ACEDrawingView which uses this code to draw:
- (void)draw
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddPath(context, path);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, self.lineWidth);
CGContextSetStrokeColorWithColor(context, self.lineColor.CGColor);
CGContextSetBlendMode(context, kCGBlendModeNormal);
CGContextSetAlpha(context, self.lineAlpha);
CGContextStrokePath(context);
}
I use this code to change the lineWidth based on the pressure in touchesMoved:
// save all the touches in the path
UITouch *touch = [touches anyObject];
//3d touch
CGFloat maximumPossibleForce = touch.maximumPossibleForce;
CGFloat force = touch.force;
CGFloat normalizedForce = force/maximumPossibleForce;
CGFloat lineWidth = normalizedForce * 10;
self.currentTool.lineWidth = lineWidth;
It all works good until I draw another line which crosses the previous one. Then it looks like the image above.
Any help would be greatly appreciated! Please ask for more code if needed, or take a quick look at ACEDrawingView. Thank you!
I'm drawing a path in my SKScene to show the track of my object.
Everything works fine, I can draw the path for all points and add it to scene.
The problem happens when I try to printscreen my scene, everything appears but the path.
Where am i doing it wrong?
- (void)drawPathTrackBall
{
// length of my nsmutablearray with the object track
NSUInteger len = [_trackBallPath count];
// start image context
CGRect bounds = self.scene.view.bounds;
UIGraphicsBeginImageContextWithOptions(bounds.size, YES, [UIScreen mainScreen].scale);
[self.view drawViewHierarchyInRect:bounds afterScreenUpdates:YES];
// draw my path
SKShapeNode *line = [[SKShapeNode alloc] init];
CGMutablePathRef linePath = CGPathCreateMutable();
CGPathMoveToPoint(linePath, NULL, _ballStartX, _ballStartY);
for(NSUInteger i=0; i<len; ++i)
{
NSValue *nsPoint = [_trackBallPath objectAtIndex:i];
CGPoint p = nsPoint.CGPointValue;
CGPathAddLineToPoint(linePath, NULL, p.x, p.y);
}
line.path = linePath;
line.lineWidth = 0.5f;
line.strokeColor = [UIColor redColor];
[_container addChild:line];
_screenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
Thanx
First of all - you're creating simple path, its not connected or drawed to somewhere in your code to context. I didn't learn SpriteKit yet, but I know CoreGraphics well and I think you just can't simply use there SpriteKit API to create a screenshot. Instead try to use native CoreGraphics methods.
To make your code work you need:
Obtain context to which you will draw.
As you've already created a path - just add it to context.
Then stroke/fill it.
To obtain current context to draw call: CGContextRef ctx = UIGraphicsGetCurrentContext();
To add path use : CGContextAddPath(ctx, path);
To stroke/fill use CGContextStrokePath(ctx); / CGContextFillPath(ctx);
So your updated code should look like(notice I've removed code related to SpriteKit stuff):
- (void)drawPathTrackBall
{
// length of my nsmutablearray with the object track
NSUInteger len = [_trackBallPath count];
// start image context
CGRect bounds = self.scene.view.bounds;
UIGraphicsBeginImageContextWithOptions(bounds.size, YES, [UIScreen mainScreen].scale);
[self.view drawViewHierarchyInRect:bounds afterScreenUpdates:YES];
CGContextRef ctx = UIGraphicsGetCurrentContext(); // Obtain context to draw.
CGMutablePathRef linePath = CGPathCreateMutable();
CGPathMoveToPoint(linePath, NULL, _ballStartX, _ballStartY);
for(NSUInteger i=0; i<len; ++i)
{
NSValue *nsPoint = [_trackBallPath objectAtIndex:i];
CGPoint p = nsPoint.CGPointValue;
CGPathAddLineToPoint(linePath, NULL, p.x, p.y);
}
// I've exchanged SpriteKit node API calls with CoreGraphics equivalents.
CGContextSetLineWidth(ctx, 0.5f);
CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
CGContextAddPath(ctx, linePath);
CGContextStrokePath(ctx);
_screenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
Try it.
I am working with a drawing app where I draw inside CGlayers and then draw on grahics context, but I drawing is getting blurred.
Here is my code
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();//Get a reference to current context(The context to draw)
NSLog(#"Size1%#", NSStringFromCGRect(self.bounds));
if(self.currentDrawingLayer == nil)
{
self.currentDrawingLayer = CGLayerCreateWithContext(context, self.bounds.size, NULL);
}
CGPoint mid1 = midPoint(m_previousPoint1, m_previousPoint2);
CGPoint mid2 = midPoint(m_currentPoint, m_previousPoint1);
CGContextRef layerContext = CGLayerGetContext(self.currentDrawingLayer);
// UIGraphicsBeginImageContextWithOptions(self.bounds.size,YES,0.0);
CGContextSetLineCap(layerContext, kCGLineCapRound);
CGContextSetBlendMode(layerContext, kCGBlendModeNormal);
CGContextSetLineJoin(layerContext, kCGLineJoinRound);
CGContextSetLineWidth(layerContext, self.lineWidth);
CGContextSetStrokeColorWithColor(layerContext, self.lineColor.CGColor);
CGContextSetShouldAntialias(layerContext, YES);
CGContextSetAllowsAntialiasing(layerContext, YES);
CGContextSetAlpha(layerContext, self.lineAlpha);
CGContextSetFlatness(layerContext, 1.0f);
CGContextBeginPath(layerContext);
CGContextMoveToPoint(layerContext, mid1.x, mid1.y);//Position the current point
CGContextAddQuadCurveToPoint(layerContext, m_previousPoint1.x, m_previousPoint1.y, mid2.x, mid2.y);
CGContextStrokePath(layerContext);//paints(fills) the line along the current path.
CGContextDrawLayerInRect(context, self.bounds, self.currentDrawingLayer);
}
I get blurr strokes with this code, according to docs, whenever we create the CGlayer with graphicsContext, they say that it will provide the resolution of the device, but then why I am getting blur lines.
Here is the image of blur lines
Regards
Ranjit
Substitute this in your code where you create your CGLayer:
if(self.currentDrawingLayer == nil)
{
CGFloat scale = self.contentScaleFactor;
CGRect bounds = CGRectMake(0, 0, self.bounds.size.width * scale, self.bounds.size.height * scale);
CGLayerRef layer = CGLayerCreateWithContext(context, bounds.size, NULL);
CGContextRef layerContext = CGLayerGetContext(layer);
CGContextScaleCTM(layerContext, scale, scale);
self.currentDrawingLayer = layer;
}
I'm having some issues while trying to draw lines smoothly on touches moved when my UIView is zoomed in. The thing is that lines start to look very pixelated upon certain zoom level.
My view hierarchy is really simple as of now, since this is more like a proof of concept. I have a UIScrollView, my UIView as a child and also it is set as the zoom view.
My drawRect: implementation looks like this:
CGContextRef context = UIGraphicsGetCurrentContext();
[self.layer renderInContext:context];
CGPoint mid1 = midPoint(_previousPoint1, _previousPoint2);
CGPoint mid2 = midPoint(_currentPoint, _previousPoint1);
CGContextSetBlendMode(context, kCGBlendModeNormal);
CGContextMoveToPoint(context, mid1.x, mid1.y);
CGContextAddQuadCurveToPoint(context, _previousPoint1.x, _previousPoint1.y, mid2.x, mid2.y);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineJoin(context, kCGLineJoinRound);
CGContextSetLineWidth(context, self.lineWidth);
CGContextSetStrokeColorWithColor(context, self.lineColor.CGColor);
CGContextStrokePath(context);
Dumping the contents of the layer into the context is an important part since I'm only refreshing the bounding box enclosing the path formed by the last three touched points (_previousPoint1, _previousPoint2 and _currentPoint).
That operation is actually done on touchesMoved method and the function that handles it is the following:
- (void)calculateMinImageArea:(CGPoint)pp1 :(CGPoint)pp2 :(CGPoint)cp
{
// calculate mid point
CGPoint mid1 = midPoint(pp1, pp2);
CGPoint mid2 = midPoint(cp, pp1);
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, mid1.x, mid1.y);
CGPathAddQuadCurveToPoint(path, NULL, pp1.x, pp1.y, mid2.x, mid2.y);
CGRect bounds = CGPathGetBoundingBox(path);
CGPathRelease(path);
CGRect drawBox = [self extendedRect:bounds];
UIGraphicsBeginImageContext(drawBox.size);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
UIGraphicsEndImageContext();
[self setNeedsDisplayInRect:drawBox];
}
I was able to find a workaround using CAShapeLayers to draw the paths. However, the low performance wasn't acceptable.
I would really appreciate if someone can point me to the right approach.
Thanks.
[self setContentScaleFactor:scale];
use this in scrollViewDidEndZooming: delegate method
Hope this help's you
I am working on a drawing project, where I have an eraser option. The code given below is for when I start my app and draw some lines and the proceed to use the eraser. It works fine and I get the eraser effect. Now the second scenario is where I draw some 10 lines and then click on the "undo button" and undo the whole thing, then I redo the whole thing, and now when I click on the "eraser button" and try to erase some part, but instead, it will clear the whole drawing. This is what I am trying to figure out, but i'm not understanding where I am going wrong so friends, please help me out.
Below is my code.
- (void)drawRect:(CGRect)rect
{
case DRAW:
{
[m_curImage drawAtPoint:CGPointMake(0, 0)];
CGPoint mid1 = midPoint(m_previousPoint1, m_previousPoint2);
CGPoint mid2 = midPoint(m_currentPoint, m_previousPoint1);
CGContextRef context = UIGraphicsGetCurrentContext();
[self.layer renderInContext:context];
CGContextMoveToPoint(context, mid1.x, mid1.y);
CGContextAddQuadCurveToPoint(context, m_previousPoint1.x, m_previousPoint1.y, mid2.x, mid2.y);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, self.lineWidth);
CGContextSetStrokeColorWithColor(context, self.lineColor.CGColor);
CGContextSetAlpha(context, self.lineAlpha);
CGContextSetAllowsAntialiasing(context, YES);
CGContextStrokePath(context);
// [super drawRect:rect];
}
break;
case ERASE:
{
[m_curImage drawAtPoint:CGPointMake(0, 0)];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, rect);
CGContextSetBlendMode(context, kCGBlendModeClear);
[super drawRect:rect];
break;
}
case UNDO:
{
[m_curImage drawInRect:self.bounds];
break;
}
case REDO:
{
[m_curImage drawInRect:self.bounds];
break;
}
default:
break;
}
}
These are functions that run when I click on undo/redo.
-(void)redrawLine
{
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0f);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
NSDictionary *lineInfo = [m_lineArray lastObject];
m_curImage = (UIImage*)[lineInfo valueForKey:#"IMAGE"];
UIGraphicsEndImageContext();
[self setNeedsDisplayInRect:self.bounds];
}
-(void)undoButtonClicked
{
if([m_lineArray count] > 0)
{
NSMutableArray *line = [m_lineArray lastObject];
[m_bufferArray addObject:line];
[m_lineArray removeLastObject];
[self redrawLine];
}
m_drawStep = UNDO;
}
-(void)redoButtonClicked
{
if([m_bufferArray count] > 0)
{
NSMutableArray *line = [m_bufferArray lastObject];
[m_lineArray addObject:line];
[m_bufferArray removeLastObject];
[self redrawLine];
}
m_drawStep = REDO;
}
Please tell me whether I am doing it right.
Regards,
Ranjit
I have solved this issue, the problem and below is the code which works perfectly
case :ERASE
{
CGPoint mid1 = midPoint(m_previousPoint1, m_previousPoint2);
CGPoint mid2 = midPoint(m_currentPoint, m_previousPoint1);
[m_curImage drawAtPoint:CGPointMake(0, 0)];
CGContextRef context = UIGraphicsGetCurrentContext();
[self.layer renderInContext:context];
CGContextMoveToPoint(context, mid1.x, mid1.y);
CGContextAddQuadCurveToPoint(context, m_previousPoint1.x, m_previousPoint1.y, mid2.x, mid2.y);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetBlendMode(context, kCGBlendModeClear);
CGContextSetLineJoin(context, kCGLineJoinRound);
CGContextSetLineWidth(context, self.lineWidth);
CGContextSetStrokeColorWithColor(context, self.lineColor.CGColor);
CGContextSetShouldAntialias(context, YES);
CGContextSetAllowsAntialiasing(context, YES);
CGContextSetFlatness(context, 0.1f);
CGContextSetAlpha(context, self.lineAlpha);
CGContextStrokePath(context);
}
Regards
Ranjit.
This solved my issue, if anyone has the same issue, please implement this.
Here what is happening is when you erase the whole rect will get clear na?? so instead of using that erase method draw line in same color that of screen so that it will look like window is clearing and its easy too jus try out this.
For example
if your drawing screen is black then draw line for touchesmoved on screen in black color so that it will look like clearing.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
lastPoint = [touch locationInView:imagevw];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:imagevw];
UIGraphicsBeginImageContext(self.imagevw.frame.size);
[self.imagevw.image drawInRect:CGRectMake(0, 0, self.imagevw.frame.size.width, self.imagevw.frame.size.height)];
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), width);
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 0.0, 0.0, 0.0, 1.0); // black color
CGContextBeginPath(UIGraphicsGetCurrentContext());
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
CGContextStrokePath(UIGraphicsGetCurrentContext());
imagevw.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
This is code to draw line in black color on black view... just try out...it may help you..