How to fill the area below a path in Quartz 2D? - ios

I am using Quartz 2D in a UIView object to draw several curves, like this example:
This is the code I have right now (I have omitted the calculation of control points and other stuff):
for (int i = 1; i < points.count; ++i) {
CGContextMoveToPoint(context, previousXValue, previousYValue);
CGContextAddCurveToPoint(context,
firstControlPointXValue, firstControlPointYValue,
secondControlPointXValue, secondControlPointYValue,
xValue, yValue
);
// CGContextFillPath(context);
CGContextStrokePath(context);
}
Now I would like to fill the area below the curve, but if I use CGContextFillPath the result is the following:
which makes sense, because according to Quartz 2D documentation:
When you fill the current path, Quartz acts as if each subpath contained in the path were closed. It then uses these closed subpaths
and calculates the pixels to fill.
Then I tried to move the path to the lower right corner and close the path, but the fill method doesn't have any effect:
CGContextMoveToPoint(context, rect.size.width, rect.size.height);
CGContextClosePath(context);
CGContextFillPath(context);
How could I fill the whole area below the curve and not just the subarea closed in every subpath?
EDIT:
I have found a temporary solution: drawing a shape using the curve and two vertical lines on each subpath:
for (int i = 1; i < points.count; ++i) {
// Draw the curve
CGContextMoveToPoint(context, previousXValue, previousYValue);
CGContextAddCurveToPoint(context,
firstControlPointXValue, firstControlPointYValue,
secondControlPointXValue, secondControlPointYValue,
xValue, yValue
);
CGContextStrokePath(context);
// Draw a shape using curve's initial and final points
CGContextMoveToPoint(context, previousXValue, rect.size.height);
CGContextAddLineToPoint(context, previousXValue, previousYValue);
CGContextAddCurveToPoint(context,
firstControlPointXValue, firstControlPointYValue,
secondControlPointXValue, secondControlPointYValue,
xValue, yValue
);
CGContextAddLineToPoint(context, xValue, rect.size.height);
CGContextFillPath(context);
}
I don't know if this is overkill, so improvements are also welcomed. Besides, I'm getting this result:
Note that vertical lines appear as a result of drawing the subareas next to each other. How can that be avoided?

The idea is right, but you should not fill the individual curves, but rather, create a single path and then fill that. So, start with CGContextMoveToPoint to the lower left corner, CGContextAddLineToPoint to your first point, do the CGContextAddCurveToPoint for all of the curves, and at the end, do a CGContextAddLineToPoint to the lower right corner (and you might as well do a CGContextClosePath for good measure).
This is a simplified example, where I just have an array of points, but it illustrates the idea:
- (void)drawRect:(CGRect)rect
{
if (!self.points)
[self configurePoints];
DataPoint *point;
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorRef color = [[UIColor redColor] CGColor];
CGContextSetStrokeColorWithColor(context, color);
CGContextSetFillColorWithColor(context, color);
point = self.points[0];
CGContextMoveToPoint(context, point.x, self.bounds.size.height);
CGContextAddLineToPoint(context, point.x, point.y);
for (point in self.points)
{
// you'd do your CGContextAddCurveToPoint here; I'm just adding lines
CGContextAddLineToPoint(context, point.x, point.y);
}
point = [self.points lastObject];
CGContextAddLineToPoint(context, point.x, self.bounds.size.height);
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFillStroke);
}

Related

iOS - 2D line drawing on negative coordinates

I am developing an app in which user can draw lines. The desktop version of the same app is able to draw lines on negative coordinates and I would like to have same capability in iOS app too.
What I've tried so far:-
I have a UIViewController inside which I have overridden - (void)drawRect:(CGRect)rect and drawn the line. Here is the code I am using:-
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, self.bounds);
CGPoint startPoint = CGPointMake(self.startHandle.frame.origin.x + self.startHandle.frame.size.width/2, self.startHandle.frame.origin.y + self.startHandle.frame.size.height/2);
CGPoint endPoint = CGPointMake(self.endHandle.frame.origin.x + self.endHandle.frame.size.width/2, self.endHandle.frame.origin.y + self.endHandle.frame.size.height/2);
CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
CGContextSetLineWidth(context, 2.0f);
CGContextMoveToPoint(context, startPoint.x, startPoint.y ); //start at this point
CGContextAddLineToPoint(context, endPoint.x, endPoint.y); //draw to this point
It works very well until I have startPoint.x as a negative value. With negative value, I don't see the portion of line that is behind 0,0 coordinates. Any help or information is appreciated.
Following on from what #matt said (he has since deleted his answer, shame as it was still useful), you simply want to apply a transform to the context in order to draw in a standard cartesian coordinate system, where (0,0) is the center of the screen.
The context provided to you from within drawRect has its origin at the top left of the screen, with the positive y-direction going down the screen.
So first, we'll want to first flip the y-axis so that the positive y-direction goes up the screen.
We can do this by using CGContextScaleCTM(context, 1, -1). This will reflect the y-axis of the context. Therefore, this reflected context will have a y-axis where the positive direction goes up the screen.
Next, we want to translate the context to the correct position, so that (0,0) corresponds to the center of the screen. We should therefore shift the y-axis down by half of the height (so that 0 on the y-axis is now at the center), and the x-axis to the right by half of the width.
We can do this by using CGContextTranslateCTM(context, width*0.5, -height*0.5), where width and height are the width and height of your view respectively.
Now your context coordinates will look like this:
You could implement this into your drawRect like so:
-(void) drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGFloat width = self.bounds.size.width;
CGFloat height = self.bounds.size.height;
// scale and translate to the standard cartesian coordinate system where the (0,0) is the center of the screen.
CGContextScaleCTM(ctx, 1, -1);
CGContextTranslateCTM(ctx, width*0.5, -height*0.5);
CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
CGContextSetLineWidth(ctx, 2.0);
// add y-axis
CGContextMoveToPoint(ctx, 0, -height*0.5);
CGContextAddLineToPoint(ctx, 0, height*0.5);
// add x-axis
CGContextMoveToPoint(ctx, -width*0.5, 0);
CGContextAddLineToPoint(ctx, width*0.5, 0);
// stroke axis
CGContextStrokePath(ctx);
// define start and end points
CGPoint startPoint = CGPointMake(-100, 100);
CGPoint endPoint = CGPointMake(100, -200);
CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor);
CGContextSetLineWidth(ctx, 5.0);
CGContextMoveToPoint(ctx, startPoint.x, startPoint.y );
CGContextAddLineToPoint(ctx, endPoint.x, endPoint.y);
CGContextStrokePath(ctx);
}
This will give the following output:
I added the red lines to represent the y and x axis, in order to clarify what we've done here.
CGContextClearRect(context, self.bounds);
You can draw out of this bounds,please make a larger rect.

How to draw lines with different thicknesses in CoreGraphic in iOS?

I draw grids with CoreGraphic in iOS (as shown below). But for some lines, I'd like to change line thickness. How can I change line thickness in CoreGraphic?
//Get the CGContext from this view
CGContextRef context = UIGraphicsGetCurrentContext();
//Set the stroke (pen) color
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
//Set the width of the pen mark
CGContextSetLineWidth(context, 1.0);
//Draw vertical lines
float max = totalVerticalLines*numskipPixels_h+orgx;
for(float i = orgx; i <= max; i+=numskipPixels_h){
CGContextMoveToPoint(context, i, orgy);
CGContextAddLineToPoint(context, i, orgy - verLineLength);
}
//Draw vertical lines
float min = orgy - totalHorLines*numskipPixels_v;
for(float i = orgy; i > min; i-=numskipPixels_v){
CGContextMoveToPoint(context, orgx, i);
CGContextAddLineToPoint(context, orgx+horLineLength, i);
}
//Draw it
CGContextStrokePath(context);
Create different paths for the different thicknesses, change the thickness between calls to CGContextStrokePath.
Like so:
float min = orgy - totalHorLines*numskipPixels_v;
for(float i = orgy; i > min; i-=numskipPixels_v){
CGContextMoveToPoint(context, orgx, i);
CGContextAddLineToPoint(context, orgx+horLineLength, i);
}
//Draw it
CGContextStrokePath(context);
//Set the width of the pen mark to a different value
CGContextSetLineWidth(context, 5.0);
//start a new path
CGContextBeginPath(context);
CGContextMoveToPoint(context, point1x, point1y);
CGContextAddLineToPoint(context, point2x, point2y);
//Draw the new path (uses the new line thickness)
CGContextStrokePath(context);

Core Graphics drawing a square result in rectangle

I am trying to draw a square in my context, but it results in a rectangle with a short and longer side. (in my ipad retina simulator)
This is part of the drawRect method of my view object:
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat minx = 0;
CGFloat maxx = 45 - 1;
CGFloat miny = 0;
CGFloat maxy = 45 - 1;
CGContextSetLineWidth(context, 1.0);
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextMoveToPoint(context, minx, maxy);
CGContextAddLineToPoint(context, minx, miny);
CGContextAddLineToPoint(context, maxx, miny);
CGContextStrokePath(context);
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextMoveToPoint(context, maxx, miny);
CGContextAddLineToPoint(context, maxx, maxy);
CGContextAddLineToPoint(context, minx, maxy);
CGContextStrokePath(context);
What am I doing wrong? Do I need to set an aspect ratio or something?
Edit:
I found out that
CGContextScaleCTM(context, 1, 0.75f);
makes the result square again, but the end result is not spot on.
Does anybody know why I need to correct the vertical scale?
Found it....
Turns out I was modifying the views rect elsewhere.
Disabled that, and now my squares are square again.
Thanks for the hint Ismael !!

Trouble drawing CoreGraphics lines in drawRect() method

I'm working on the Stanford CS193p course and am trying to draw a graph. Drawing the axes works, but I can't draw the graph itself. I'm getting the message
CGContextAddLineToPoint: no current point.
when I try to draw. Here's the code.
- (void)drawRect:(CGRect)rect
{
NSLog(#"Drawing Graph View");
CGPoint origin = CGPointMake(20, 350);
CGRect rect2 = CGRectMake(origin.x, origin.y, 250, -250);
[AxesDrawer drawAxesInRect:rect2 originAtPoint:origin scale:[self scale]];
CGContextRef context = UIGraphicsGetCurrentContext();
UIGraphicsPushContext(context);
CGContextSetLineWidth(context, 2);
[[UIColor redColor] setStroke];
CGContextMoveToPoint(context, origin.x, origin.y);
for (CGFloat i=0; i<250; i++) {
CGFloat yChange = [self.dataSource deltaY:self];
CGContextAddLineToPoint(context, origin.x+i, origin.y-yChange);
CGContextStrokePath(context);
}
UIGraphicsPopContext();
}
You have to place CGContextStrokePath(context); outside the for loop. Otherwise it will create a fresh path on every run through the loop and that fails.
Do you not have a problem with:
CGRectMake(origin.x, origin.y, 250, -250)
You specify a negative height!

Resize UIView to fit a CGPath

I have a UIView subclass on which the user can add a random CGPath. The CGPath is added by processing UIPanGestures.
I would like to resize the UIView to the minimal rect possible that contains the CGPath. In my UIView subclass, I have overridden sizeThatFits to return the minimal size as such:
- (CGSize) sizeThatFits:(CGSize)size {
CGRect box = CGPathGetBoundingBox(sigPath);
return box.size;
}
This works as expected and the UIView is resized to the value returned, but the CGPath is also "resized" proportionally resulting in a different path that what the user had originally drawn. As an example, this is the view with a path as drawn by the user:
And this is the view with the path after resizing:
How can I resize my UIView and not "resize" the path?
Use the CGPathGetBoundingBox. From Apple documentation:
Returns the bounding box containing all points in a graphics path. The
bounding box is the smallest rectangle completely enclosing all points
in the path, including control points for Bézier and quadratic curves.
Here a small proof-of-concept drawRect methods. Hope it helps you!
- (void)drawRect:(CGRect)rect {
//Get the CGContext from this view
CGContextRef context = UIGraphicsGetCurrentContext();
//Clear context rect
CGContextClearRect(context, rect);
//Set the stroke (pen) color
CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
//Set the width of the pen mark
CGContextSetLineWidth(context, 1.0);
CGPoint startPoint = CGPointMake(50, 50);
CGPoint arrowPoint = CGPointMake(60, 110);
//Start at this point
CGContextMoveToPoint(context, startPoint.x, startPoint.y);
CGContextAddLineToPoint(context, startPoint.x+100, startPoint.y);
CGContextAddLineToPoint(context, startPoint.x+100, startPoint.y+90);
CGContextAddLineToPoint(context, startPoint.x+50, startPoint.y+90);
CGContextAddLineToPoint(context, arrowPoint.x, arrowPoint.y);
CGContextAddLineToPoint(context, startPoint.x+40, startPoint.y+90);
CGContextAddLineToPoint(context, startPoint.x, startPoint.y+90);
CGContextAddLineToPoint(context, startPoint.x, startPoint.y);
//Draw it
//CGContextStrokePath(context);
CGPathRef aPathRef = CGContextCopyPath(context);
// Close the path
CGContextClosePath(context);
CGRect boundingBox = CGPathGetBoundingBox(aPathRef);
NSLog(#"your minimal enclosing rect: %.2f %.2f %.2f %.2f", boundingBox.origin.x, boundingBox.origin.y, boundingBox.size.width, boundingBox.size.height);
}

Resources