Canvas2D 及其实现

优质
小牛编辑
128浏览
2023-12-01

Canvas是HTML5新引入的元素,它是一个画布。开发者可以用JavaScript脚本在该元素上绘制任意图形(2D或者3D)。Canvas元素有两个属性“width”和“height”,用来设置画布的宽度和高度。Canvas本身来讲并没有定义绘制图形的动作和行为,只是提供了一个获取绘图的上下文(context)对象的方法-getContext来获取绘制2D或者3D上下文。

Canvas的‘getContext’方法包含一个参数,该参数用来指定创建上下文对象的类型。对于2d的图形操作,通过传递参数值’2d’,浏览器会返回一个2d的绘图上下文,称为CanvasRenderingContext2D, 它提供了用于绘制2d图形的各种API, 包括基本图形绘制(例如线,矩形,圆弧),文字绘制,图形变换,图片绘制及合成等。我们把以上这些称之为Canvas2D。下图是一个使用Canvas2d的简单例子,后面的讨论都会基于它来进行。

enter image description here

Canvas 2D是HTML5草案的一部分,被众多的浏览器所支持, 包括chrome, IE, Firefox, Safari等等。 不仅如此,越来越多的浏览器开始支持在Canvas中绘制3D图形,该部分规范由Khronos组织定义。 也就是,通过传入适当的参数来调用Canvas的getContext函数以创建3D上下文对象用于绘制3D图形, 目前不同的浏览器支持不同的参数,例如chromium支持“webkit-3d”和”experimental-webgl”。

值得注意的是,2D和3D是互斥的,不能同时在同一个canvas中操作它们,也就是说,当创建了一个2D的上下文对象后,你不能再为其创建3D的上下文,反之亦然。本章将重点介绍Canvas 2D方面的只是及在WebKit和chromium中的实现, Canvas 3D也就是WebGL将在下一章中作介绍。

CanvasRenderingContext2D 介绍

前面说到,CanvasRenderingContext2D是2d图形绘制的上下文对象,其提供了用于绘制2d图形的API,W3C工作组起草了标准的草案,详细见参考文献2。该对象由JavaScript代码创建后,JavaScript便可以调用它的API在画布上绘制图形了。这些API主要的作用就是在画布上绘制点,线,矩形,弧形,图片等等,除此之外,还提供了这些绘制的样式和合成效果等。下面按类简单介绍一下它所包含这些API。

第一部分,如下两个函数save和restore用来保存或者恢复2D上下文的状态;

enter image description here

第二部分,用来设置变换参数,例如缩放,旋转,平移等,这些构成了一个变换矩阵,缺省是Identity矩阵;

enter image description here

第三部分,对象的两个属性globalAlpha和globalCompositeOperation,用来设置alpha值和合成模式,alpha值表示将要绘制图形的透明度,合成模式表示将要绘制的图形和绘制对象如何合成,合成模式有多种,例如source-over, source-in, source-out, destination-over,destination-in, destination-in等等;

enter image description here

第四部分,有关设置颜色和样式,包括两个属性strokeStyle, fillStyle,gradient, pattern, 用来表示笔画和填充的格式,变化率,和用来填充图片的模式

enter image description here

第五部分,有关线的样式属性,它们包括线宽度,线端样式及其线与线的连接方式等;

enter image description here

第六部分,有关阴影方面的属性,这是全局的,影响所有绘制的图形;

enter image description here

第七部分,绘制矩形的相关操作,包括清除矩形区域,填充和绘制矩形边框;

enter image description here

第八部分,Path相关的操作,用来绘制自定义的路径的图形,这可以是图形可以是直线,弧形,曲线等;

enter image description here

第九部分,与文字相关的操作,包括字体,文字对齐方式等属性和绘制文字等操作;

enter image description here

第十部分,绘制图像到画布上,图像来源可以Image元素,Canvas元素,或者是Video元素。通过设置的样式,可以把待绘制的图像以不同的方式绘制到目标的Canvas上;

enter image description here

第十一部分,有关像素方面的操作,包括ImageData创建,以及从canvas读取内容创建ImageData和把ImageData内容写到canvas;

enter image description here

第十二部分,定义与上面一些API相关的类(接口),例如CanvasGradient,CanvasPattern等,这些类街头连同上面的API都被定义WebIDL格式。

通过以上解释,那么canvas2d例子中的代码就比较容易理解了。那段JS代码首先创建一个2D的上下文对象,然后设置填充的颜色为红色,最后填充一个80x100的矩形内部为红色。你可以尝试把例子放在chromium里面运行然后看看能得到什么。值得提醒的是,当画布的宽度或者高度发生改变时,该画布上的所有绘制的内容都将被移除。

WebKit和Chromium的支持

W3C定义了2D context标准的草案, 这些接口保存在IDL文件中。 WebKit根据这IDL来直接生成相关的C++类的代码,这些类包括CanvasRenderingContext2D,CanvasPattern, CanvasGradient, ImageData, TextMetrics等, 它们和标准中的接口一一对应,你可以在WebKit/Source/WebCore/html/canvas中找到它们。那么它们被JS中的代如何码调用的呢?答案是生成JavaScript绑定。

Canvas2D上下文类的具体绘制动作由实现平台决定, 其由软件或者硬件来完成取决于移植。下面, 我们来看看WebKit如何支持Canvas2D以及如何提供合适的接口将实现交给移植来完成的。

首先,大致理解一下WebKit和chromium中支持Canvas2D的重要的类模块和它们之间关系,如下图所示。同以前一样, 包括WebKit的基础类和与平台相关的绘图类及支持canvas2D硬件加速的类。

enter image description here

让我们简单了解一下这些类的作用:

  • WebKit端:HTMLCanvasElement:DOM中的对应着HTML5 的canvas元素,该类包含有关为2D或者3D context服务的相关接口,主要的作用创建JS使用的2D或者3D上下文对象,绘图的平台无关的GraphicsContext对象,后端存储的buffer
  • GraphicsContext:WebKit的平台无关的上下文类,用于绘制工作,具体调用平台相关的上下文类来实现绘制
  • PlatformGraphicsContext:平台绘制上下文类,不同的平台有不同的实现,在chromium中是PlatformContextSkia
  • ImageBuffer:WebKit平台无关后端存储类,不同平台会定义不同的结构,在chromium中会使用SkCanvas
  • RenderHTMLCanvas:RenderObject的子类,为canvas而设计的

Chromium端:

  • PlatformContextSkia: Chromium中的PlatformGraphicsContext类
  • SkCanvas:skia画布,包含所有的绘制状态,使用SkDevice来绘制图形
  • SkDevice:设备类,包含一个SkBitmap作为后端,利用光栅扫描算法来生成像素值保存于后端存储中,用于软件绘制方案
  • SkGpuDevice:设备类,包含一个绘制的目标对象,通过GrContext来绘制,其利用硬件加速的GL库来绘制2D图形
  • GrContext: GPU绘制的上下文类,包含一个平台相关的3D上下文成员
  • Canvas2DLayerChromium:LayerChromium的子类,包含一个硬件加速的Canvas2D层 其他类:之前介绍过,不再赘述上图中加速部分相关的GraphicsLayer, CCLayerImpl等,因为在之前介绍过,为方便起见,图中省略了它们。

Chromium中的Canvas2D的绘制操作的实现都是由图形库skia来完成,这里包括软件和硬件加速实现,chromium所要做的就是把WebKit中的调用交给skia来执行并和自己的绘制模型和硬件加速机制集成起来。 那么如何打开或者关闭Canvas2D的硬件加速功能呢? Chromium中提供了两个选项,分别是“--disable-accelerated-2d-canvas”和“--enable-accelerated-2d-canvas”,用户可以通过查看“about://gpu”来确认。

在介绍了基础设施之后,下面来看看具体的软件和硬件加速如何实现Canvas2D的。后面的软件实现和硬件加速实现都基于本章开始的canvas例子来说明。

Canvas 2D的软件实现

这里我们以本章中的Canvas2D的例子来说这个过程。

首先,当执行到JS代码中的canvas.getContext时,WebKit通过V8 JS绑定会调用HTMLCanvasElement.getContext。该函数根据传入的参数来决定创建2D或者3D的上下文对象。 在这里, CanvasRenderingContext2D对象会被创建。此时其他有关的对象例如ImageBuffer,GraphicsContext等不会被创建,直到后面使用到时才会被创建,这是WebKit的做事原则。

其次,当设置fillStyle属性时,WebKit同样通过V8 JS绑定调用CanvasRenderingContext2D.setFillStyle,在这种情形下,2D上下文对象会开始创建相关对象(在软件实现情况下,上图中右侧框中的对象不会被创建,包括ImageBuffer, GraphicsContext, PlatformContextSkia, SkCanvas,SkDevice和 SkBitmap)。

当执行JS的fillRect时,CanvasRenderingContext2D调用GraphicsContext来完成绘制。 这两个类是WebKit的基础类,GraphicsContext需要有不同移植来具体实现绘制工作,在chromium中,这就是PlatformContextSkia。该类是一个转接口,调用skia来绘制。

SkCanvas根据之前设定的样式,由SkDevice利用光栅扫描法来计算生成相应的像素值,结果保存在一个SkBitmap中。

最后,当fillRect操作调用完成之后,会安排一个Invalidate相关区域的命令。而后,当该命令被执行了,WebKit会遍历RenderLayer依次绘制RenderObject的内容,当绘制Canvas元素时,会把之前Canvas绘制在SkBitmap的内容绘制到网页的Bitmap上(该bitmap通过共享内存机制会被Browser进程绘制在Tab子窗口中)。下图显示的是当有更新请求时,WebKit遍历RenderLayer树和Render树来绘制网页及Canvas的详细调用过程。

enter image description here

Canvas 2D的硬件加速实现

在硬件加速实现Canvas情况下,有一些地方是相似的,下面主要介绍它们的不同之处。

首先,硬件加速需要创建更多的对象和设施,主要有两点,一是如前面章节介绍的,会为Canvas元素创建一个新的RenderLayer及其相应的GraphicsLayer,Canvas2DLayerChromium,CCLayerImpl等。二是因为利用GL来渲染,所以为skia的SkCanvas创建一个SkGpuDevice, GrTexture, GrContext来使用GL绘制2D图形,同时,跟合成器一样,它也会创建3D的上下文对象- WebGraphicsContext3DCommandBufferImpl, 将skia的GrContext, GrTexture等对gl调用转发给GPU进程,具体的创建过程可参考ImageBufferSkia.cpp文件中的createAcceleratedCanvas函数。

其次,当调用fillRect时,canvas的内容由SkCanvas调用SkGpuDevice将其绘制在Texture,当然这些GL的操作都通过WebGraphicsContext3DCommandBufferImpl交给GPU进程来完成绘制。最后,会请求一个更新某个区域的任务。

最后,更新请求会调度合成操作,其首先调用Canvas2DLayerImpl::paintContentsIfDirty绘制自己,然后将Canvas的Texture合成起来生成网页内容。

源文件目录

WebKit/Source/WebCore/html/canvas/
           WebKit支持Canvas相关的类,包括支持2D和3D上下文及其所涉及的其他类
WebKit/Source/WebCore/platform/graphics/skia/
           实现WebKit中的平台相关的2D图形功能,Canvas2D在chromium中的实现依赖skia图形库

参考文献

  1. HTML5 canvas element http://www.w3schools.com/html5/html5_canvas.asp
  2. Canvas2D context http://www.w3.org/TR/2dcontext/
  3. WebKit HTMLCanvasElement http://developer.apple.com/library/safari/#documentation/WebKit/Reference/HTMLCanvasElementRef/HTMLCanvasElementClassReference.html#//apple_ref/doc/uid/TP4000947