Canvas 的基本原理

过个年一下荒废了个把月。 最近刚接触canvas,将一些概念点简单归纳下,canvas是基于像素的图像API,与svg的最大的区别在于canvas需要重绘(canvas移除图片时需要重新绘制,而SVG可以通过编辑元素节点来编辑图片),并且基于基于像素绘制(svg顾名思义是矢量),更详细的对比mark在此:?SVG 与 HTML5 的 canvas 各有什么优点 而且我个人认为虽然canvas的API也很复杂,但是svg更复杂,囧rz。以下是我将我接触canvas过程中认为需要厘清的概念点归纳如下。

基础结构

canvas元素本身没有任何外观,它就是一块空白画板,提供给JS的一套API,最早由Safari引入,IE9之前可以使用一些类库在IE中模拟canvas,大部分的API都不在canvas元素自身定义,canvas元素自身属性与常规的HTML元素并没有多大区别, 它的绘图API都定义在一个CanvasRenderingContext2D对象上(这里简单翻译成上下文对象),该对象通过getContext()方法获得,代码示例:

<html>
<head>
<title>坐标系demo</title>
</head>
<body>
<canvas id = 'square' width= 200 heigth=200></canvas>
</body>
<script>
var canvas = document.getElementById('square')
var ctx = canvas.getContext('2d')//2d表示画板维度,输入3d将得到一个更为复杂的3d图形API,也称WebGL

默认坐标系与当前坐标系

图像绘制需要参考坐标系定位, canvas的默认坐标系即画布左上角原点(0,0),但是如果图像的每次绘制都参考一个固定点将缺少灵活性,于是canvas引入了“当前坐标系”的概念,所谓“当前坐标系”即指图像在此时绘制的时候所参考的坐标系,它也会作为图像状态(图像状态的概念将在后介绍)的一部分。比如rotate旋转操作,改变当前坐标系也就是改变了rotate的参考点,试想下如果没有当前坐标系的概念,无论是旋转,缩放,倾斜等操作不就只能参考画布左上角原点了吗(默认坐标系)?

canvas提供了translate()setTransform()这两个方法分别影响当前坐标系与默认坐标系。

translate()setTransform()方法

translate()方法将坐标原点进行上下左右移动。它所影响的就是在图像在绘制的时候所参考的“当前坐标系”,举个例子?:

可以直接在demo中操作观察:

坐标系DEMO

代码:

<html>
<head>
<title>坐标系demo</title>
</head>
<body>
<canvas id = 'square' width= 200 heigth=200></canvas>
</body>
<script>
var canvas = document.getElementById('square')
var ctx = canvas.getContext('2d')

ctx.beginPath()
ctx.translate(20,20) //translate影响了当前坐标系
ctx.moveTo(0,0)
ctx.lineTo(100,20)
ctx.stroke()
</script>
</html>

无任何坐标系变化的图像绘制:

图片描述

translate()方法将坐标原定移动到(20,20)后得到当前坐标系后的绘制

图片描述

了解这点后setTransform()也很容易,该方法影响的是默认坐标系,也就是说它并非将原点移来移去,而是重置当前坐标系,定义一个新的默认坐标系,什么叫影响默认坐标系,比如说前面的translate()所移动的坐标原点(0,0)还是初始的默认坐标系,而现在setTransform()所影响的就是这个原点(0,0)的坐标系,还是之前的demo,当加入ctx.setTransform(1,0.5,-0.5,1,30,10)这条语句后,图像绘制将变成:

图片描述

这是因为setTransform()将默认坐标系重新定义了,于是translate()基于新的默认坐标系来得到当前坐标系。理解了这两个概念也就掌握了canvas中坐标系的变换。

setTransform()transform()方法

setTransform()这个API略复杂, 它所接受的参数与transform()(使用transform()可直接得到一个变换结构,可代替rotate()等方法,并且更为灵活)一样为6个参数,setTransform(a,b,c,d,e,f) 而坐标系变化的原理就是通过与这6个参数进行以下运算后得出的:

x' = ax + cy +e
y' = bx + dy +f

这种坐标系变换也被称为仿射变换(affine transform),关于该变换的栗子可参考这两篇博客:
?Html5 Canvas 变换矩阵与坐标变形之间的关系

路径

路径是绘制所有图形的基础,不同于SVG中path使用属性M,L,A等控制的XML文档,canvas调用上下文对象的方法来完成路径的绘制,调用beginPath()开始一段新路径,每段路经又有子路径,正是依靠这些子路径使得图形成形。调用beginPath()后调用MoveTo()开始一段子路径。绘制完成后使用closePath()闭合路径,从而形成一个闭合区域,这时候就可以使用fill()等方法填充该区域了。每次开始一段新路径的绘制必须再次调用beginPath(),否则新绘制的路径将作为之前路径的子路径继续绘制。

类似于lineTo()是最简单的直线段路径方法, canvas还提供了bezierCurveTo()quadraticCurveTo()这些复杂的曲线路径方法,非常复杂,所以估计一般这种操作还是先找轮子解决。

另外需要注意的是,当一条路径的两条子路径不相交的时候(比如绘制一个镂空的图形),画布将采用“非零绕数原则”判断某点是在路径内还是路径外, 这样以便于填充的时候区别哪些区域是需要填充的。

有关非零绕数原则的原理可以参考这里:mark? 非零环绕数规则和奇-偶规则

canvas的图像状态

canvas的属性与方法与我们面向对象中的属性方法并没有太大区别,只是这里涉及到了一个图像状态的概念。在canvas中,无法通过getContext()方法获得多个上下文(context)对象,而图像属性都是基于canvas的上下文对象,也就是说无法同时拥有两个属性。形象地比喻就是图像属性就像画笔, 粗细,大小,颜色。由于同一时间只能有一个上下文对象所以只能同一时间使用一支画笔。这时候当需要其它的图像属性(另一支画笔)的时候就只能通过保存当前图像状态,然后新建一个图像状态来切换。

这时候就需要借助save()restore()来切换图像状态,每次save()都将保存当前图像状态,图像状态包括当前的图像属性,当前坐标系,裁剪区域等信息。比如以下demo以两种颜色画线:

直接在demo中修改代码观察图像状态demo
JS代码:

var canvas = document.getElementById('square')

var ctx = canvas.getContext('2d')

ctx.beginPath()
ctx.strokeStyle = "red"
ctx.moveTo(0,0)
ctx.lineTo(100,20)
ctx.stroke()
ctx.save()//保存当前图像状态(画笔)

ctx.beginPath()
ctx.strokeStyle = "blue"
ctx.moveTo(0,0)
ctx.lineTo(100,40)
ctx.stroke()
ctx.restore()//恢复到最近保存图像状态(画笔)

ctx.beginPath()
ctx.moveTo(0,0)
ctx.lineTo(100,60)
ctx.stroke()

输出如下:

图片描述

这些图像属性包括:

  • fillStyle

  • font

  • globalAlpha

  • globalCompositeOperation

  • lineCap

  • lineJoin

  • lineWidth

  • miterLimit

  • textAlign

  • textBaseline

  • shadowBlur

  • shadowColor

  • shadowOffsetX

  • shadowOffsetY

  • strokeStyle

canvas背景

一般的纯色背景填充可以使用fillStyle属性,但是当涉及更复杂的图片或者渐变色填充就需要CanvasPatternCanvasGradient对象了,可以通过creatPattern()方法得到CanvasPattern,这里需要注意的是该API不仅可以代入一般的图片,也可以使用canvas元素,比如画面外一个不可见的canvas元素用于插入。
关于这两个API的细节直接参考文档:

?CanvasPattern
?CanvasGradient

像素操作

基于像素的canvas可以实现针对单个像素的操作,这也是画布底层的API,通过调用getImageData()方法将返回一个ImageData对象,该对象表示画布中原始的RGBA像素信息,通过调用creatImageData()方法也可以创建一个空的ImageData对象,最后putImageData()方法将处理后的像素输出到画布中。

微软有篇不错的教程(使用 Canvas 将彩色照片变成黑白照片)解释像素操作,其中的操作是将彩色照片转成灰白,使用的原理是将RGB三个分量提取出来,经过计算后(关键计算语句如下)重新赋值为灰度变量。

myGray = parseInt((myRed + myGreen + myBlue) / 3);

          // Assign average to red, green, and blue.
          myImage.data[i] = myGray;
          myImage.data[i + 1] = myGray;
          myImage.data[i + 2] = myGray;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
HTML5 CANVAS游戏开发实战(PDF和源代码) 第一部分 准备工作篇 第1章 准备工作 / 2 1.1 html5介绍 / 2 1.1.1 什么是html5 / 2 1.1.2 html5的新特性 / 2 1.2 canvas简介 / 5 1.2.1 canvas标签的历史 / 5 1.2.2 canvas的定义和用法 / 6 1.2.3 如何使用canvas来绘图 / 6 1.2.4 canvas的限制 / 7 1.3 开发与运行环境的准备 / 7 1.3.1 浏览器的支持 / 7 1.3.2 准备一个本地的服务器 / 8 1.4 开发工具的选择 / 8 1.5 测试与上传代码 / 12 1.6 javascript中的面向对象 / 13 1.6.1 类 / 13 1.6.2 静态类 / 16 .1.6.3 继承 / 16 1.7 小结 / 17 第二部分 基础知识篇 第2章 canvas基本功能 / 20 2.1 绘制基本图形 / 20 2.1.1 画线 / 20 2.1.2 画矩形 / 22 2.1.3 画圆 / 24 2.1.4 画圆角矩形 / 26 2.1.5 擦除canvas画板 / 27 2.2 绘制复杂图形 / 28 2.2.1 画曲线 / 28 2.2.2 利用clip在指定区域绘 图 / 30 2.2.3 绘制自定义图形 / 31 2.3 绘制文本 / 32 2.3.1 绘制文字 / 32 2.3.2 文字设置 / 33 2.3.3 文字的对齐方式 / 38 2.4 图片操作 / 41 2.4.1 利用drawimage绘制图片 / 41 2.4.2 利用getimagedata和putimagedata绘制图片 / 45 2.4.3 利用createimagedata新建像素 / 47 2.5 小结 / 49 第3章 canvas高级功能 / 50 3.1 变形 / 50 3.1.1 放大与缩小 / 50 3.1.2 平移 / 53 3.1.3 旋转 / 54 3.1.4 利用transform矩阵实现多样化的变形 / 56 3.2 图形的渲染 / 65 3.2.1 绘制颜色渐变效果的图形 / 65 3.2.2 颜色合成之globalcompositeoperation属性 / 67 3.2.3 颜色反转 / 69 3.2.4 灰度控制 / 70 3.2.5 阴影效果 / 71 3.3 自定义画板 / 72 3.3.1 画板的建立 / 72 3.3.2 canvas画布的导出功能 / 79 3.4 小结 / 81 第4章 lufylegend开源库件 / 82 4.1 lufylegend库件简介 / 82 4.1.1 工作原理 / 82 4.1.2 库件使用流程 / 83 4.2 图片的加载与显示 / 84 4.2.1 图片显示举例 / 84 4.2.2 lbitmapdata对象 / 86 4.2.3 lbitmap对象 / 87 4.3 层的概念 / 88 4.4 使用lgraphics对象绘图 / 90 4.4.1 绘制矩形 / 90 4.4.2 绘制圆 / 91 4.4.3 绘制任意多边形 / 92 4.4.4 使用canvas的原始绘图函数进行绘图 / 93 4.4.5 使用lsprite对象进行绘图 / 94 4.4.6 使用lgraphics对象绘制图片 / 95 4.5 文本 / 101 4.5.1 文本属性 / 101 4.5.2 输入框 / 102 4.6 事件 / 103 4.6.1 鼠标事件 / 103 4.6.2 循环事件 / 104 4.6.3 键盘事件 / 105 4.7 按钮 / 106 4.8 动画 / 108 4.9 小结 / 113 第三部分 开发实战篇 第5章 从简单做起—“石头剪子布”游戏 / 116 5.1 游戏分析 / 116 5.2 必要的javascript知识 / 117 5.2.1 随机数 / 117 5.2.2 条件分支 / 117 5.3 分层实现 / 117 5.4 各个层的基本功能 / 119 5.4.1 基本画面显示 / 119 5.4.2 结果层的显示 / 126 5.4.3 控制层的显示 / 127 5.5 出拳 / 129 5.6 结果判定 / 131 5.7 小结 / 137 第6章 开发“俄罗斯方块”游戏 / 138 6.1 游戏分析 / 138 6.2 必要的javascript知识 / 138 6.3 游戏标题画面显示 / 139 6.4 向游戏里添加方块 / 141 6.5 控制方块的移动 / 152 6.5.1 键盘事件 / 152 6.5.2 触屏事件 / 155 6.6 方块的消除和得分的显示 / 157 6.7 小结 / 160 第7章 开发“是男人就下一百层”游戏 / 161 7.1 游戏分析 / 161 7.2 游戏标题画面显示 / 161 7.3 读取图片与背景显示 / 162 7.4 添加一个静止的地板 / 167 7.5 添加游戏主角 / 170 7.5.1 让游戏主角出现在画面上 / 170 7.5.2 通过键盘事件来控制游戏主角的移动 / 177 7.5.3 通过触屏事件来控制游戏主角的移动 / 178 7.6 添加多种多样的地板 / 179 7.6.1 会消失的地板 / 179 7.6.2 带刺的地板 / 181 7.6.3 带有弹性的地板 / 182 7.6.4 向左和向右移动的地板 / 184 7.7 游戏数据的显示 / 187 7.8 游戏结束与重开 / 190 7.9 小结 / 192 第8章 开发射击类游戏 / 193 8.1 游戏分析 / 193 8.2 添加一架可控飞机 / 194 8.2.1 添加一个飞机类 / 194 8.2.2 可控飞机类 / 197 8.3 为飞机添加多样化的子弹 / 203 8.3.1 建立一个子弹类 / 203 8.3.2 单发子弹 / 205 8.3.3 多发子弹 / 207 8.3.4 环形子弹 / 208 8.3.5 反向子弹 / 209 8.4 添加敌机 / 209 8.4.1 建立一个敌机类 / 210 8.4.2 建立一个敌机boss类 / 214 8.5 碰撞检测 / 217 8.5.1 飞机与子弹的碰撞 / 217 8.5.2 我机与敌机的碰撞 / 220 8.6 子弹的变更 / 221 8.6.1 建立一个弹药类 / 222 8.6.2 弹药与我机的碰撞 / 223 8.7 飞机生命值的显示 / 225 8.8 游戏胜利与失败判定 / 226 8.9 小结 / 228 第9章 开发物理游戏 / 229 9.1 box2d简介 / 229 9.2 box2dweb在lufylegend库件中的使用 / 229 9.3 创建各种各样的物体 / 234 9.3.1 矩形物体 / 234 9.3.2 圆形物体 / 237 9.3.3 多边形物体 / 239 9.4 响应鼠标拖拽物体 / 242 9.5 关节(joint) / 243 9.5.1 距离关节(b2distancejointdef) / 243 9.5.2 旋转关节(b2revolutejointdef) / 245 9.5.3 滑轮关节(b2pulleyjointdef) / 247 9.5.4 移动关节(b2prismaticjoint) / 248 9.5.5 齿轮关节(b2gearjoint) / 250 9.5.6 悬挂关节(b2linejoint) / 252 9.5.7 焊接关节(b2weldjoint) / 253 9.5.8 鼠标关节(mouse joint) / 254 9.6 力 / 254 9.7 碰撞检测 / 256 9.8 镜头移动 / 260 9.9 做一个简单的物理游戏 / 263 9.10 小结 / 267 第10章 开发网络游戏 / 268 10.1 http通信 / 268 10.1.1 如何实现http通信 / 268 10.1.2 http通信的弊端 / 275 10.2 socket通信 / 275 10.2.1 区分socket通信和http通信 / 276 10.2.2 服务器端 / 276 10.2.3 客户端 / 281 10.3 利用websocket实现简单的聊天室 / 283 10.4 做一款多人在线的坦克大战 / 293 10.4.1 服务器 / 293 10.4.2 客户端 / 293 10.5 小结 / 307 第四部分 技能提高篇 第11章 提高效率的分析 / 310 11.1 绘图时使用小数的影响 / 310 11.2 drawimage和putimagedata的效率比较 / 311 11.3 区域更新和图片大小对绘图效率的影响 / 311 11.4 图片格式对绘图效率的影响 / 313 11.5 优化代码以提高整体效率 / 314 11.5.1 使用位运算 / 314 11.5.2 少用math静态类 / 316 11.5.3 优化算法 / 319 11.6 小结 / 322

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值