PDA

View Full Version : Filling a freehand path with a solid color




RottenApple2
Jul 31, 2012, 02:24 PM
I am drawing a freehand path with the code below and now I would like to fill the same with a solid color. I have tried CGContextFillPath(UIGraphicsGetCurrentContext()); but it does not work. Any suggestions, please?

CGPoint currentPoint = [touch locationInView:self];

UIGraphicsBeginImageContext(self.frame.size);
[drawImage.image drawInRect:CGRectMake(0, 0, drawImage.frame.size.width, drawImage.frame.size.height)];

CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(),1.0);
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 1.0, 1.0, 1.0, 1.0);

CGContextBeginPath(UIGraphicsGetCurrentContext());
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
CGContextStrokePath(UIGraphicsGetCurrentContext());

drawImage.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();



PBG4 Dude
Aug 1, 2012, 12:51 PM
You have to close the path because you can only fill closed objects?

CGContextClosePath(context);
(?)

xArtx
Aug 1, 2012, 07:45 PM
CGContextRef context = UIGraphicsGetCurrentContext();

CGContextSetRGBFillColor(context, 1.0, 1.0, 1.0, 1.0);
CGContextMoveToPoint(context, 0, 0);// draw rectangle
CGContextAddLineToPoint(context, 320, 0);
CGContextAddLineToPoint(context, 320, 480);
CGContextAddLineToPoint(context, 0, 480);
CGContextAddLineToPoint(context, 0, 0);
CGContextFillPath(context);

There ya go!

RottenApple2
Aug 3, 2012, 02:58 PM
Your code works, but when I try to apply it to my path it doesn't! Can't understand why.... Here is what I do. Any suggestion as to what I am doing wrong please?

CGContextBeginPath(UIGraphicsGetCurrentContext());
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
CGContextStrokePath(UIGraphicsGetCurrentContext());

CGContextSetRGBFillColor(UIGraphicsGetCurrentContext(), 1.0, 0.4, 1.0, 1.0);
CGContextFillPath(UIGraphicsGetCurrentContext());

----------

I tried, thanks, but it does not make any difference. Also note that xArtx's code above works (and he does't close the path). I am puzzled...

dejo
Aug 3, 2012, 03:08 PM
...but when I try to apply it to my path it doesn't! Can't understand why....

You can't fill a line! You need at least three points: the origin and two other different points. The code you supplied only has two points.

RottenApple2
Aug 3, 2012, 05:30 PM
You can't fill a line! You need at least three points: the origin and two other different points. The code you supplied only has two points.

Good point...but it's a drawing app and it's meant to add a line as you swipe the finger on the screen (and it works). But how can I apply the filling to the final path?

dejo
Aug 3, 2012, 07:25 PM
But how can I apply the filling to the final path?

Where's your code that attempts to fill your final path, the one with more than two points?

RottenApple2
Aug 4, 2012, 04:10 AM
Where's your code that attempts to fill your final path, the one with more than two points?

I am lost! When I release my finger after drawing I end up with the freehand path that I want. Isn't it sufficient to then call CGContextClosePath(context); to close it with the point of origin and then call CGContextFillPath(context); to fill it with a solid color?

ArtOfWarfare
Aug 4, 2012, 07:26 AM
... I'm a little lost following this thread... is the confusion over Stroke vs. Fill?

Strokes are just lines. Fills are shapes. If you have two points, you have a line, not a shape, so you can stroke it with a color, but you can't fill it with one. Shapes have a minimum of three points (you get a triangle.)

Alternatively, you might be confused about the fact you only have two points.

When you close a path, how many points are generated? The correct answer is... ZERO! Your last point is connected to your first point via a stroke, but that doesn't cause you to suddenly have the 3 points minimum required to create a shape. You have a line that then doubles back perfectly on itself from start to finish to start. It has no area to actually fill with a color.

So, here's your issue: you have a straight shot method which opens a context, takes two points, does some stuff with it, then closes the context.

I think what you might want is some kind of method with a bit more logic to it, i.e, a loop, which might be able to handle more than two points.

Hope that helps! (Hope I'm not too tired and burned out to actually be helpful...)

RottenApple2
Aug 4, 2012, 08:25 AM
...

Alternatively, you might be confused about the fact you only have two points.

When you close a path, how many points are generated? The correct answer is... ZERO! Your last point is connected to your first point via a stroke, but that doesn't cause you to suddenly have the 3 points minimum required to create a shape. You have a line that then doubles back perfectly on itself from start to finish to start. It has no area to actually fill with a color.

So, here's your issue: you have a straight shot method which opens a context, takes two points, does some stuff with it, then closes the context.

I think what you might want is some kind of method with a bit more logic to it, i.e, a loop, which might be able to handle more than two points.


Thank you very much for the explanations. I get your point, but still I don't understand how to solve the problem. Let's say that with the code above I draw a concave path. This is made of hundreds of tiny points next to each other. Shouldn't I be able to close it and fill it?

dejo
Aug 4, 2012, 09:12 AM
Let's say that with the code above I draw a concave path. This is made of hundreds of tiny points next to each other. Shouldn't I be able to close it and fill it?

You should. But none of the code you've provided has shown such a path. Only xArtx has shown code with more than two points.

ArtOfWarfare
Aug 4, 2012, 09:12 AM
Thank you very much for the explanations. I get your point, but still I don't understand how to solve the problem. Let's say that with the code above I draw a concave path. This is made of hundreds of tiny points next to each other. Shouldn't I be able to close it and fill it?

Unless you're paraphrasing your code, no. As you've posted your code on the forums, it takes exactly two points, the current one and the prior one, draws a stroke between them, and draws another stroke between them (with the close shape command) and then tries to fill in the shape with no area that lies between those two lines that lie perfectly on top of each other.

If you run all of that code repeatedly, you'll just end up drawing several 2-sided shapes.

What you probably want will look more like this:
// iVars:
CGPoint points[100];
int pointCount = 0; //Set this in init I think... I've been spending the last several weeks learning Java where iVars can be initialized when you declare them... I don't think that's allowed in Obj-C, is it?

- (void)touchMovesWithEvent:(NSEvent*)event {
// Code that converts the received event into a CGPoint newPoint should be typed here...
points[pointCount++] = newPoint;
CGContextBeginPath(UIGraphicsGetCurrentContext());
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), points[0].x, points[0].y);
for (int i = 1; i < pointCount; i++)
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), points[i].x, points[i].y);
CGContextStrokePath(UIGraphicsGetCurrentContext());

CGContextSetRGBFillColor(UIGraphicsGetCurrentContext(), 1.0, 0.4, 1.0, 1.0);
CGContextFillPath(UIGraphicsGetCurrentContext());
}

I haven't so much as tested it in any way (I didn't even write out some of the starting code) but what it should do is keep a running list of all your points that it adds to each time it detects another touch. It then draws a shape, starting at the first point, then going to each of the in between points, and then returning to the first point with the close shape command. It then fills the shape.

Some short comings of using my code exactly as it is:
- There's no way to start a second shape.
- Is filling in each partially finished shape a good idea? Maybe, maybe not... it seems to me if you tried doing a shape with any concave parts that this would mess it up.
- It's limited to 100 points in the shape. Beyond that, I think it'll crash... simply increasing this number may not be the best idea either, as that will cause it to potentially set aside unnecessary memory. Maybe find some way to optimize the code so that, for example, if there's a 180 degree angle at a point, it'll get rid of the point.
- The code is a bit of a mess, kind of on purpose, kind of because I'm too lazy to so much as start up Xcode or a text editor... IE, the for loop has no braces (which makes it a bit odd / maybe difficult to read / it's just begging to cause issues later on when you add more to the loop), the point increment occurs inside of the array index... personally, I like doing things like that but I don't see others do it very often... it kind of obscures your code which is bad for collaboration, but good for the TopCoder challenge round...

So, yeah, I leave those as exercises for you, because I basically gave wrote the code that answers your question, I think.

RottenApple2
Aug 4, 2012, 09:29 AM
Many thanks for your help. Your explanations are very useful. As soon as I return in front of the Mac I'll try it and let you know!

dejo
Aug 4, 2012, 08:40 PM
int pointCount = 0; //Set this in init I think... I've been spending the last several weeks learning Java where iVars can be initialized when you declare them... I don't think that's allowed in Obj-C, is it?

It's not allowed but ints always default to 0. So:
int pointCount;
would have the same effect.

RottenApple2
Aug 5, 2012, 12:45 AM
Hi again. Unfortunately I do not think the approach you suggest could work in my case as the user should be able to draw pretty complex paths which could potentially be much longer than 100 points...

I am including the whole drawing code so that it is clearer for everyone where the issue is. By no means I expect you do to my "homeworks" but I would be grateful if you could at least point me towards the right direction. I have the feeling that I should implement the fill algorithm in touchesEnded but if this is the case I do not know how to make the path created in touchesMoved accessible in touchesEnded. Or maybe I have to seek a completely different solution. . Thanks in advance!

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
mouseSwiped = NO;
UITouch *touch = [touches anyObject];

lastPoint = [touch locationInView:self];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
mouseSwiped = YES;
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self];

FirstPoint = currentPoint;

UIGraphicsBeginImageContext(self.frame.size);

[drawImage.image drawInRect:CGRectMake(0, 0, drawImage.frame.size.width, drawImage.frame.size.height)];
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);

Int *BrushSize = 4;

CGContextSetLineWidth(UIGraphicsGetCurrentContext(), BrushSize);
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 1.0, 1.0, 1.0, 1.0);
CGContextBeginPath(UIGraphicsGetCurrentContext());
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
CGContextStrokePath(UIGraphicsGetCurrentContext());

drawImage.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

lastPoint = currentPoint;

mouseMoved++;

if (mouseMoved == 10) {
mouseMoved = 0;
}
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

UITouch *touch = [touches anyObject];
NSLog(@ "Finished drawing");
}

chown33
Aug 5, 2012, 02:17 AM
... if this is the case I do not know how to make the path created in touchesMoved accessible in touchesEnded.

Use an instance variable, which holds the path being constructed.

The touchesBegan method begins a new path. The touchesMoved and touchesEnded methods then accumulate (append) their points to the path being built. In addition, touchesEnded fills the just-completed path.

RottenApple2
Aug 5, 2012, 06:54 AM
Use an instance variable, which holds the path being constructed.

The touchesBegan method begins a new path. The touchesMoved and touchesEnded methods then accumulate (append) their points to the path being built. In addition, touchesEnded fills the just-completed path.

Almost there...I have created two instance variables, one for the context and one for the path that I will be creating and, later on, filling.

CGContextRef context3;
CGMutablePathRef _path;


I suppose that when I begin the path with CGContextBeginPath I should specify that the path I am creating is _path and belongs to context3. Does anyone know the right syntax for that?

In touchesMoved can I still call UIGraphicsEndImageContext(); or should I call it only in touchesEnded?

In touchesEnded what is the right syntax to retrieve _path so that I can then close it and fill it?

Thanks

chown33
Aug 5, 2012, 07:12 PM
I suppose that when I begin the path with CGContextBeginPath I should specify that the path I am creating is _path and belongs to context3. Does anyone know the right syntax for that?


The right syntax for what? Storing values into the instance variables? Calling the functions? You've already posted code for both. Your code that stores into an instance variable is here in red:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
mouseSwiped = NO;
UITouch *touch = [touches anyObject];

lastPoint = [touch locationInView:self];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
mouseSwiped = YES;
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self];

FirstPoint = currentPoint;

I'm assuming those are instance variables, but I'm just guessing. You haven't shown any @interface for the class that @implement's the methods, so they could be static variables.

The code that calls the functions is in your touchesMoved method. I assume you know what it does, since I assume you wrote it.


In touchesMoved can I still call UIGraphicsEndImageContext(); or should I call it only in touchesEnded?

Try it both ways, see what happens.

You're asking for specific details of what code to write, even though you already said, 'By no means I expect you do to my "homeworks" ...'. One way to learn what works is to try logical things to see which one works.

If you haven't looked at Apple's sample code, you should search it for examples that use the functions you intend to use.


In touchesEnded what is the right syntax to retrieve _path so that I can then close it and fill it?

If you don't know how to use instance variables, you should study them more. I.e. it's the same "do your homework" answer as above.