Painting

What are the painting building blocks?

  • Path describes a sequence of potentially disjoint movements on a plane. Paths tracks a current point as well as one or more subpaths (created via Path.moveTo). Subpaths may be closed (i.e., the first and last points are coincident), open (i.e., the first and last points are distinct), or self intersecting (i.e., movements within the path intersect). Paths incorporate lines, arcs, beziers, and more; each operation begins at the current point and, once complete, defines the new current point. The current point begins at the origin. Paths can be queried (via Path.contains), transformed (via Path.transform), and merged (via Path.combine, which accepts a PathOperation).

    • PathFillType defines the criteria determining whether a point is contained by the path. PathFillType.evenOdd casts a ray from the point outward, summing the number of edge crossings; an odd count indicates that the point is internal. PathFillType.nonZero considers the path’s directionality. Again casting a ray from the point outward, this method sums the number of clockwise and counterclockwise crossings. If the counts aren’t equal, the point is considered to be internal.

  • Canvas represents a graphical context supporting a number of drawing operations. These operations are captured by an associated PictureRecorder and, once finalized, transformed into a Picture. The Canvas is associated with a clip region (i.e., an area within which painting will be visible), and a current transform (i.e., a matrix to be applied to any drawing), both managed using a stack (i.e., clip regions and transforms can be pushed and popped as drawing proceeds). Any drawing outside of the canvas’s culling box (“cullRect”) may be discarded; by default, however, affected pixels are retained. Many operations accept a Paint parameter which describes how the drawing will be composited (e.g., the fill, stroke, blending, etc).

    • Canvas exposes a rich API for drawing. The majority of these operations are implemented within the engine.

      • Canvas.clipPath, Canvas.clipRect, etc., refine (i.e., reduce) the clip region. These operations compute the intersection of the current clip region and the provided geometry to define a new clip region. The clip region can be anti-aliased to provide a gradual blending.

      • Canvas.translate, Canvas.scale, Canvas.transform, etc., alter the current transformation matrix (i.e., by multiplying it by an additional transform). The former methods apply standard transformations, whereas the latter applies an arbitrary 4x4 matrix (specified in column-major order).

      • Canvas.drawRect, Canvas.drawLine, Canvas.drawPath, etc., perform fundamental drawing operations.

      • Canvas.drawImage, Canvas.drawAtlas, Canvas.drawPicture, etc., copy pixels from a rendered image or recorded picture into the current canvas.

      • Canvas.drawParagraph paints text into the canvas (via Paragraph._paint).

      • Canvas.drawVertices, Canvas.drawPoints, etc., describe solids using a collection of points. The former constructs triangles from a set of vertices (Vertices) and a vertex mode (VertexMode); this mode describes how vertices are composed into triangles (e.g., VertexMode.triangles specifies that each sequence of three points defines a new triangle). The resulting triangles are filled and composited using the provided Paint and BlendMode. The latter paints a set of points using a PointMode describing how the collection of points is to be interpreted (e.g., as defining line segments or disconnected points).

    • The save stack tracks the current transformation and clip region. New entries can be pushed (via Canvas.save or Canvas.saveLayer) and popped (via Canvas.restore). The number of items in this stack can also be queried (via Canvas.getSaveCount); there is always at least one item on the stack. All drawing operations are subject to the transform and clip at the top of the stack.

      • All drawing operations are performed sequentially (by default or when using Canvas.save/Canvas.restore). If the operation utilizes blending, it will be blended immediately after completing.

      • Canvas.saveLayer allows drawing operations to be grouped together and composited as a whole. Each individual operation will still be blended within the saved layer; however, once the layer is completed, the composite drawing will be blended as a whole using the provided Paint and bounds.

        • For example, an arbitrary drawing can be made consistently translucent by first painting it using an opaque fill, and then blending the resulting layer with the canvas. If instead each component of the drawing were individually blended, overlapping regions would appear darker.

        • This is particularly useful for antialiased clip regions (i.e., regions that aren’t pixel aligned). Without layers, any operations intersecting the clip would needed to be antialiased (i.e., blended with the background). If a subsequent operation intersects the clip at this same point, it would be blended with both the background and the previous operation; this produces visual artifacts (e.g., color bleed). If both operations were combined into a layer and composited as a whole, only the final operation would be blended.

        • Note that though this doesn’t introduce a new framework layer, it does cause the engine to switch to a new rendering target. This is fairly expensive as it flushes the GPU’s command buffer and requires data to be shuffled.

  • Paint describes how a drawing operation is to be applied to the canvas. In particular, it specifies a number of graphical parameters including the color to use when filling or stroking lines (Paint.color, Paint.colorFilter, Paint.shader), how new painting is to be blended with old painting (Paint.blendMode, Paint.isAntiAlias, Paint.maskFilter), and how edges are to be drawn (Paint.strokeWidth, Paint.strokeJoin, Paint.strokeCap). Fundamental to most drawing is whether the result is to be stroked (e.g., drawn as an outline) or filled; Paint.style exposes a PaintingStyle instance specifying the mode to be used.

    • If stroking, Paint.strokeWidth is measured in logical pixels orthogonal to the path being painted. A value of 0.0 will cause the line to be rendered as thin as possible (“hairline rendering”).

      • Any lines that are drawn will be capped at their endpoints according to a StrokeCap value (via Paint.strokeCap; StrokeCap.butt is the default and does not paint a cap). Caps extend the overall length of lines in proportion to the stroke width.

      • Discrete segments are joined according to a StrokeJoin value (via Paint.strokeJoin; StrokeJoin.miter is the default and extends the original line such that the next can be drawn directly from it). A limit may be specified to prevent the original line from extending too far (via Paint.strokeMiterLimit; once exceeded, the join reverts to StrokeJoin.bevel).

    • ColorFilter describes a function mapping from two input colors (e.g., the paint’s color and the destination’s color) to a final output color (e.g., the final composited color). If a ColorFilter is provided, it overrides both the paint color and shader; otherwise, the shader overrides the color.

    • MaskFilter applies a filter (e.g., a blur) to the drawing once it is complete but before it is composited. Currently, this is limited to a Gaussian blur.

  • Shader is a handle to a Skia shader utilized by the engine. Several are exposed within the framework, including Gradient and ImageShader. These are analogous, with the former generating pixels by smoothly blending colors and the latter reading them directly from an image. Both support tiling so that the original pixels can be extended beyond their bounds (a different TileMode may be specified in either direction); ImageShader also supports an arbitrary matrix to be applied to the source image.