CGContextRestoreGState seems not to restore the previous state - iphone

So, I need to draw a series of dotted lines along an axis.
In the drawRect method I call another method to draw the axis..
In that method I save the GState draw the axis and then save the gstate again draw the dotted line when appropriate,then stroke the path restore the gstate add some thing to the axis fill the path..
The thing is now the whole thing is dotted ...
It seems the the code didn't discard the dotted line pattern when I restored the gstate ...
CGContextSaveGState(ctx);
CGContextSetFillColorWithColor(ctx, [[UIColor whiteColor] CGColor]);
.......
//draw a dased line
CGContextSaveGState(ctx);
CGContextSetLineWidth(ctx, 1.0);
CGContextSetStrokeColorWithColor(ctx, [UIColor whiteColor].CGColor);
CGFloat const kDashedLinesLength[] = {1.0f, 0.5f};
CGContextSetLineDash(ctx, 0.0, kDashedLinesLength, 2);
CGContextMoveToPoint(ctx, LEFT_EXCLUSION_LENGTH + AXIS_LINE_WIDTH, crtYval);
CGContextAddLineToPoint(ctx, LEFT_EXCLUSION_LENGTH + AXIS_LINE_WIDTH + self.xAxis.visibleLength , crtYval);
CGContextStrokePath(ctx);
CGContextRestoreGState(ctx);
...
CGContextFillPath(ctx);
CGContextRestoreGState(ctx);
How can I make sore that only the line I need gets doted ???

I'm not sure what's wrong with your code, but it's not a bug in the context restore which works fine. Likely you have another error in the code you replaced with .....
Could be that you have unbalanced save and restores which will cause all kinds of problems - I'd check that first.
I copied and pasted it with minimal changes into a small iOS test app custom view's drawRect method and ran it and I'm seeing what I expect.
here's the little test code that works as expected:
- (void)drawRect:(CGRect)rect
{
// Drawing code
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSaveGState(ctx);
CGContextSetFillColorWithColor(ctx, [[UIColor blackColor] CGColor]);
CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor);
CGContextSetLineWidth(ctx, 2.0);
//draw a dased line
CGContextSaveGState(ctx);
CGContextSetStrokeColorWithColor(ctx, [UIColor blueColor].CGColor);
CGFloat const kDashedLinesLength[] = {1.0f, 2.0f};
CGContextSetLineDash(ctx, 0.0, kDashedLinesLength, 2);
CGContextMoveToPoint(ctx, 100, 100 );
CGContextAddLineToPoint(ctx, 100, 200);
CGContextStrokePath(ctx);
CGContextRestoreGState(ctx);
CGContextMoveToPoint(ctx, 200, 100 );
CGContextAddLineToPoint(ctx, 200, 200);
CGContextStrokePath(ctx);
CGContextFillPath(ctx);
CGContextRestoreGState(ctx);
}
And is the sample output:

You need to be careful to always exactly balance a Save with a Restore. I've had situations where I had one or the other too many in a totally different location of my app. Because iOS partially reuses contexts this can leave a graphics context in a weird state.
You should search your source code for all instances of a Save and a Restore and count if there are equal numbers of them.

I think I know your problem. Look at it this way: The line isn't drawn until you stroke the path. So whatever pattern is in effect when you call CGContextAddLineToPoint (for example) doesn't matter. When you call CGContextAddLineToPoint you're not drawing a line, you are simply building a path. I'm guessing that your subroutine for drawing the axes does not stroke them. The axes don't get drawn until you later call CGContextStrokePath, at which point the dashed pattern is in effect.

Related

iPhone Quartz Drawing Eraser

I am trying to make an iPhone application with erasing. I am running into 2 problems, if you have a solution for either one please answer this. I would like to erase part of an image.
1) I am currently just clearing the rect but it has a square edge. I would like it to be round, I have previously tried translating but this does not work. I also need it to translate/rotate as few times as possible to maintain the same performance.
2) In addition I wanted to know if there are any other ways of erasing. When erasing fast it is erasing ever 1/2 inch. Is there a way to stroke a path and clear the rect or something? Sorry if this is hard to understand.
CGRect circleRect = CGRectMake([touch CGPointValue].x, [touch CGPointValue].y, 25, 25);
CGContextClearRect(currentContext,circleRect);
This code should do what you're looking for:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, self.strokeWidth);
CGContextSetBlendMode(context, kCGBlendModeClear);
CGContextSetStrokeColorWithColor(context, [[UIColor clearColor] CGColor]);
CGContextBeginPath(context);
CGContextMoveToPoint(context, lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(context, currentPoint.x, currentPoint.y);
CGContextStrokePath(context);
CGContextFlush(context);
The key points are
1) CGContextSetLineCap(context, kCGLineCapRound), which makes it round
2) CGContextSetBlendMode(context, kCGBlendModeClear) which clears the context.
I realize this is an older question but I thought I would expand on Eric Reids answer as I was trying to use his example outside of the drawRect method and had to modify it to get it to work with a context I created in a 'touchesMoved' method in a UIView subclass.
I call this method from 'touchesMoved' and pass it the CGPoint of the touch in the view I'm drawing in.
-(void) processEraseAtPoint:(CGPoint)point
{
// setup a context with the size of our canvas view (the canvas view is the UIView instance I'm drawing into)
UIGraphicsBeginImageContext(self.canvasView.bounds.size);
// get a reference to the context we just created
CGContextRef context = UIGraphicsGetCurrentContext();
// draw the image we want to edit into this context (this is the image containing the drawing I want to erase part of)
[self.canvasView.incrementalImage drawAtPoint:CGPointZero];
// set our context options
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, self.canvasView.settings.brushDiameter);
CGContextSetBlendMode(context, kCGBlendModeClear);
// make the color clear since we're erasing
CGContextSetStrokeColorWithColor(context, [[UIColor clearColor] CGColor]);
// start our path in this context
CGContextBeginPath(context);
// set our first point
CGContextMoveToPoint(context, lastTouch.x, lastTouch.y);
// draw from our last point to this point
CGContextAddLineToPoint(context, point.x, point.y);
// stroke this path (in this case it's clear so it will erase what's there)
CGContextStrokePath(context);
// set our incrementalImage in the canvasView with the updated image from this context
// Note that in the canvasView 'drawRect' method I am calling
// '[self.incrementalImage drawInRect:rect]', so this new image will get drawn
// in my canvasView when I call 'setNeedsDisplay'
self.canvasView.incrementalImage = UIGraphicsGetImageFromCurrentImageContext();
// cleanup our context
CGContextFlush(context);
UIGraphicsEndImageContext();
// set our last touch point for the next line segment
lastTouch = point;
// update our view
[self.canvasView setNeedsDisplay];
}

Using UIGraphicsPushContext(context) and UIGraphicsPopContext()

Very frustrating experience understanding UIGraphicsPushContext(context) and UIGraphicsPopContext()
My understanding is that I can set attributes of the context like the stroke color and then push the context on a stack so I can set a new color for the current context. When I'm done doing that I can return to the context by popping it.
Below is my code. When I run the code below two lines are drawn in blue. What I expect to happen is: that I first set the color green. Go to the blueLine function and push the green context. Draw in blue. Then pop the green context. Allowing the drawLine function to drawn in green.
Here is a screenshot of what is drawn (two blue lines): http://dl.dropbox.com/u/1207310/iOS%20Simulator%20Screen%20shot%20Feb%205%2C%202012%209.00.35%20PM.png
Any help is GREATLY appreciated! Thank you.
- (void)drawBlueLine:(CGContextRef)context
{
UIGraphicsPushContext(context);
[[UIColor blueColor] setStroke];
CGContextBeginPath(context);
CGContextMoveToPoint(context, self.bounds.origin.x, 100);
CGContextAddLineToPoint(context, self.bounds.origin.x+self.bounds.size.width, 200);
CGContextStrokePath(context);
UIGraphicsPopContext();
}
- (void)drawLine:(CGContextRef)context
{
UIGraphicsPushContext(context);
CGContextBeginPath(context);
CGContextMoveToPoint(context, self.bounds.origin.x, 200);
CGContextAddLineToPoint(context, self.bounds.origin.x+self.bounds.size.width, 300);
CGContextStrokePath(context);
UIGraphicsPopContext();
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
[[UIColor redColor] setStroke];
CGContextSetLineWidth(context, 5.0);
[self drawBlueLine:context];
[self drawLine:context];
}
Or shouldn't this work?
- (void)drawLine:(CGContextRef)oldContext
{
UIGraphicsPushContext(oldContext); //makes oldContext the current context but there is a copy on the stack
[[UIColor blueColor] setStroke];
CGContextBeginPath(oldContext);
CGContextMoveToPoint(oldContext, self.bounds.origin.x, 200);
CGContextAddLineToPoint(oldContext, self.bounds.origin.x+self.bounds.size.width, 300);
CGContextStrokePath(oldContext);
UIGraphicsPopContext(); //previous oldContext is moved back into place containing red?
}
- (void)drawRect:(CGRect)rect
{
CGContextRef oldContext = UIGraphicsGetCurrentContext();
[[UIColor redColor] setStroke];
[self drawLine:oldContext];
//anything I draw here should be red. Shouldn't it?`
What UIGraphicsPushContext and UIGraphicsPopContext does as change which context is being used for drawing, not necessarily save and restore any given context's state. What your code is doing is simply repetitively replacing the context that was current prior to the call to drawRect: with the context you grab with UIGraphicsGetCurrentContext (they are likely to be the same anyway).
If you want to save an restore a given context's state, use CGContextSaveGState and CGContextRestoreGState instead.
The problem is the way you are setting the color, you need to use
CGContextSetStrokeColorWithColor(context, color)
like this:
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor)
and
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor)
so that the color is connected to the context.
[[UIColor redColor] setStroke];
is changing the color for all strokes...I'm curious if it would overide the above function if it was set for the context, I think it would not...

How to fill the shapes with color

I am new to Objective-C and iOS development. I have a view with some figures drawn by core drawing. Now I want to fill those shapes with color and I don't know the path or context of shape. Does Objective-C have any function like flood-fill or put-pixel so that by having only the stroke color I can fill any shape inside my view.
-(void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 4.0);
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGFloat components[] = {1.0, 0.0, 0.0, 1.0};
CGColorRef color = CGColorCreate(colorspace, components);
CGContextSetStrokeColorWithColor(context, color);
CGContextMoveToPoint(context, 50, 0);
CGContextAddLineToPoint(context, 0,320);
CGContextStrokePath(context);
CGColorSpaceRelease(colorspace);
CGColorRelease(color);
CGContextSetLineWidth(context, 2.0);
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGContextSetRGBFillColor(context, 0, 255, 0, 1.0);
CGContextMoveToPoint(context, 100, 100);
CGContextAddLineToPoint(context, 150, 150);
CGContextAddLineToPoint(context, 100, 200);
CGContextAddLineToPoint(context, 50, 150);
CGContextAddLineToPoint(context, 100, 100);
CGContextStrokePath(context);
CGContextSetLineWidth(context, 2.0);
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGRect rectangle = CGRectMake(60,170,200,80);
CGContextAddEllipseInRect(context, rectangle);
CGContextStrokePath(context);
}
This will make a circle and a square with some common region. I want a particular region to fill with color when a user taps that region.
So you are looking for flood fill in Objective C.
This is my implementation of Scan Line Flood Fill Algorithm.
Github : UIImageScanlineFloodfill
If you want to learn basic of flood fill:
Lode's Computer Graphics Tutorial Flood Fill
I hope this will help you.
Put your drawing code in the question and I can give you a specific answer.
Normally you just need to set a fill color using
[[UIColor redColor] setFill];
And any shapes you draw your core graphics will be filled with that color automatically.
Try this command:-
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
Core Graphics is closely tied to UIView and its drawRect: method and has a C interface. As you point out, you always draw in the current graphics context. I can think of 2 approaches: (1) When the user taps myView, set a property like myView.pointTapped, and in drawRect: find the shape to fill, set a fill color, and fill the closed path using GGGeometry methods.
There is an objective-C alternative: (2) Take a look at UIBezierPath. It's not as comprehensive as drawing in Core Graphics but it can be used in your ViewController implementation.

Draw lines and fills with a chalk texture in iPhone app

I have code in my iPhone app to draw a pie chart in a UIView. All is great but I need to be able to use a chalk-like texture to draw the outlines and then to fill in the different colours of the pie chart.
Here are extracts from the existing code for each area:
Drawing the circle around the pie chart:
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(ctx, 1.0, 1.0, 1.0, 1.0);
CGContextSetLineWidth(ctx, 8.0);
// Draw a thin line around the circle
CGContextAddArc(ctx, x, y, r, 0.0, 360.0*M_PI/180.0, 0);
CGContextClosePath(ctx);
CGContextDrawPath(ctx, kCGPathStroke);
... and drawing a segment of the pie:
CGContextSetRGBStrokeColor(ctx, 1.0, 1.0, 1.0, 1.0);
CGContextSetLineWidth(ctx, 8.0);
CGContextMoveToPoint(ctx, x, y);
CGContextAddArc(ctx, x, y, r, (startDeg-90)*M_PI/180.0, (endDeg-90)*M_PI/180.0, 0);
CGContextClosePath(ctx);
CGContextDrawPath(ctx, kCGPathStroke);
... then filling it with a colour fill:
CGContextSetRGBFillColor(ctx, color.red, color.green, color.blue, color.alpha );
CGContextMoveToPoint(ctx, x, y);
CGContextAddArc(ctx, x, y, r, (startDeg-90)*M_PI/180.0, (endDeg-90)*M_PI/180.0, 0);
CGContextClosePath(ctx);
CGContextFillPath(ctx);
Ignoring the fact that I haven't pasted in the variable declarations for many of the variables used, I hope the code is fairly self-explanatory.
Now: How could I change the code to use a texture for the actual drawing of the lines/arcs?
Similarly, how can I use a texture fill instead of a colour fill?
I'm trying to get this app finished off and this is my last stumbling block. Other questions on SO and elsewhere on the web just don't seem to answer the question in a way I can follow.
Any help will be gratefully received!
Robert
Know this is outdated but one can set fill or stroke patterns using this simple code snippet:
// Somewhere in initialization code.
UIColor *pattern = [UIColor colorWithPatternImage:[UIImage imageNamed:#"pattern.png"]];
CGColorRef fillColor = [pattern CGColor];
CGColorRef strokeColor = [pattern CGColor];
// Probably in |draw| method call.
CGContextSetStrokeColorWithColor(context, strokeColor);
CGContextSetFillColorWithColor(context, fillColor);
Hope it will help someone. There are also other great advanced possibilities - you can manipulate patterns in many ways: shift, scale, rotate, use colored or stencil patters and many many others, but this requires deep digging in api docs.

draw a line in UIImageView problem

In my application i can draw a line in a UIImageView by the code below,and i want redraw the line even longer when i call the function,however,the output come out is not as expected,it will just draw a new line and remove the old one,the length is remain the same jus the y position different,i dont know which line of my code is wrong or i havent understand the CGContext class in correct way,please help i have scratch my head all the days and cannot find out the problem
- (void) drawLine {
lastPoint = currentPoint;
lastPoint.y -= 40;
//drawImage is a UIImageView declared at header
UIGraphicsBeginImageContext(drawImage.frame.size);
[drawImage.image drawInRect:CGRectMake(0, 0, drawImage.frame.size.width, drawImage.frame.size.height)];
//sets the style for the endpoints of lines drawn in a graphics context
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetLineCap(ctx, kCGLineCapButt);
//sets the line width for a graphic context
CGContextSetLineWidth(ctx,2.0);
//set the line colour
CGContextSetRGBStrokeColor(ctx, 1.0, 0.0, 0.0, 1.0);
//creates a new empty path in a graphics context
CGContextBeginPath(ctx);
//begin a new path at the point you specify
CGContextMoveToPoint(ctx, currentPoint.x, currentPoint.y);
//Appends a straight line segment from the current point to the provided point
CGContextAddLineToPoint(ctx, currentPoint.x,lastPoint.y);
//paints a line along the current path
CGContextStrokePath(ctx);
drawImage.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
currentPoint = lastPoint;
}
I am sure you are done with this, but you have a typo.
CGContextAddLineToPoint(ctx, currentPoint.x,lastPoint.y);
should be:
CGContextAddLineToPoint(ctx, lastPoint.x,lastPoint.y);