Canvaskit 入门,绘制基本图形

1,271 阅读6分钟

大家好,我是前端西瓜哥。

本文介绍 canvaskit 的简单用法,如何绘制几种基本图形,对 canvaskit 做一个简单入门。

canvaskit 是 skia 的 wasm 封装版本,大概可以理解为加强版的 Canva 2d。

skia 是 Google 旗下的开源 2D 渲染引擎,使用 C++ 编写,用于 Chorme、Android、Flutter 等产品。

没错,我们经常用的 Canvas 2D,在 Chrome 浏览器上,其底层正是 skia。

得益于 wasm 技术,我们也可以用 js 来使用 skia 强大的功能,比如丰富的混合模式、布尔运算。

安装和初始化

我们用常规的 npm 的方式安装。

npm i canvaskit-wasm

在 html 的 head 标签下添加:

<script src="/node_modules/canvaskit-wasm/bin/canvaskit.js"></script>

然后在 index.js 文件加入下面内容。

import InitCanvasKit from 'canvaskit-wasm';

const main = async () => {
  const CanvasKit = await InitCanvasKit({
    locateFile(file) => '/node_modules/canvaskit-wasm/bin/' + file,
  });
  
  // 。...
}

main();

InitCanvasKit 会根据设置找到对应的路径下的 canvaskit.wasm 文件进行加载。

这个加载步骤是异步的,完成后再进行下一步的渲染操作了。

我们要像 Canvas 2D 一样,找一个 canvas 元素,基于它创建绘制表面 surface 对象。

const surface = CanvasKit.MakeWebGLCanvasSurface('stage');

CanvasKit.MakeWebGLCanvasSurface() 方法接受一个  id 选择器字符串,或一个 Canvas HTML 元素

该方法会尝试使用 WebGL 的方式做图形的 GPU 渲染,如果浏览器不支持,会回滚为 CPU 软渲染。

另外可以用 CanvasKit.MakeSWCanvasSurface() 方法,直接使用基于 CPU 的软渲染(Software Rendering)。

还有个  CanvasKit.MakeGPUCanvasSurface() 方法,是基于 WebGPU 的,需要传入 WebGPUCanvasContext 对象。

绘制圆角矩形

接下来我们来绘制一个圆角矩形。

创建圆角矩形。

const roundRect = CanvasKit.RRectXY(
  CanvasKit.XYWHRect(5050200100), // 矩形
  10// 圆角的 x 值,rx (准确来说是椭圆角)
  10// 圆角的 y 值,ry
);

这个方法返回的是一个 Float32Array 类型数组。

之所以用类型数组,是因为我们需要和 wasm 通信,wasm 支持传入的参数有各种类型。所以你会发现 CanvasKit 有大量方法会返回类型数组。

CanvasKit 里面提供了各种工具方法,上面的 CanvasKit.XYWHRect() 方法指定矩形的左上角位置和宽高。此外我也还可用 CanvasKit.LTRBRect()  基于左上右下点来得到矩形。

接着创建画笔对象 Paint,该对象用来描述如何对图形进行填充或描边。

这里我们要绘制一个 2px 的红色描边的圆角矩形。

const paint = new CanvasKit.Paint(); // 创建 paint 对象
paint.setColor(CanvasKit.Color4f(0.9001.0)); // 设置红色
paint.setStyle(CanvasKit.PaintStyle.Stroke); // 填充还是描边,这里选择描边
paint.setStrokeWidth(2); // 线宽
paint.setAntiAlias(true); // 抗锯齿

CanvaKit 被 new 创建的对象,或者方法前置为 Make 创建的对象,都是在 wasm 中创建的,而 wasm 并没有 GC(自动垃圾回收)。所以你需要在合适的时候手动删除,比如绘制结束后。写法为:

paint.delete()

最后绘制到画布上。

surface.drawOnce((canvas) => {
  canvas.clear(CanvasKit.WHITE); // 用白色清空画布
  canvas.drawRRect(roundRect, paint); // 绘制圆角矩形
});

图片

绘制普通矩形

绘制没有圆角的普通矩形。

canvas.drawRect(CanvasKit.XYWHRect(5050200100), paint);

图片

绘制椭圆

绘制椭圆也是类似的,提供一个矩形数据。

canvas.drawOval(CanvasKit.XYWHRect(5050200100), paint);

图片

绘制圆形

指定圆形的圆心和半径。

canvas.drawCircle(
  100// cx
  100// cy
  80// radius
  paint,
);

图片

绘制直线

指定直线的起点和终点。

canvas.drawLine(2020140100, paint);

图片

绘制椭圆弧

需要指定包围椭圆的矩形、起始角度、结束角度、是否使用中心。

canvas.drawArc(
  CanvasKit.XYWHRect(5050200100), // oval,包围椭圆的矩形
  0// startAngle,起始角度。极轴坐标中正右方向为 0 度,角度方向为顺时针
  90// endAngle,结束角度
  true, // useCenter,是否使用中心,即是否额外加多两条连接到圆心的线
  paint,
);

需要特别注意的是,这个方法传入的 角的单位是角度,而不是弧度。这个比较罕见。

图片

绘制点

提供点数组来批量绘制点。

使用点模式

canvas.drawPoints(
  CanvasKit.PointMode.Points, // 模式为点模式
  [20, 20, 50, 60, 90, 30, 150, 60], // 点的集合
  paint,
);

渲染了一些点,注意它们是方形的。

图片

改为绘制线模式,会按顺序将两个点为一组,绘制一条线

canvas.drawPoints(
  CanvasKit.PointMode.Lines, // 模式为线模式
  [20, 20, 50, 60, 90, 30, 150, 60], // 点的集合
  paint,
);

图片

最后是 多段线模式,多个点依次相连。

canvas.drawPoints(
  CanvasKit.PointMode.Polygon, // 模式为多段线模式
  [20, 20, 50, 60, 90, 30, 150, 60], // 点的集合
  paint,
);

图片

绘制路径

绘制路径是比较复杂的命令,我们可以用它绘制复杂的图形。

const path = new CanvasKit.Path();
path
  .moveTo(20150)
  .lineTo(5060)
  .arc(8080200, Math.PI) // 圆弧
  .cubicTo(90301506019090); // 三阶贝塞尔曲线
canvas.drawPath(path, paint);

图片

绘制图片

const imgData = await fetch('./fe_watermelon.jpg').then((res) =>
  res.arrayBuffer(),
);
const img = CanvasKit.MakeImageFromEncoded(imgData);

canvas.drawImage(img, 10, 10);

drawImage 是绘制图片中最简单的方法,把图片原封不动地绘制到画布上。

除了该方法还有其他的高级方法,比如 drawImageRect、drawImageNine,根据需求选择使用。

图片

绘制文字

虽然用了 wasm,但权限并不会扩大,依旧是拿不到本地的字体数据。

又因为没有使用 canvas 2d,没法通过指定字体名的方式使用本地字体,所以 canvaskit 需要额外加载字体数据,才能进行文字渲染。

const fontData = await fetch('../../fonts/SourceHanSansCN-Regular.otf').then(
  (res) => res.arrayBuffer(),
);

const fillPaint = new CanvasKit.Paint();
fillPaint.setColor(CanvasKit.Color(0, 0, 0, 1));
fillPaint.setStyle(CanvasKit.PaintStyle.Fill);
fillPaint.setAntiAlias(true);

const typeface = CanvasKit.Typeface.MakeFreeTypeFaceFromData(fontData)!;
const font = new CanvasKit.Font(typeface, 24);
// 绘制文字
canvas.drawText('AW 123 西瓜测试', 50, 50, fillPaint, font);

图片

看着还处理了字距(kerning)的场景。

绘制文字段落

绘制文字段落,是 canvaskit 比较特别的能力

渲染一段文字,宽度固定为 290px,文字超过会自动换行,高度自适应。

const fontMgr = CanvasKit.FontMgr.FromData(fontData);
// 段落样式
const paraStyle = new CanvasKit.ParagraphStyle({
  textStyle: {
    color: CanvasKit.BLACK,
    fontFamilies: ['SourceHanSansCN-Regular'],
    fontSize: 28,
  },
  textAlign: CanvasKit.TextAlign.Left,
});

const text =
  'Any sufficiently entrenched 一行白鹭上青天,春江水暖鸭先知。technology is indistinguishable from Javascript';
const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
builder.addText(text);
const paragraph = builder.build();
paragraph.layout(290); // 设置包裹文字的宽度

canvas.drawParagraph(paragraph, 10, 10);

canvas.drawRect(CanvasKit.XYWHRect(10, 10, 290, 300), paint); // 辅助参照用

图片

结尾

我是前端西瓜哥,关注我,学习更多前端图形学知识。


相关阅读,

Web 端图形渲染方案这么多,到底该选哪一种?

Canvas 简单入门小教程