Canvas Overview
Canvas 是 tgfx 绘图流水线的"第一入口",提供了丰富的 2D 绘图 API。本文档介绍 Canvas 的完整使用方法,帮助开发者快速上手 tgfx 的绑定 GPU 上下文、使用 Canvas 进行 2D 绘图,以及提交渲染结果。
1. 绘图流水线入口
1.1 创建链路概览
tgfx 的绘图流水线遵循 Device → Context → Surface → Canvas 的创建链路:

| 组件 | 职责 |
|---|---|
| Device | 代表 GPU 设备,管理 GPU 资源的生命周期 |
| Context | 渲染上下文,负责创建和管理 GPU 资源,发出绘图命令 |
| Surface | 渲染目标,管理 Canvas 绑定的像素存储 |
| Canvas | 绘图接口,提供所有 2D 绘图 API |
1.2 代码示例
int main() {
// 1. 从 Device 锁定 Context(线程安全)
auto context = device->lockContext();
// 2. 创建 Surface(选择下面三种方式之一)
auto surface = Surface::Make(context, 800, 600);
// 3. 获取 Canvas(由 Surface 管理生命周期)
auto canvas = surface->getCanvas();
// 4. 绑定完成,开始绑定...
canvas->clear(Color::White());
canvas->drawRect(Rect::MakeXYWH(100, 100, 200, 150), paint);
// 5. 提交渲染
context->flushAndSubmit();
// 6. 解锁 Context
device->unlock();
}
1.3 Surface 的三种创建方式
方式一:指定尺寸创建
在 GPU 上分配指定尺寸的渲染目标,最常用的方式。
// 创建 800x600 的 Surface,支持 MSAA(4x 采样)
auto surface = Surface::Make(context, 800, 600,
false, // alphaOnly
4, // sampleCount (MSAA)
false, // mipmapped
0, // renderFlags
nullptr); // colorSpace
方式二:从 BackendTexture 创建
包装已有的 GPU 纹理,适用于与外部纹理交互的场景。
// 从 OpenGL 纹理创建
GLTextureInfo glInfo = {textureID, GL_TEXTURE_2D, GL_RGBA8};
BackendTexture backendTexture(glInfo, width, height);
auto surface = Surface::MakeFrom(context, backendTexture,
ImageOrigin::TopLeft,
1); // sampleCount
// 从 Metal 纹理创建
MetalTextureInfo metalInfo = {metalTexture};
BackendTexture backendTexture(metalInfo, width, height);
auto surface = Surface::MakeFrom(context, backendTexture, ImageOrigin::TopLeft);
方式三:从 BackendRenderTarget 创建
包装已有的渲染目标(如 FBO),适用于渲染到外部帧缓冲的场景。
// 从 OpenGL FBO 创建
GLFrameBufferInfo glInfo = {fboID, GL_RGBA8};
BackendRenderTarget renderTarget(glInfo, width, height);
auto surface = Surface::MakeFrom(context, renderTarget, ImageOrigin::TopLeft);
// 从 Metal 纹理创建(作为渲染目标)
MetalTextureInfo metalInfo = {metalTexture};
BackendRenderTarget renderTarget(metalInfo, width, height);
auto surface = Surface::MakeFrom(context, renderTarget, ImageOrigin::TopLeft);
注意:使用
MakeFrom创建时,调用者需确保 backend 对象在 Surface 生命周期内保持有效。
2. 状态栈
Canvas 维护一个状态栈,用于保存和恢复矩阵(matrix)与裁剪区域(clip)的状态。
2.1 save / restore
save() 保存当前状态,restore() 恢复到上一次保存的状态。
int main() {
canvas->save(); // 保存状态,返回 saveCount = 0
canvas->translate(100, 100);
canvas->drawRect(rect, paint); // 在 (100, 100) 位置绘制
canvas->restore(); // 恢复状态,translate 被撤销
canvas->drawRect(rect, paint); // 在原点位置绘制
}
2.2 getSaveCount / restoreToCount
getSaveCount() 返回当前保存的层数,restoreToCount(n) 恢复到指定层。
int main() {
int count0 = canvas->save(); // count0 = 0
canvas->translate(50, 50);
int count1 = canvas->save(); // count1 = 1
canvas->rotate(45);
int count2 = canvas->save(); // count2 = 2
canvas->scale(2, 2);
canvas->restoreToCount(count1); // 一次性恢复到 count1,撤销 rotate 和 scale
}
2.3 saveLayer / saveLayerAlpha
saveLayer() 和 saveLayerAlpha() 在保存状态的同时,创建一个离屏图层用于后续绘制。当 restore() 时,离屏图层会应用指定的 Paint(透明度、滤镜、混合模式等)后合成到主画布。
int main() {
// 使用 saveLayerAlpha 实现半透明绘制
canvas->saveLayerAlpha(0.5f); // 创建离屏图层,alpha = 0.5
canvas->drawCircle(100, 100, 50, redPaint);
canvas->drawCircle(130, 100, 50, bluePaint);
canvas->restore(); // 整体以 50% 透明度合成到主画布
// 使用 saveLayer 应用 ImageFilter
Paint layerPaint;
layerPaint.setImageFilter(ImageFilter::Blur(10, 10));
canvas->saveLayer(&layerPaint);
canvas->drawImage(image, 0, 0);
canvas->restore(); // 应用模糊滤镜后合成
}
saveLayer 的离屏渲染本质

| 阶段 | 操作 |
|---|---|
saveLayer() | 分配离屏纹理,后续绘制重定向到离屏 |
| 绑定操作 | 所有 draw* 调用都绘制到离屏纹理 |
restore() | 离屏纹理作为 Image,应用 Paint 后绘制到主画布 |
性能提示:
saveLayer会分配额外的 GPU 纹理,频繁使用可能影响性能。仅在需要整体应用透明度、滤镜或特殊混合模式时使用。
3. 变换
Canvas 通过变换矩阵控制绑定内容的位置、大小、旋转等。所有变换都是左乘(pre-multiply),即"先变换,后绑定"。
3.1 变换方法一览
| 方法 | 说明 |
|---|---|
translate(dx, dy) | 平移 |
scale(sx, sy) | 缩放 |
rotate(degrees) | 绕原点旋转 |
rotate(degrees, px, py) | 绕指定点旋转 |
skew(sx, sy) | 斜切 |
concat(matrix) | 与当前矩阵相乘 |
setMatrix(matrix) | 直接设置矩阵(覆盖) |
resetMatrix() | 重置为单位矩阵 |
getMatrix() | 获取当前矩阵 |
3.2 左乘顺序的理解
Canvas 的变换是左乘(pre-multiply),这意味着代码中后写的变换先作用。
int main() {
// 数学等价于:M = Translate * Rotate * Scale
// 对图形的作用顺序:先 Scale → 再 Rotate → 最后 Translate
canvas->translate(200, 200); // 最后执行:移动到 (200, 200)
canvas->rotate(45); // 其次执行:旋转 45 度
canvas->scale(2, 2); // 最先执行:放大 2 倍
canvas->drawRect(Rect::MakeXYWH(0, 0, 50, 50), paint);
}

3.3 concat 与 setMatrix
除了使用 translate、scale 等便捷方法外,也可以直接操作底层的变换矩阵。主要通过 concat 叠加变换,或用 setMatrix 强制替换当前变换:
concat(matrix):将传入的矩阵与当前矩阵进行左乘。这与调用便捷方法的行为一致,效果会累加到当前的坐标系上。setMatrix(matrix):直接将当前坐标系的变换状态替换为传入的矩阵,抛弃之前的变换历史。这在需要重置或应用绝对变换时很有用。
int main() {
// 1. concat:叠加变换矩阵
Matrix rotation = Matrix::MakeRotate(45);
canvas->concat(rotation); // currentMatrix = currentMatrix * rotation
// 2. setMatrix:覆盖当前矩阵
Matrix transform = Matrix::MakeTrans(100, 100);
canvas->setMatrix(transform); // currentMatrix = transform
// 3. resetMatrix:快速重置为单位矩阵(相当于撤销所有变换)
canvas->resetMatrix(); // currentMatrix = I
}
4. 裁剪
Canvas 的裁剪区域(Clip)用于限制后续所有绘制操作的可见范围,只有在裁剪区域内的部分才会被真正绘制到屏幕或图像上。所有多次裁剪操作的效果都是求交集(intersect),即裁剪区域只能越裁越小。
4.1 裁剪方法
| 方法 | 说明 |
|---|---|
clipRect(rect) | 用矩形裁剪 |
clipPath(path) | 用路径裁剪 |
getTotalClip() | 获取当前裁剪路径 |
4.2 代码示例
int main() {
// 示例 1:矩形裁剪
canvas->save(); // 保存当前无裁剪限制的干净状态
canvas->clipRect(Rect::MakeXYWH(50, 50, 200, 200));
canvas->drawCircle(150, 150, 100, paint); // 圆形会被限制在矩形内
canvas->restore(); // 恢复状态,移除矩形裁剪限制
// 示例 2:路径裁剪(如椭圆裁剪)
canvas->save(); // 再次保存无裁剪的状态
Path clipPath;
clipPath.addOval(Rect::MakeXYWH(100, 100, 200, 200));
canvas->clipPath(clipPath);
canvas->drawImage(image, 0, 0); // 图片仅在椭圆区域内可见
canvas->restore(); // 恢复状态
}
4.3 裁剪的求交特性
多次裁剪会取交集,裁剪区域只会越来越小。
int main() {
canvas->clipRect(Rect::MakeXYWH(0, 0, 200, 200)); // 裁剪区域 A
canvas->clipRect(Rect::MakeXYWH(100, 100, 200, 200)); // 裁剪区域 B
// 最终裁剪区域 = A ∩ B = (100, 100, 100, 100)
canvas->drawColor(Color::Red()); // 只有交集区域显示为红色
}

4.4 裁剪与变换的配合
裁剪路径会受当前矩阵影响。
int main() {
canvas->rotate(45);
canvas->clipRect(Rect::MakeXYWH(0, 0, 100, 100)); // 裁剪区域也旋转了 45 度
canvas->drawImage(image, 0, 0);
}
5. 绘制方法总览
Canvas 提供了丰富的绘制方法,涵盖基础图形、路径、图片、文本等。
5.1 基础图形
| 方法 | 说明 |
|---|---|
drawRect(rect, paint) | 绘制矩形 |
drawRoundRect(rect, rx, ry, paint) | 绘制圆角矩形 |
drawRRect(rrect, paint) | 绘制 RRect(可变圆角矩形) |
drawOval(rect, paint) | 绘制椭圆 |
drawCircle(cx, cy, r, paint) | 绘制圆形 |
drawLine(x0, y0, x1, y1, paint) | 绘制线段 |
int main() {
Paint paint;
// 绘制矩形
canvas->drawRect(Rect::MakeXYWH(50, 50, 100, 80), paint);
// 绘制圆角矩形
canvas->drawRoundRect(Rect::MakeXYWH(200, 50, 100, 80), 15, 15, paint);
// 绘制圆形
canvas->drawCircle(100, 200, 40, paint);
// 绘制线段
canvas->drawLine(200, 180, 350, 220, paint);
}
5.2 路径与形状
| 方法 | 说明 |
|---|---|
drawPath(path, paint) | 绘制路径 |
drawShape(shape, paint) | 绘制 Shape 对象 |
int main() {
// 绘制自定义路径
Path path;
path.moveTo(100, 50);
path.lineTo(150, 150);
path.lineTo(50, 150);
path.close(); // 三角形
Paint paint;
paint.setColor(Color::Green());
canvas->drawPath(path, paint);
// 绘制 Shape(支持缓存优化)
auto shape = Shape::MakeFrom(path);
canvas->drawShape(shape, paint);
}
5.3 网格绘制 (Mesh)
网格绘制(Mesh)常用于实现复杂的几何体渲染或图像形变(如水波纹变形)。
在使用 drawMesh 渲染时,颜色混合规则(Color Blending Rules)非常关键。网格最终呈现的颜色取决于 Paint 的着色器(Shader)和 Mesh 是否带有顶点颜色(Vertex Colors):
- 有 Shader + 有顶点颜色:着色器颜色与顶点颜色相乘(Modulate 混合)。
- 有 Shader + 无顶点颜色:纯着色器颜色(比如纯图片纹理)。
- 无 Shader + 有顶点颜色:纯顶点颜色。
- 无 Shader + 无顶点颜色:使用
Paint的颜色。
⚠️ 注意:只要存在 Shader 或顶点颜色,
Paint自身的颜色(paint.setColor())就不会参与最终的颜色混合计算。
| 方法 | 说明 |
|---|---|
drawMesh(mesh, paint) | 使用当前的裁剪、变换矩阵和画笔绘制一个网格 |
int main() {
// 构造 Mesh:传入顶点坐标、纹理坐标、顶点颜色、顶点索引等
auto mesh = Mesh::Make(vertices, texCoords, colors, indices);
Paint paint;
// 根据上述规则,如果赋予了 Shader,并且 mesh 中没有顶点颜色,则完全使用 Shader 颜色
paint.setShader(imageShader);
canvas->drawMesh(mesh, paint);
}
5.4 图片绘制
| 方法 | 说明 |
|---|---|
drawImage(image, paint) | 在坐标 (0,0) 处绘制原始尺寸图片 |
drawImage(image, x, y, paint) | 在指定坐标 (x,y) 处绘制图片 |
drawImage(image, sampling, paint) | 使用指定的采样选项(如邻近采样或线性采样)绘制图片 |
drawImageRect(image, dstRect, ...) | 将整张图片缩放绘制到目标矩形区域内 |
drawImageRect(image, srcRect, dstRect, ...) | 截取图片的部分区域(srcRect)并缩放绘制到目标矩形(dstRect) |
drawAtlas(atlas, matrices[], rects[], ...) | 从图集(Atlas)中批量绘制多张子图,用于减少绘制调用开销(性能优化) |
关于 SamplingOptions:在进行图片绘制和缩放时,可以通过
SamplingOptions控制图像采样和过滤的质量。如果在调用 API 时未显式传入该选项,系统默认会使用线性过滤(FilterMode::Linear和MipmapMode::Linear)来保证图像在缩放时的平滑度。
int main() {
auto image = Image::MakeFromFile("photo.png");
// 简单绘制
canvas->drawImage(image, 50, 50);
// 绘制到指定区域(会缩放)
canvas->drawImageRect(image, Rect::MakeXYWH(200, 50, 150, 100));
// 从图片的一部分切割绘制
Rect srcRect = Rect::MakeXYWH(0, 0, 64, 64); // 取图片左上角 64x64
Rect dstRect = Rect::MakeXYWH(50, 200, 128, 128); // 放大到 128x128
canvas->drawImageRect(image, srcRect, dstRect);
// 从图集(Atlas)中批量绘制(性能优化)
Matrix transforms[] = {Matrix::MakeTrans(0, 0), Matrix::MakeTrans(64, 0)};
Rect rects[] = {Rect::MakeXYWH(0, 0, 32, 32), Rect::MakeXYWH(32, 0, 32, 32)};
canvas->drawAtlas(atlas, transforms, rects, nullptr, 2);
}
5.5 文本绘制
| 方法 | 说明 |
|---|---|
drawSimpleText(text, x, y, font, paint) | 简单文本绘制(UTF-8) |
drawGlyphs(glyphs[], positions[], count, font, paint) | 绘制字形数组 |
drawTextBlob(textBlob, x, y, paint) | 绘制预排版的 TextBlob |
int main() {
Font font(typeface, 24);
Paint paint;
paint.setColor(Color::Black());
// 1. 简单文本
canvas->drawSimpleText("Hello, tgfx!", 50, 100, font, paint);
// 2. 绘制字形数组(用于精确控制每个字形的渲染位置,常用于自定义排版引擎)
GlyphID glyphs[] = { 42, 43, 44 }; // 字形索引,通常从字体中获取
Point positions[] = { Point::Make(50, 150), Point::Make(70, 150), Point::Make(90, 150) };
canvas->drawGlyphs(glyphs, positions, 3, font, paint);
// 3. 使用 TextBlob(支持复杂排版和缓存,推荐多次绘制同一段文本时使用)
auto textBlob = TextBlob::MakeFrom("预排版文本", font);
canvas->drawTextBlob(textBlob, 50, 200, paint);
}
5.6 其他绘制
| 方法 | 说明 |
|---|---|
clear(color) | 清除画布(BlendMode::Src) |
drawColor(color, blendMode) | 填充颜色(可指定混合模式) |
drawPaint(paint) | 用 Paint 填充整个画布 |
drawPicture(picture) | 回放 Picture 录制的命令 |
int main() {
// 清除为白色
canvas->clear(Color::White());
// 半透明覆盖
canvas->drawColor(Color::FromRGBA(0, 0, 255, 128), BlendMode::SrcOver);
// 回放录制的 Picture
canvas->drawPicture(picture);
}
6. 刷新与提交
Canvas 的绘制指令通常是异步记录的。当我们调用 drawRect、save 等 API 时,实际上只是将这些指令记录在了一个命令缓冲区(Recording)中,并没有立即交给 GPU 执行。
要让屏幕上真正显示出画面,或者让 Surface 的像素数据真正更新,必须显式地进行刷新(Flush)并提交(Submit)这些指令。
6.1 提交方式
Context 提供了灵活的提交控制,可以一步到位,也可以分步进行:
| 方法 | 说明 |
|---|---|
context->flushAndSubmit(syncCpu) | 一步完成:直接将当前所有记录的绘图指令刷新并提交给 GPU 执行。参数 syncCpu=true 表示 CPU 会阻塞等待 GPU 执行完毕(常用于需要立即读取像素的场景)。 |
context->flush() | 仅刷新:将当前的绘图指令打包成一个 Recording 对象返回,但不提交给 GPU。此时 GPU 尚未开始工作。 |
context->submit(recording, syncCpu) | 仅提交:将之前通过 flush() 获取的 Recording 提交给 GPU 执行。 |
6.2 同步 vs 异步提交
int main() {
canvas->drawRect(rect, paint);
// 方式一:同步提交(等待 GPU 完成)
context->flushAndSubmit(true); // syncCpu = true,阻塞直到 GPU 完成
// 方式二:异步提交(不等待)
context->flushAndSubmit(false); // syncCpu = false,立即返回
}
6.3 Recording 异步提交模式
Recording 允许将绘图命令的刷新与提交分离,实现更精细的控制。
int main() {
// 阶段 1:绑定
canvas->drawImage(image1, 0, 0);
canvas->drawImage(image2, 100, 0);
// 阶段 2:刷新到 Recording(不发送到 GPU)
auto recording = context->flush();
// 阶段 3:可以在此做其他 CPU 工作...
processSomeData();
// 阶段 4:提交到 GPU
if (recording) {
context->submit(std::move(recording), false);
}
}
注意:如果创建了多个 Recording,提交后面的 Recording 会强制先提交之前的所有 Recording,以保持正确的渲染顺序。
6.4 提交流程图

7. 异步像素回读
Surface::asyncReadPixels() 支持异步读取 GPU 渲染结果到 CPU 内存,避免阻塞渲染线程。
7.1 API 概览
| 方法 | 说明 |
|---|---|
surface->asyncReadPixels(rect) | 发起异步回读,返回 SurfaceReadback |
readback->isReady(context) | 检查数据是否就绪(非阻塞) |
readback->lockPixels(context, flipY) | 锁定并获取像素指针(可能阻塞) |
readback->unlockPixels(context) | 解锁像素数据 |
7.2 代码示例
int main() {
// 完成绘制
canvas->drawImage(image, 0, 0);
context->flushAndSubmit();
// 发起异步回读
auto readback = surface->asyncReadPixels(Rect::MakeWH(800, 600));
if (!readback) {
return; // 回读区域无效
}
// 方式一:轮询等待(非阻塞)
while (!readback->isReady(context)) {
// 做其他事情...
std::this_thread::yield();
}
// 方式二:直接 lockPixels(会阻塞等待)
const void* pixels = readback->lockPixels(context);
if (pixels) {
// 访问像素数据
auto info = readback->info();
// info.width(), info.height(), info.rowBytes()...
// 完成后解锁
readback->unlockPixels(context);
}
}
7.3 flipY 参数
如果 Surface 的 origin 是 BottomLeft(OpenGL 默认),像素数据会是上下翻转的。使用 flipY = true 可以在 CPU 端进行翻转校正。
int main() {
// 如果需要顶部为原点的像素数据
const void* pixels = readback->lockPixels(context, true); // flipY = true
}
8. 辅助工具
8.1 AutoCanvasRestore
RAII 风格的状态保存/恢复辅助类,构造时自动 save(),析构时自动 restore()。
int main() {
{
AutoCanvasRestore acr(canvas); // 自动 save
canvas->translate(100, 100);
canvas->rotate(45);
canvas->drawRect(rect, paint);
} // 离开作用域自动 restore
// 状态已恢复
canvas->drawRect(rect, paint); // 在原位置绘制
}
8.2 PictureRecorder
用于录制绘图命令,生成可重复回放的 Picture 对象。适用于需要多次绘制相同内容的场景。
int main() {
// 录制绘图命令
PictureRecorder recorder;
auto recordCanvas = recorder.beginRecording();
recordCanvas->drawCircle(50, 50, 30, paint);
recordCanvas->drawRect(rect, paint);
auto picture = recorder.finishRecordingAsPicture();
// 多次回放
for (int i = 0; i < 5; i++) {
canvas->save();
canvas->translate(i * 100, 0);
canvas->drawPicture(picture);
canvas->restore();
}
}
附录:快速参考
Canvas 方法速查表
| 分类 | 方法 |
|---|---|
| 状态栈 | save, restore, saveLayer, saveLayerAlpha, getSaveCount, restoreToCount |
| 变换 | translate, scale, rotate, skew, concat, setMatrix, resetMatrix, getMatrix |
| 裁剪 | clipRect, clipPath, getTotalClip |
| 基础图形 | drawRect, drawRoundRect, drawRRect, drawOval, drawCircle, drawLine |
| 路径/形状 | drawPath, drawShape |
| 图片 | drawImage, drawImageRect, drawAtlas |
| 文本 | drawSimpleText, drawGlyphs, drawTextBlob |
| 网格 | drawMesh |
| 其他 | clear, drawColor, drawPaint, drawPicture |
提交流程速查
// 最简方式
context->flushAndSubmit();
// 分步方式
auto recording = context->flush();
// ... do other work ...
context->submit(std::move(recording));
