着色与效果
一、Shader:颜色从哪来
Paint 有一个 color 属性,设置后整个绘制区域变成那个颜色——这在画单色图形时够用,但现实需求远不止于此。渐变、图像纹理、两种颜色叠加……靠一个 color 字段做不到。
解决这个问题的思路是把颜色决策从 Paint 里抽离出来,变成一个独立对象,可以随时换掉。这就是 Shader 的角色:接管 Paint 的颜色输出。
设置了 Shader 之后,Paint.color 不再起作用,颜色完全由 Shader 决定。但有一个例外:Paint.alpha 仍然有效,它作为全局调光器叠在 Shader 输出之上:
最终颜色 = shader_output × paint.alpha
这个设计让颜色逻辑和绘制逻辑彻底分开——同一个 Shader 可以被任意多个 Paint 复用,也可以在运行时动态切换。
五种 Shader 类型
纯色 Shader
auto shader = Shader::MakeColorShader(Color::FromRGBA(66, 133, 244, 255));
paint.setShader(shader);
单独用意义不大,常见于 MakeBlend 组合中。
图像 Shader
auto shader = Shader::MakeImageShader(image,
TileMode::Repeat, TileMode::Repeat, SamplingOptions{});
paint.setShader(shader);
canvas->drawRect(Rect::MakeWH(400, 300), paint);
图像纹理平铺,TileMode 控制超出边界时的行为(下面专门讲)。
渐变 Shader
四种渐变,分别对应不同的空间分布方式:
| 类型 | 工厂方法 | 形状 |
|---|---|---|
| 线性渐变 | MakeLinearGradient(start, end, colors, positions) | 沿直线 |
| 径向渐变 | MakeRadialGradient(center, radius, colors, positions) | 从圆心向外 |
| 锥形渐变 | MakeConicGradient(center, startAngle, endAngle, colors, positions) | 绕中心点旋转 |
| 菱形渐变 | MakeDiamondGradient(center, radius, colors, positions) | 从中心到菱形顶点 |

positions 参数可以为空(颜色均匀分布),不为空时必须从 0 开始、以 1 结束,且严格递增。
// 蓝紫线性渐变矩形
auto shader = Shader::MakeLinearGradient(
Point::Make(0, 0), Point::Make(300, 0),
{Color::FromRGBA(66, 133, 244, 255), Color::FromRGBA(170, 0, 255, 255)},
{});
paint.setShader(shader);
canvas->drawRoundRect(Rect::MakeXYWH(20, 20, 300, 80), 12, 12, paint);
混合 Shader(MakeBlend)
把两个 Shader 用 BlendMode 叠在一起:
// image texture + orange tint with Multiply blend
auto imgShader = Shader::MakeImageShader(photo, TileMode::Clamp, TileMode::Clamp);
auto tintShader = Shader::MakeColorShader(Color::FromRGBA(255, 140, 0, 160));
auto blended = Shader::MakeBlend(BlendMode::Multiply, imgShader, tintShader);
paint.setShader(blended);
canvas->drawRect(rect, paint);

TileMode:越界了怎么办
渐变和图像 Shader 都有一个"定义范围",超出这个范围时,TileMode 控制如何填充:

| TileMode | 效果 | 典型用途 |
|---|---|---|
Clamp | 边缘颜色向外延伸 | 大多数情况的默认选择 |
Repeat | 无缝平铺重复 | 图案背景、纹理 |
Mirror | 镜像后平铺,视觉更柔和 | 对称纹理 |
Decal | 超出范围完全透明 | ImageFilter 边缘处理 |
二、Filter:对像素做后处理
画出来之后想加模糊、阴影、调色……这些是对"已经产生的像素"做二次处理,靠 Shader(颜色来源)做不到。
后处理分三个层次,作用对象不同,开销也不同:
- ImageFilter — 对整张离屏图像做处理,最重,但能做最复杂的效果
- ColorFilter — 逐像素颜色变换,不需要离屏缓冲,轻量
- MaskFilter — 对绘制形状的 alpha 通道做变换,控制哪些像素被绘制
ImageFilter:离屏图像级处理
设置了 ImageFilter 的 Paint,绘制流程会多一个离屏 pass:
绘制内容 → 离屏纹理 → ImageFilter 处理 → 写入目标画布(应用 BlendMode)
这就是 ImageFilter 开销比其他两种高的原因——多了一次 GPU roundtrip。但它能做到其他方式做不到的效果。
内置的 ImageFilter:
| 类型 | 工厂方法 | 说明 |
|---|---|---|
| 高斯模糊 | Blur(blurX, blurY, tileMode) | 最常用 |
| 投影阴影 | DropShadow(dx, dy, blurX, blurY, color) | 阴影 + 原内容 |
| 仅投影阴影 | DropShadowOnly(...) | 只有阴影,无原内容 |
| 内阴影 | InnerShadow(dx, dy, blurX, blurY, color) | 向内的阴影 |
| 仅内阴影 | InnerShadowOnly(dx, dy, blurX, blurY, color) | 只有内阴影,无原内容 |
| 颜色滤镜包装 | ColorFilter(colorFilter) | 把 ColorFilter 升级为 ImageFilter |
| 自定义 | Runtime(effect) | RuntimeEffect 自定义 GPU 着色 |
| 串联 | Compose(inner, outer) 或 Compose(filters) | 先执行 inner 再执行 outer;vector 版本按顺序依次执行 |
// 投影阴影示例
auto shadow = ImageFilter::DropShadow(4, 8, 12, 12,
Color::FromRGBA(0, 0, 0, 120));
paint.setImageFilter(shadow);
canvas->drawRoundRect(cardRect, 16, 16, paint);

// 毛玻璃:模糊 + 白色调色叠加
auto blur = ImageFilter::Blur(20.0f, 20.0f, TileMode::Clamp);
auto tint = ImageFilter::ColorFilter(
ColorFilter::Blend(Color::FromRGBA(255, 255, 255, 40), BlendMode::SrcOver));
auto frostGlass = ImageFilter::Compose(blur, tint);
paint.setImageFilter(frostGlass);
canvas->drawRect(glassRect, paint);
ColorFilter:不需要离屏的逐像素调色
ColorFilter 在不分配离屏纹理的情况下,对每个像素做颜色变换——性能远好于 ImageFilter,适合调色类需求。
| 类型 | 工厂方法 |
|---|---|
| 混合叠色 | Blend(color, mode) |
| 颜色矩阵 | Matrix(4×5 array) |
| 亮度提取 | Luma() |
| Alpha 阈值 | AlphaThreshold(threshold) |
| 串联 | Compose(inner, outer) |
颜色矩阵是最灵活的方式:4×5 矩阵对 [R, G, B, A] 做线性变换:
R' = a00*R + a01*G + a02*B + a03*A + a04
G' = a10*R + ...
B' = ...
A' = ...
灰度化示例(ITU-R BT.601 亮度权重):
std::array<float, 20> grayMatrix = {
0.299f, 0.587f, 0.114f, 0, 0,
0.299f, 0.587f, 0.114f, 0, 0,
0.299f, 0.587f, 0.114f, 0, 0,
0, 0, 0, 1, 0
};
paint.setColorFilter(ColorFilter::Matrix(grayMatrix));

MaskFilter:改变绘制形状的 alpha
MaskFilter 不改变颜色,而是在绘制前对形状的 alpha 通道做变换——控制哪些像素最终被绘制。
// 径向渐变蒙版:中心不透明,边缘渐透明
auto radialShader = Shader::MakeRadialGradient(
center, radius,
{Color::FromRGBA(0, 0, 0, 255), Color::FromRGBA(0, 0, 0, 0)}, {});
paint.setMaskFilter(MaskFilter::MakeShader(radialShader));
canvas->drawSimpleText("Hello", 0, 0, font, paint);

串联的执行顺序
ImageFilter::Compose(inner, outer) 执行顺序是先 inner,再 outer。也支持 Compose(vector) 按数组顺序依次执行:
// blur first, then add drop shadow
auto blur = ImageFilter::Blur(3, 3);
auto shadow = ImageFilter::DropShadow(10, 10, 8, 8, Color::Black());
auto combined = ImageFilter::Compose(blur, shadow);

三、PathEffect:绘制前改变路径形状
有时候想要的不是"画一条直线",而是"画一条虚线";不是"画一个星形",而是"画一个圆角星形";不是"画完整路径",而是"只画路径的前 60%"。这些需求都是在绘制之前改变路径形状。
PathEffect 在绘制之前变换路径,变换后的路径才交给 Paint 绘制。因为不涉及离屏渲染,开销比 ImageFilter 低得多。注意:PathEffect 不属于 Paint 体系,而是通过 Shape::ApplyEffect(shape, effect) 独立应用到 Shape 上。
MakeDash:虚线
float intervals[] = {12.0f, 6.0f}; // [实线长度, 空白长度, ...]
// 普通虚线:首尾可能出现残缺的半截虚线段
auto dash = PathEffect::MakeDash(intervals, 2, 0.0f);
// adaptive=true:自动微调每段间隔,使首尾对齐,不出现残缺
auto adaptiveDash = PathEffect::MakeDash(intervals, 2, 0.0f, true);

phase 参数控制起始偏移量(对虚线动画有用)。adaptive 模式会按路径总长度自动调整间隔比例,让首段和尾段都是完整的虚线段——dash 和 gap 均等比缩放,不是单独拉伸 gap。
MakeCorner:路径圆角化
// round all corners of any path (star, polygon, etc.)
auto corner = PathEffect::MakeCorner(16.0f);
auto shape = Shape::ApplyEffect(Shape::MakeFrom(starPath), corner);
canvas->drawShape(shape, paint);

radius 控制圆角半径,越大圆角越明显。对复杂路径同样有效——所有尖角都会被圆化。
MakeTrim:路径裁剪
MakeTrim 最典型的用途是路径描绘动画:让 end 从 0 动画到 1,路径从头到尾逐渐出现:
float progress = 0.6f; // 0~1, driven by animation
auto trim = PathEffect::MakeTrim(0.0f, progress);
auto shape = Shape::ApplyEffect(Shape::MakeFrom(circlePath), trim);
canvas->drawShape(shape, paint);
start 和 end 都支持超出 [0,1] 范围(循环),当 end < start 时路径方向反转。
四、完整处理顺序与选型
Paint 上的处理顺序
同一个 Paint 设置了多种效果时,处理顺序固定:
原始绘制内容
↓ 绘制 → MaskFilter(控制 alpha 形状)
→ ColorFilter(逐像素调色)
离屏纹理
↓ ImageFilter(图像级后处理)
最终图像 → BlendMode 合成到画布
注意:PathEffect 不在 Paint 处理管线中。PathEffect 通过 Shape::ApplyEffect 在绘制前独立处理路径形状,变换后的 Shape 再交给 canvas->drawShape(shape, paint) 绘制。
选型速查
| 需求 | 推荐方案 |
|---|---|
| 渐变 / 图像纹理 / 颜色叠加 | Shader |
| 路径形状变换(虚线/圆角/动画裁剪) | PathEffect |
| 颜色调整(灰度/色调/亮度) | ColorFilter |
| 软边缘 / 形状渐隐 | MaskFilter |
| 阴影 / 模糊 / 复杂后处理 | ImageFilter |
| 完全自定义 GPU 着色 | RuntimeEffect → ImageFilter::Runtime |
注意事项
- ImageFilter::Compose 链越长,GPU pass 越多,上线前用帧分析工具测一下
- PathEffect 先改变路径,再描边——不是先描边再变形路径
saveLayer本身就分配离屏纹理;saveLayer 内再加 ImageFilter 不会双倍分配,但要避免多层 saveLayer 嵌套- ColorFilter 和 MaskFilter 可以在不分配离屏纹理的情况下运行,如果只需要调色或软边缘,优先选这两个而不是 ImageFilter
