Canvas 的基本原理

news/2024/9/8 23:08:48

过个年一下荒废了个把月。 最近刚接触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;

http://lihuaxi.xjx100.cn/news/245249.html

相关文章

离线安装k8s 1.9.0

说明本文参考 https://segmentfault.com/a/1190000012755243。在前 文基础上整理、增加说明&#xff0c;避坑。 踩过的坑&#xff1a; 安装k8s 1.9.0 实践&#xff1a;问题集锦 环境说明 环境信息&#xff08;采用一个master节点两个node节点) 192.168.1.137 tensorflow0 node …

妹子在生产服务器执行了 rm -rf /*,还好有我帮她恢复了

点击上方"搜云库技术团队"关注选择"设为星标"技术/ 架构 / 资料 / 工作 / 内推经历了两天不懈努力&#xff0c;终于恢复了一次误操作删除的生产服务器数据。对本次事故过程和解决办法记录在此&#xff0c;警醒自己&#xff0c;也提示别人莫犯此错。也希望遇…

提前了解客户背景很有必要

2019独角兽企业重金招聘Python工程师标准>>> 最近&#xff0c;公司与某电商展开了合作&#xff0c;我司将为该电商提供提供一套广告解决方案。我有幸参与到这个项目&#xff0c;了解该电商的需求&#xff0c;思考公司的产品是否能够和如何满足这些需求。近日&#x…

【python】编程语言入门经典100例--6

题目&#xff1a;用*号输出字母C的图案。转载于:https://blog.51cto.com/netsyscode/1743964

大数据处理也要安全--关于MaxCompute的安全科普

[TOC] 1.企业大数据处理现状 当今社会数据收集手段不断丰富&#xff0c;行业数据大量积累&#xff0c;数据规模已增长到了传统软件行业无法承载的海量数据&#xff08;百GB、TB乃至PB&#xff09;级别。基于此&#xff0c;阿里云推出有了一套快速、完全托管的GB/TB/PB级数据仓库…

Linux基础网络服务概述

童鞋们前面我们讲解了Linux命令基础知识&#xff0c;在这里我就不多言啦&#xff0c;直接进入我们今天的焦点—服务。再说服务之前我考虑了许久怎么样让大家能更清楚的明白服务是怎么一回事儿&#xff1f;这里我就拿我们身边的例子来给大家讲解下我们都是知道一座大厦有一个地址…

QQ群功能设计与心理学

2019独角兽企业重金招聘Python工程师标准>>> 刚刚在一个Java技术交流群&#xff0c;发了个 "博客投票"的广告。 群主两眼一黑&#xff0c;瞬间就把我给干掉了。 看到QQ给出的系统消息&#xff0c;发现QQ群的一个功能做得很不错。 大家注意到&#xff0c;右…

Apache项目 架构师的30条设计原则

点击上方“搜云库技术团队”&#xff0c;选择“设为星标”回复“1024”获取独家整理的学习资料本文作者叫 Srinath&#xff0c;是一位科学家&#xff0c;软件架构师&#xff0c;也是一名在分布式系统上工作的程序员。他是 Apache Axis2 项目的联合创始人&#xff0c;也是 Apach…