Core Graphics (Quartz 2D) | Actual Warfare Explanation: Path with Demo

Keywords: Fragment Programming github

Many children's shoes know Core Graphics, but they have not heard of Quartz 2D. The relationship between them can be seen in the previous article (and so on, in fact, the difference between potatoes and potatoes). Searching for Core Graphics in Duniang is mostly the operation of CGPathRef, without detailed description and explanation of API, which is not easy to understand. Fang naturally did not explain.

This paper is based on the basic concept of drawing Path and API, and combines with the actual combat to complete the graphics drawing.

This article is a summary of official documents and practical warfare.

Paths (Path) Principle

A path defines one or more shapes or subpaths. A subpath may consist of a straight line, a curve, or both. It can be either open or closed.

Creating and drawing paths are separate tasks. 1. Creating a path first. 2. When we need to render a path, we use Quartz to draw it.

Create and draw paths

Subpaths are composed of points, lines, arcs and curves. Quartz also provides functions for drawing rectangles and ellipses. Points define the starting point and end point of drawing. Of course, some pictures can be drawn only by selecting the origin, such as arcs, ellipses and so on.

Note: Complex graphics are mostly composed of n sub-paths.

For path rendering, Quartz provides "two" sets of APIs (which allow me to understand), CGPath and CGContext, respectively.

  • CGContext is a direct operation context, drawing subpaths in the context, closing the current subpath by CGContextClosePath(context), and then continuing to draw the next path, then how to continue to modify or draw the previous path is unlikely to be achieved. Therefore..
  • When CGPath comes out, it operates on the CGPathRef object. Every time this sub-path is drawn, CGContextAddPath (context, curve Path) is added to the current context, and then the next sub-path is drawn, at which time all the sub-paths can be saved for reuse.

Therefore, the author suggests that CGPath be used to draw complex composite graphics and CGContext to draw simple graphics.

0. context

//Get the context, UIKit coordinate system.
CGContextRef context = UIGraphicsGetCurrentContext();
..
/* Begin a new path. The old path is discarded. */
/* So it's important to note that the sub-paths are not created here, if CGContext is used to plot the paths,
 * Calling CGContextBeginPath(context) again when you start drawing the next subpath overrides
 * The last path, so this function can be called only once*/
CGContextBeginPath(context);

1. point Point

  CGContextMoveToPoint(context, p1.x, p1.y);
  //perhaps
  CGPathMoveToPoint(path, nil, fromPoint.x, fromPoint.y);

2. line Lines

  CGContextMoveToPoint(context, p1.x, p1.y);
  CGContextAddLineToPoint(context, x, y);
  CGContextClosePath(context);
  //perhaps
  CGMutablePathRef path = CGPathCreateMutable();
  CGPathMoveToPoint(path, nil, fromPoint.x, fromPoint.y);
  CGPathAddLineToPoint(path, nil, toPoint.x, toPoint.y);
  CGPathCloseSubpath(path);
  CGContextAddPath(context, path);
  CGPathRelease(path);

3. circular arc Arcs

  CGContextAddArc(context, center.x, center.y, radius, radians(0.0f), radians(360.0f), 0);
  CGContextClosePath(context);
  //perhaps
  CGMutablePathRef path = CGPathCreateMutable();
  CGPathAddArc(path, nil, center.x, center.y, 50, radians(-90.0f), radians((- 180.0f)), YES);
  CGPathCloseSubpath(path);
  CGContextAddPath(context, path);
  CGPathRelease(path);

  //Other drawing methods
  CGContextAddArcToPoint;
  CGContextAddQuadCurveToPoint;

4. curve Curves

  CGContextAddCurveToPoint(context, refPoint1.x, refPoint1.y, refPoint2.x, refPoint2.y, toPoint.x, toPoint.y);

  CGContextMoveToPoint(context, fromPoint.x, fromPoint.y);
  CGContextAddQuadCurveToPoint(context, refPoint1.x, refPoint1.y, toPoint.x, toPoint.y);
  CGContextClosePath(context);

  //perhaps
  CGMutablePathRef path = CGPathCreateMutable();
  CGPathMoveToPoint(path, nil, fromPoint.x, fromPoint.y);
  CGPathAddCurveToPoint(path, nil,refPoint1.x, refPoint1.y, refPoint2.x, refPoint2.y, toPoint.x, toPoint.y);
  CGPathCloseSubpath(path);
  CGContextAddPath(context, path);
  CGPathRelease(path);

5. rectangle Rectangle

  CGContextAddRect(context, rect);
  CGContextClosePath(context);

  //perhaps
  CGPathAddRect(path, nil, rect);
  CGPathCloseSubpath(path);
  CGContextAddPath(context, path);
  CGPathRelease(path);

  //Add multiple rectangles together
   CGContextAddRects(context, rects, 3);

6. Ellipses

  CGContextAddEllipseInRect(context, rect);
  CGContextClosePath(context);

  //perhaps
  CGPathAddEllipseInRect(path, nil, rect);
  CGPathCloseSubpath(path);
  CGContextAddPath(context, path);
  CGPathRelease(path);

Summary of Drawing Path Rules

  1. Before drawing the path, call the function CGContextBeginPath.
  2. Lines, arcs and curves begin at the current point. An empty path has no current point; we must use CGContext- MoveToPoint to set the starting point of the first subpath, or call a convenience function to implicitly complete the task.
  3. To close the current subpath, call the function CGContextClosePath. Then the path will start a new subpath, even if we do not show setting a new starting point.
  4. When drawing an arc, Quartz draws a straight line between the current point and the starting point of the arc.
  5. The Quartz program that adds ellipses and rectangles adds a new closed subpath to the path.
  6. We must call the drawing function to fill or edge a path, because the path is not drawn when it is created.

Deep characterization (edge drawing, filling)

Attributes Affecting Edge Stroke

The following table shows some attributes of context, and modifying these attributes will affect subsequent edge-tracing operations.

Parameter Function to set parameter value
Line width CGContextSetLineWidth
Line join CGContextSetLineJoin
Line cap CGContextSetLineCap
Miter limit CGContextSetMiterLimit
Line dash pattern CGContextSetLineDash
Stroke color space CGContextSetStrokeColorSpace
Stroke color CGContextSetStrokeColorCG...
Stroke pattern CGContextSetStrokePattern

Stroke function

Quartz provides the functions in the following table to trace the current path.

Function Description
CGContextStrokePath Edge the current path
CGContextStrokeRect Note: Create a rectangular subpath and edge it
CGContextStrokeRectWithWidth Note: Create a rectangular subpath, set the stroke width, and trace it.
CGContextStrokeEllipseInRect Note: Create an ellipse subpath and trace it
CGContextStrokeLineSegments Draw edges on a set of straight lines
CGContextDrawPath Note: To draw the current subpath in the context, you need to specify CGPathDrawingMode (see table below)

CGPathDrawingMode has the following types:

  • kCGPathFill, fill only
  • kCGPathEOFill, parity filling
  • kCGPathStroke, only edge tracing
  • KCGPathFill Stroke, both filling and edge-tracing
  • kCGPathEOFillStroke, Parity Stroke

Fill path

Quartz considers every subpath included in the path as closed. Then, these closed paths are used and filled pixels are calculated. Paths such as ellipses and rectangles have distinct regions. But if the path is composed of several overlapping parts or the path contains multiple sub-paths, we have two rules to define the filling area.

  • Non-zero winding number rule:
    To determine whether a point needs to be drawn, we begin by drawing a straight line across the drawing boundary. Starting from 0, the count is added to 1 for each path fragment passing through a straight line from left to right, and minus 1 for each path fragment passing through a straight line from right to left. If the result is 0, the point is not drawn, otherwise it is drawn. The direction of path fragment drawing will affect the result.

  • Even-odd rule: In order to determine whether a point is drawn, we start from that point and draw a straight line through the drawing boundary. Calculate the number of path fragments passing through the line. If it is odd, the point is drawn, and if it is even, the point is not drawn. The direction of path fragment drawing does not affect the result.

To sum up: the default rule is non-zero winding number. The filling rule of non-zero winding number is related to the direction of drawing, while the even-odd rule is independent of the direction.
See: Official documents.

Quartz provides the functions in the following table to fill in the current path.

Function Description
CGContextEOFillPath Fill the current path with parity rules
CGContextFillPath Filling current path with non-zero winding rule
CGContextFillRect Note: Create a rectangular subpath and fill it.
CGContextFillRects Note: Create a set of rectangular paths and fill them in
CGContextFillEllipseInRect Note: Create an elliptical path and fill it in
CGContextDrawPath Note: To draw the current subpath in the context, you need to specify CGPathDrawingMode

Set up mixed mode

Mixed mode specifies how Quartz draws the drawing to the background. Quartz defaults to normal blend mode, which uses the following formula to calculate how foreground and background drawings are blended:

result = (alpha * foreground) + (1 - alpha) *background

Call CGContextSetBlendMode(context, kCGBlendModeNormal) to set round mode.

You can be there. Quartz 2D Programming Guide - Chapter Paths See the concrete effect of mixed mode.

Note: CGContext save and restore functions

When constructing complex paths, which are composed of n sub-paths and each sub-path has different styles, we need to make different modifications to the context attributes of the current context. In order to ensure that the context parameter values of the next sub-path remain the original values (i.e., not inherit the parameter values of the previous sub-path), we need to be in each sub-path. Call CGContextSaveGState(context) to save the current context state before drawing a subpath, and restore the settings after drawing the current subpath, that is, call CGContextRestoreGState(context).

Paths in action, drawing graphics.

In actual combat, we draw a graph, which is composed of several sub-paths with different shapes.

Implementation of drawRect

In order to use Core Graphics to plot, the easiest way is to customize a class that inherits from UIView and override the drawRect method of a subclass. Drawing graphics in this method.
Core Graphics has to have a canvas to paint things on it. In the drawRect method, we can get the Context of the current stack top directly.

- (void)drawRect:(CGRect)rect {

    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextBeginPath(context);

    //1. line drawing
    [self beginLinesDemo:context];

    //2. arc drawing
    [self beginArcsDemo:context];

    //3. curve drawing
    [self beginCurvesDemo:context];

    //4. ellipse plotting
    [self beginEllipsesDemo:context];

    //5. rectangle drawing
    [self beginRectangleDemo:context];

    //6. The Attributes Affecting the Stroke Edge
//    CGContextSet...
    //7. Functions of Path Edge
//    CGContextStroke...
    //8. fill path
//    CGContextFill...
}

Line Drawing Pentagon

This article focuses not on how to calculate the Pentagon, but on how to use CG. So if you are interested in computing, I think Du Niang will give you the answer, go on.

- (void)beginLinesDemo:(CGContextRef)context
{
    //1.Line line
    CGContextSaveGState(context);
    cg_drawPentagramByLine(context, CGPointMake(150, 150), 100);

    //Setting stroke attributes
    CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
    CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
    CGContextSetLineWidth(context, 2);

    CGContextDrawPath(context, kCGPathFillStroke);
    CGContextRestoreGState(context);
}

void cg_drawPentagramByLine(CGContextRef context, CGPoint center,CGFloat radius)
{
    CGPoint p1 = CGPointMake(center.x, center.y - radius);
    CGContextMoveToPoint(context, p1.x, p1.y);
    CGFloat angle=4 * M_PI / 5.0;

    for (int i=1; i<=5; i++)
    {
        CGFloat x = center.x -sinf(i*angle)*radius;
        CGFloat y = center.y -cosf(i*angle)*radius;
        CGContextAddLineToPoint(context, x, y);
    }

    CGContextClosePath(context);
}

The drawing is as follows:


Pentagram.png

Arc Link Pentagon Vertex

- (void)beginArcsDemo:(CGContextRef)context
{
  //2.Arcs curve
  CGContextSaveGState(context);
  cd_drawCircleByArc(context, CGPointMake(150, 150), 102);

  //Setting stroke attributes
  CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
  CGContextSetLineWidth(context, 2);

  CGContextDrawPath(context, kCGPathStroke);
  CGContextRestoreGState(context);
}

void cd_drawCircleByArc(CGContextRef context, CGPoint center, CGFloat radius)
{
    CGContextAddArc(context, center.x, center.y, radius, radians(0.0f), radians(360.0f), 0);

    //The following two uses can be studied on their own
//    CGContextAddArcToPoint;
//    CGContextAddQuadCurveToPoint;
}

The drawing is as follows:


PentagramCircle.png

Curve Drawing Windmill

- (void)beginCurvesDemo:(CGContextRef)context
{
    //3.Curve curve
    CGContextSaveGState(context);
    cd_drawPinwheelByCurve(context,  CGPointMake(300, 100),  CGPointMake(0, 200),  CGPointMake(0, 0),  CGPointMake(300, 300));

    CGContextSetRGBFillColor(context, 246 / 255.0f, 122 / 255.0f , 180 / 255.0f, 0.6);
    CGContextSetStrokeColorWithColor(context, [UIColor orangeColor].CGColor);
    CGContextDrawPath(context, kCGPathFillStroke);

    CGMutablePathRef curvePath = cd_drawPinwheelCurveByPath(CGPointMake(300, 200), CGPointMake(0, 100), CGPointMake(300, 0), CGPointMake(0, 300));
    CGContextAddPath(context, curvePath);
    CGPathRelease(curvePath);

    CGContextDrawPath(context, kCGPathFillStroke);
    CGContextRestoreGState(context);
}
void cd_drawPinwheelByCurve(CGContextRef context,CGPoint refPoint1, CGPoint refPoint2, CGPoint fromPoint, CGPoint toPoint)
{
    CGContextMoveToPoint(context, fromPoint.x, fromPoint.y);
    CGContextAddCurveToPoint(context, refPoint1.x, refPoint1.y, refPoint2.x, refPoint2.y, toPoint.x, toPoint.y);
    CGContextAddQuadCurveToPoint(context, refPoint1.x, refPoint1.y, toPoint.x, toPoint.y);
}

CGMutablePathRef cd_drawPinwheelCurveByPath(CGPoint refPoint1, CGPoint refPoint2, CGPoint fromPoint, CGPoint toPoint)
{
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, nil, fromPoint.x, fromPoint.y);
    CGPathAddCurveToPoint(path, nil,refPoint1.x, refPoint1.y, refPoint2.x, refPoint2.y, toPoint.x, toPoint.y);
    CGPathCloseSubpath(path);
    //Other addCurve s create curve methods with context.
    return path;
}

The drawing is as follows:


Pinwheel.png

So far, so much has been written about Paths trunk in Quartz, as well as the edge and corner knowledge points and API s, which are not described in detail here.

Demo address.

Detailed view Official document .
This is Core Graphic's second article, the actual combat details, and then CG beautification, if you are interested, to a compliment and attention. We'll see you next time.

Posted by tolutlou on Mon, 10 Dec 2018 17:03:05 -0800