[翻译] Flutter 内幕之 Painting

424 阅读6分钟

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 捕获,一旦完成,就转换为 PcitureCanvas 与一个剪辑(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 指定三个点各自的序列来定义一个新的三角形)。由此产生的三角形是通过给定的 PaintBlendModel 填充并合成所得到的。后者使用 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.strokeCapStrokeCap.butt是默认值,并且不绘制笔触)。笔帽与笔划宽度成比例地延长线的总长度。
      • 不相连的 line 根据 StrokeJoin 值(通过 Paint.strokeJoinStrokeJoin.miter 是默认值,它延伸原有的 line ,这样下一条 line 就可以直接从它开始绘制)来进行连接。可以指定一个限制以防止原始的 line 延伸得太远(通过 Paint.strokeMiterLimit;超出该限制后,join 将恢复为 StrokeJoin.bevel
    • ColorFilter 描述了一种函数,它从两种输入颜色(例如,绘图颜色和目标颜色)映射到最终的输出颜色(例如,最终的合成颜色)。如果给定 ColorFilter ,它将覆盖绘图颜色和着色器;否则,着色器会覆盖颜色。
    • MaskFilter 会在完成绘制之后,合成之前将滤镜(例如,blur)应用于图形上。当前,仅限于高斯模糊。
  • Shader 是 engine 使用的 Skia 着色器的句柄。在 framework 中有包含多个 shader,例如 GradientImageShader。他们都是类似的,前者通过平滑地混合颜色来生产像素,而后者直接从图像中读取它们。两者都支持平铺,以便原始像素可以扩展超出其边界(不同的 TileMode 可以指定在任何方向);ImageShader 还支持应用于源图像的任意矩阵。