Painting
原文地址:www.flutterinternals.org/rendering/p…
Painting 有哪些组成部分?
Path
描述了平面上一系列可能不相交的运动。Paths 跟踪当前点以及一个或者多个 subpaths(通过 Path.moveTo
创建)。Subpath 可以是封闭的(即,第一个点和最后一个点是重合的),开放的(即,第一个点和最后一个点是不同)或自相交的(即,路径相交)。Paths 包含直线、圆弧、贝塞尔曲线等;每个操作都从当前点开始,一旦完成,就定义新的当前点。当前点从原点开始。Paths 可以被查询(通过 Path.contains
),转换(通过 Path.transform
)和合并(通过 Path.combine
,接收 PathOperation
参数)。
PathFillType
定义了一个点是否被 path 包含的标准。PathFillType.evenOdd
从点向外投射一条射线,将与边缘交叉的数量加起来,奇数结果则表示点在内部。PathFillType.nonZero
考虑 path 的方向性。同样,从该点向外投射射线,这种方法将顺时针和逆时针交叉的次数相加。如果结果不相等,则认为这一点在内部。
Canvas
表示支持许多绘图操作的图形上下文。这些操作由关联的 PictureRecorder
捕获,一旦完成,就转换为 Pciture
。Canvas
与一个剪辑(clip)区域(即, 一个可以看见绘图的区域)和一个当前 transform(即,一个被应用到任何绘制的矩阵)相关联,两者都用一个栈来管理(随着绘制的进行,clip 区和 transform 都可以被 push 和 pop)。任何在 canvas 的裁剪区域(cullRect
)外的图形都有可能被丢弃;但是,默认情况下会保留受影响的像素。许多操作都接受一个 Paint
参数,该参数描述了如何合成图形(例如,fill、stroke、blending)。
Canvas
为绘图提供了丰富的 API。这些操作中的大多数都是在引擎中实现的。
Canvas.clipPath
, Canvas.clipRect
等优化(即缩小)剪辑区域。这些操作计算当前剪辑区域和所提供的几何图形的交集,以定义新的剪辑区域。该剪辑区域可以进行抗锯齿以提供渐变混合(gradual blending)。
Canvas.translate
, Canvas.scale
, Canvas.transform
等,更改当前的转换矩阵(即,将其乘以其他转换)。前一种方法应用标准变换,而后一种方法应用任意的 4 x 4 矩阵(以列为主顺序)。
Canvas.drawRect
, Canvas.drawLine
, Canvas.drawPath
等执行基本的绘制操作。
Canvas.drawImage
, Canvas.drawAtlas
, Canvas.drawPicture
等,将渲染图像或录制图片中的像素复制到当前 canvas 中。
Canvas.drawParagraph
将文本绘制到 canvas 中(通过 Paragraph._paint
)。
Canvas.drawVertices
, Canvas.drawPoints
等使用点的集合来描述实体。前者根据一组顶点(Vertices)和一个顶点模式(VertexMode
)构造三角形。此模式描述了如何将顶点组成三角形(例如, VertexModel.triangles
指定三个点各自的序列来定义一个新的三角形)。由此产生的三角形是通过给定的 Paint
和 BlendModel
填充并合成所得到的。后者使用 PointMode
绘制一组点,描述有特定解释的点集合(例如,定义线段或不连续的点)。
- 缓存栈跟踪当前的 transformation 和剪辑区域。可以通过
Canvas.save
或者 Canvas.saveLayer
push 新的输入,通过 Canvas.restore
进行 pop。栈中 items 的数量也可以被查询(通过 Canvas.getSaveCount
);栈上至少会有一个 item。所有绘图操作都需要在栈顶进行转换和裁剪。
- 所有绘图操作均按顺序执行(默认情况下或使用
Canvas.save
/Canvas.restore
时)。如果操作使用混合(blending),它将在完成后立即混合。
Canvas.saveLayer
允许将绘图操作分组并作为一个整体进行合并。每个单独的操作仍将在 saved layer 中混合;但是,一旦 layer 完成这个操作,将使用提供的 Paint
和 bounds 将合成图形作为一个整体进行混合。
- 例如,可以通过以下方法使任意图形始终保持半透明状态:先使用不透明填充(opaque fill)绘制,然后将生成的 layer 与 canvas 进行混合,使其始终保持半透明。相反,如果将图形的每个部分分别进行混合,则重叠区域将会显得更暗。
- 这对于抗锯齿剪辑区域(即未像素对其的区域)特别有用。如果没有 layers,任何与剪裁(clip)相交的操作都需要进行抗锯齿(即与背景混合)。如果后续操作在同一点与剪裁(clip)相交,则它与背景还有前一个操作进行混合;这会造成视觉假象(例如,颜色失真)。如果这两个操作被合并到一个 layer 中并组合成一个整体,那么只有最后一个操作会被混合。
- 请注意,尽管这没有引入新的 framework layer,但确实会导致 engine 切换到新的渲染目标。这是相当昂贵的,因为它会刷新 GPU 的命令缓冲区并重新整理数据。(指的是离屏渲染吧)
Paint
描述了如何将绘制操作应用于 canvas。特别的,它指定了许多图形参数,包括 fill 或 stroke line 时使用的颜色(Paint.color
, Paint.colorFilter
, Paint.shader
),如何讲新旧绘制融合(Paint.blendMode
, Paint.isAntiAlias
, Paint.maskFilter
)以及绘制边缘的方式(Paint.strokeWidth
, Paint.strokeJoin
, Paint.strokeCap
)。大多数绘图的基础是要描边(stroke)(例如,绘制轮廓)还是填充(fill);Paint.style
公开了一个 PaintingStyle
实例,该实例指定了要使用的模式。
Paint.strokeWidth
为 line 的宽度,值 0.0 将使线尽可能细(“细线渲染”)。
- 绘制的任何 line 都将根据
StrokeCap
值在其端点设置笔触(通过Paint.strokeCap
;StrokeCap.butt
是默认值,并且不绘制笔触)。笔帽与笔划宽度成比例地延长线的总长度。
- 不相连的 line 根据 StrokeJoin 值(通过
Paint.strokeJoin
;StrokeJoin.miter
是默认值,它延伸原有的 line ,这样下一条 line 就可以直接从它开始绘制)来进行连接。可以指定一个限制以防止原始的 line 延伸得太远(通过 Paint.strokeMiterLimit
;超出该限制后,join 将恢复为 StrokeJoin.bevel
)
ColorFilter
描述了一种函数,它从两种输入颜色(例如,绘图颜色和目标颜色)映射到最终的输出颜色(例如,最终的合成颜色)。如果给定 ColorFilter
,它将覆盖绘图颜色和着色器;否则,着色器会覆盖颜色。
MaskFilter
会在完成绘制之后,合成之前将滤镜(例如,blur)应用于图形上。当前,仅限于高斯模糊。
Shader
是 engine 使用的 Skia 着色器的句柄。在 framework 中有包含多个 shader,例如 Gradient
和 ImageShader
。他们都是类似的,前者通过平滑地混合颜色来生产像素,而后者直接从图像中读取它们。两者都支持平铺,以便原始像素可以扩展超出其边界(不同的 TileMode
可以指定在任何方向);ImageShader
还支持应用于源图像的任意矩阵。