
miniblink49 中的 Skia 绘制上下文SkCanvas API 实战与源码级解析【免费下载链接】miniblink49a lighter, faster browser kernel of blink to integrate HTML UI in your app. 一个小巧、轻量的浏览器内核用来取代wke和libcef项目地址: https://gitcode.com/GitHub_Trending/mi/miniblink49本文基于 miniblink49 仓库内自带的 Skia 文档 SkCanvas 指南 展开SkCanvas 是 Skia 的绘制上下文负责接收所有绘制命令、维护变换矩阵栈与裁剪栈。你将学会如何用它完成从清屏、仿射变换到各种几何图形与位图、文本绘制的完整流程并了解 miniblink49 的 raster 光栅化与 cc 合成层是如何在真实浏览器内核场景中调用这些 API 的。SkCanvas 的定位只管理画在哪不管怎么画skcanvas.md 对 SkCanvas 的官方定义是它是 Skia 的 drawing context知道绘制应指向哪里屏幕或离屏像素并维护一个矩阵栈stack of matrices与裁剪栈stacks of clips。与 PostScript、cairo、awt 等类似上下文不同Skia 刻意不把任何绘制属性颜色、线宽等保存在 canvas 上。每个绘制调用都通过独立的SkPaint对象显式指定样式。这一设计从源码结构上也能印证SkCanvas.h 中所有drawXxx接口的最后一个参数都是const SkPaint或const SkPaint*而 canvas 自身状态只有矩阵/裁剪相关的成员与save()/restore()栈。这意味着canvas 管状态当前变换矩阵、当前裁剪区域paint 管外观颜色、填充/描边样式、线宽、抗锯齿、shader、xfermode 等。快速上手一段可复制的绘制代码原文档给出的示例是一个填充七角星heptagram函数签名void draw(SkCanvas* canvas)正是 miniblink 内部各绘制回调用到的统一模式void draw(SkCanvas* canvas) { const SkScalar scale 256.0f; const SkScalar R 0.45f * scale; const SkScalar TAU 6.2831853f; SkPath path; path.moveTo(R, 0.0f); for (int i 1; i 7; i) { SkScalar theta 3 * i * TAU / 7; path.lineTo(R * cos(theta), R * sin(theta)); } path.close(); SkPaint p; p.setAntiAlias(true); canvas-clear(SK_ColorWHITE); canvas-translate(0.5f * scale, 0.5f * scale); canvas-drawPath(path, p); }关键点先用path.moveTo/lineTo/close构建路径p.setAntiAlias(true)开启抗锯齿clear()白屏后translate()将原点移到画布中心最后drawPath落笔。注意此处translate之后没有restore()——如果后续还要以原坐标绘制其他内容就必须成对调用 save/restore。变换矩阵栈save / restore 与 translate / rotate / scale原文档第二个示例展示了经典的保存—变换—绘制—恢复模式绘制一个旋转 45 度的蓝色矩形void draw(SkCanvas* canvas) { canvas-save(); canvas-translate(SkIntToScalar(128), SkIntToScalar(128)); canvas-rotate(SkIntToScalar(45)); SkRect rect SkRect::MakeXYWH(-90.5f, -90.5f, 181.0f, 181.0f); SkPaint paint; paint.setColor(SK_ColorBLUE); canvas-drawRect(rect, paint); canvas-restore(); }这里有两个容易踩坑的细节对应 SkCanvas.h 中的注释save/restore 必须配对。restoreToCount(int saveCount)的文档明确写道count 超过 saveCount 是错误用法最低可以传saveCount 1。旋转围绕当前原点。示例先把原点平移到 (128,128) 再旋转矩形以-90.5, -90.5为中心这样旋转后仍然居中。若不先 translate旋转将围绕 (0,0) 进行。从 SkCanvas.h 可以看到完整的仿射变换 API 集translate(dx, dy)、scale(sx, sy)、rotate(degrees)角度单位是度、concat(const SkMatrix)用于直接拼接任意矩阵。这些操作都是乘到当前矩阵上的因此顺序很重要——先 translate 再 rotate 与先 rotate 再 translate 结果不同。在 miniblink 的真实合成流程里这套 API 被大量使用。例如 CompositingLayer.cpp 的drawToCanvasChildren在绘制每个子层前通过子层的currentTransform修改 canvas 矩阵、用clipRect设置子层边界裁剪绘制完成后恢复LayerTreeHost.cpp 的drawToCanvas入口处第一句就是canvas-save()确保整棵层树的变换不会泄漏到外部 canvas 状态。清屏与整屏填充clear、drawPaint、drawColor文档指出清空画布最笨的办法是画一个巨大矩形而正确姿势有两种// 方式一用 SkPaint 填充整个 canvas受当前 clip 约束 void draw(SkCanvas* canvas) { SkPaint paint; paint.setColor(SK_ColorWHITE); canvas-drawPaint(paint); } // 方式二只想要纯色可带 xfermode直接 drawColor省去分配 paint void draw(SkCanvas* canvas) { canvas-drawColor(SK_ColorWHITE); }两者语义上有区别源码签名也能印证drawPaint(const SkPaint paint)SkCanvas.h用 paint 中指定的颜色、shader、xfermode 填充整个画布尊重当前裁剪若 paint 带 shadershader 还会尊重 canvas 当前矩阵。drawColor(SkColor color, SkXfermode::Mode mode SkXfermode::kSrcOver_Mode)SkCanvas.h默认kSrcOver_Mode混合模式适合快速清屏。clear(SkColor color)SkCanvas.h非虚的便捷入口示例代码里的canvas-clear(SK_ColorWHITE)走的就是它。miniblink 的 LayerTreeHost.cpp 中clearCanvas实现展示了透明合成窗口下的真实清屏策略构造一个SkPaint当useLayeredBuffer为真时调用clearPaint.setARGB(0, 0xFF, 0xFF, 0xFF)填充全透明色再交给 canvas——这正是canvas 管画在哪、paint 管画成什么样的典型案例。完整绘制 API 一览从矩形到文本原文档给出一段覆盖主要 draw API 的完整示例本文将其完整保留并按类别加注释SkBitmap source; void draw(SkCanvas* canvas) { canvas-drawColor(SK_ColorWHITE); // 清屏 SkPaint paint; paint.setStyle(SkPaint::kStroke_Style); // 描边模式 paint.setStrokeWidth(4); // 线宽 4 paint.setColor(SK_ColorRED); SkRect rect SkRect::MakeXYWH(50, 50, 40, 60); canvas-drawRect(rect, paint); // 红色描边矩形 SkRRect oval; oval.setOval(rect); oval.offset(40, 60); paint.setColor(SK_ColorBLUE); canvas-drawRRect(oval, paint); // 蓝色椭圆RRect 特例 paint.setColor(SK_ColorCYAN); canvas-drawCircle(180, 50, 25, paint); // 青色圆圆心(180,50) 半径25 rect.offset(80, 0); paint.setColor(SK_ColorYELLOW); canvas-drawRoundRect(rect, 10, 10, paint); // 黄色圆角矩形rxry10 SkPath path; path.cubicTo(768, 0, -512, 256, 256, 256); // 三次贝塞尔曲线 paint.setColor(SK_ColorGREEN); canvas-drawPath(path, paint); // 绿色路径 canvas-drawBitmap(source, 128, 128, paint); // 绘制位图注意 paint 是指针可为 null SkRect rect2 SkRect::MakeXYWH(0, 0, 40, 60); canvas-drawBitmapRect(source, rect2); // 位图按目标矩形绘制 SkPaint paint2; const char text[] Hello, Skia!; canvas-drawText(text, strlen(text), 50, 25, paint2); // 在 (50,25) 起点处绘制文本 }对应 SkCanvas.h 中的接口行号可供检索类别接口源码位置几何drawRectSkCanvas.h#L684几何drawRRectSkCanvas.h#L722几何drawCircle/drawArc/drawRoundRectSkCanvas.h#L738-L762路径/线drawPath/drawLineSkCanvas.h#L676-L770位图drawBitmapSkCanvas.h#L826文本drawText/drawTextBlob/drawTextOnPathSkCanvas.h#L906-L965回放drawPicture(const SkPicture*)SkCanvas.h#L973-L989一个值得注意的约定部分调用如drawBitmap传的是const SkPaint*指针此时 paint允许为 null其余情况 paint 参数是必须的。这一指针即可空、引用则必传的区分在 Skia 老版本 API 中普遍存在阅读 SkCanvas.h 时看到paint与paint的差异要格外留心。裁剪Clip与矩阵栈并行的第二状态SkCanvas 维护的第二个状态是裁剪栈。裁剪 API 签名为clipRect / clipRRect / clipPath均带SkRegion::Op与antialias参数SkCanvas.h#L1522-L1524。裁剪与save()/restore()配合使用save 压栈时同时保存当前裁剪restore 时恢复。miniblink 的合成器对裁剪的使用非常典型CompositingLayer.cpp 在绘制子层前用子层drawToCanvasProperties()-bounds构造SkRect并调用canvas-clipRect(...)保证子层内容不会越界绘制到父层区域——这就是文档中drawPaint 会 respect the current clip在实际窗口合成中的体现。从 SkCanvas 对象到后端Raster 与 Picture 两条落地路径原文档末尾提到如何创建 SkCanvas 对象见 canvas.md。该文档列出了 Raster、Ganesh、SkPDF、SkPicture、NullCanvas 等多个后端其中最常用的是 RasterCPU 光栅化到内存与 SkPicture记录绘制命令的 display list。结合 miniblink49 源码可以印证这两条路径都在使用中路径一Raster 表面 —— 光栅线程位图绘制。在 RasterTask.cpp 中raster 任务通过SkSurface::NewRasterDirect(info, bitmap-getPixels(), stride, surfaceProps)在客户端提供的内存上建表面再取surface-getCanvas()得到 SkCanvas然后用普通SkPaint绘制同文件中还有一处直接用SkCanvas canvas(*filterBitmap);构造面向位图的 canvas配合paint.setImageFilter(filter)canvas.clear(SK_ColorTRANSPARENT)canvas.drawBitmap(...)完成图像滤镜处理——这正是文档中clear drawBitmap paint 属性组合拳的真实工程化用法。路径二SkPicture 记录回放 —— 层缓存。RasterTask.cpp 顶部同时包含了 SkPicture.h 与 SkSurface.hLayerTreeHost.cpp 则通过skia/ext/platform_canvas.h提供的平台工具函数创建 SkCanvas。从源码结构看miniblink 的 cc 层cc/layers、cc/tiles、cc/blink下的各 LayerImpl普遍以drawToCanvas(SkCanvas*, ...)形式的接口接收上层传入的 canvas将如何得到 canvas与往 canvas 上画什么彻底解耦——这与文档强调的SkCanvas 只是绘制上下文的定位完全一致。如果你需要自己写一段独立的 Raster 绘制代码可参照 canvas.md 的推荐写法SkSurface::NewRasterN32Premul(width, height)创建表面、getCanvas()取 canvas、绘制完成后newImageSnapshot()得到SkImage再encode()导出 PNG。小结SkCanvas 是纯上下文对象状态只有矩阵栈 裁剪栈样式全部由每次调用传入的 SkPaint 决定变换操作translate/rotate/scale/concat乘在当前矩阵上务必以save()/restore()成对包裹避免状态泄漏清屏优先用clear()或drawColor()需要 shader/xfermode 时用drawPaint()注意裁剪始终生效所有drawXxxAPI 以const SkPaint结尾仅少数接受可空的const SkPaint*在 miniblink49 中SkCanvas 是 raster 光栅化cc/raster/RasterTask.cpp与 cc 层合成cc/layers/CompositingLayer.cpp、cc/trees/LayerTreeHost.cpp共同的绘制入口理解本文的 API 语义后可以直接阅读这些文件跟进真实调用链。【免费下载链接】miniblink49a lighter, faster browser kernel of blink to integrate HTML UI in your app. 一个小巧、轻量的浏览器内核用来取代wke和libcef项目地址: https://gitcode.com/GitHub_Trending/mi/miniblink49创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考