一,canvas的开发概念建立
最开始的时候,浏览器只是提供了一个canvas的标签以及它对应的api.来帮助我们在浏览器创建一个画布,以便我们在该画布上绘制想要的各种图案.
这里需要对画板有个基础的概念.canvas和小朋友的玩具画板一样,如下图:
本篇文章主要讲canvas原生的api,让开发者对canvas的开发有个直观的印象.
1.1,面向对象的编程方式
上文说到,canvas是一个画板,那么我们要在这个画板上进行绘画,首先第一步要做的就是拿到这个画板.
<canvas id="canvas" width="900" height="900"></canvas> var canvasEl = document.getElementById("canvas"); var ctx = canvasEl.getContext("2d");//获取到这个画布对象
在我们平时的业务开发中,往往都是面向过程的开发方式.而canvas实际上是个画板对象,使用canvas更多的时候,我们是使用的面相对象的思维来开发.
1.2,绘图的方式
在画板上,画完的东西是不可改的,只能覆盖或者重新画;也无状态,canvas 并不知道你画的是啥,也不知道你鼠标点的是哪个物体,要是想稍微修改下画布上面物体的大小或者让物体动起来,那一定是要清除(整个或局部)画布再重新绘制的。 当重新绘制的速度足够快,那么人眼看起来,画板上的内容就是被修改了或者变成了动画.
具体如下图所示:
二,canvas画板
<canvas id="canvas" width="150" height="150"></canvas>
2.1,画板对象的默认宽高
<canvas>
标签只有两个属性—— width
和 height
,当没有设置宽度和高度的时候,<canvas>
会初始化宽度为 300px
和高度为 150px
。
需要注意的是, 通过 CSS 也可以定义 canvas 的尺寸,但此元素尺寸非彼画布尺寸,在绘制时图像会伸缩以适应它的画布尺寸;如果元素尺寸和画布尺寸比例不一样,绘制出来的图像是扭曲的。
2.2,画板对象的属性设置
我们可以在画板对象上设置样式:
var canvasEl = document.getElementById("canvas"); canvasEl.style.background = 'grey'
2.3,画板对象的坐标体系
canvas的坐标和我们平时的坐标系不一样,左上角是坐标原点(0,0)
2.4,线条默认宽度和颜色
线条的默认宽度是 1px
,默认颜色是黑色。
但由于默认情况下 canvas
会将线条的中心点和像素的底部对齐,所以会导致显示效果是 2px
和非纯黑色问题。
2.5,渲染上下文getContext()
<canvas>
元素创造了一个固定大小的画布,它公开了一个或多个渲染上下文,其可以用来绘制和处理要展示的内容.
canvas 起初是空白的,脚本首先需要找到渲染上下文,然后在它的上面绘制.getContext()
,这个方法是用来获得渲染上下文和它的绘画功能.
我们这篇文章只讲2d绘图功能.
<canvas id="canvas" width="900" height="900"></canvas> var canvasEl = document.getElementById("canvas"); var ctx = canvasEl.getContext("2d");//获取到这个画布对象
这个ctx对象上,就拥有一些属性和方法,利用它,我们能够在这个画板上进行绘图.
2.6,绘图状态和路径
绘图状态和路径是canvas学习过程中最最重要的两个概念,理解了这两个概念,canvas就变得很简单了.
这里只是简单提及,具体还是要在下文细细理解.
我们使用canvas绘制一幅图案通常需要做以下几件事:
1,拿到画板 2,定义好画板和画笔的状态(变形啦,裁剪路径啦,画笔的颜色宽度啦之类的属性)(没有定义则是默认状态) 3,定义好裁剪路径(没有定义则是全画幅) 4,定义好路径 5,基于以上几点,利用stroke,fill,drawImage等api在画板上进行绘制
这里的第二步,就是我们的绘图状态,它通常需要结合ctx.save()和ctx.restore()来使用,以避免绘图状态之间的干扰.
这里的第四步,就是我们定义的路径,路径的定义只是告诉canvas这些路径,而并没有进行绘制.路径则需要经常使用ctx.beginPath()来重新开启一次路径的绘制.
三,绘图状态的定义与存储
当我们在画画前,要做的事情是不是选择一款合适的画笔(当我们没有选择时,canvas会提供默认的)?canvas对象有一系列的属性,来定义该画笔的形态.
3.1,画笔的定义(绘图状态)
绘图状态是canvas中非常重要的一个概念,每次画图,都是基于当前的绘图状态,类比于ps中的画笔加图层.
这就是一般文章中说的:图形上下文对象(CanvasRenderingContext2D)的当前属性值
,具体如下:
- 属性:
- 描边/填充样式:
strokeStyle
,fillStyle
,globalAlpha
- 线的样式:
lineWidth
,lineCap
,lineJoin
,miterLimit
,lineDashOffset
- 阴影:
shadowOffsetX
,shadowOffsetY
,shadowBlur
,shadowColor
, - 字体样式:
font
,textAlign
,textBaseline
,direction
- 平滑质量:
imageSmoothingEnabled
- 合成属性:
globalCompositeOperation
- 描边/填充样式:
- 当前变形
- 当前裁剪路径
比如当我们要画一个矩形时,就可以先定义好状态,再进行绘制:
3.2,画笔的重置
当我们画完一个路径,就需要重置路径,否则会前后绘制的图案之间的画笔属性会相互干扰,具体怎么触发干扰就不展开,这里只讲避免干扰的方法.那就是养成绘制图形前重置路径的习惯.一般我们会声明开启一条新的路径来重置路径.
ctx.beginPath()//告诉canvas开始一条新的路径,这时候可以设定新的画笔属性
这样一来,相当于就是告诉canvas你已经换了一只画笔(当然,如果没有重新设置画笔属性,那么之前的画笔属性将被沿用)
3,3,绘图状态的存储与复用
画过彩绘的都知道,我们画一幅色彩鲜艳的画作,不可能只使用一支笔.在作画过程中,我们可能使用很多支笔,但是我们总不能频繁地定义画笔状态吧?
为了方便起见,canvas提供了一个栈结构来存储画笔的状态.
然后使用如下两个api进行画笔状态的存储与复用:
ctx.save()//存储画笔状态 ctx.restore()//将栈中的画笔状态弹出使用
值得注意的是,这里弹出的画笔状态就是3.1列表中的画笔属性集合.
其实我感觉这样还是不够方便因为画笔状态在栈中,画笔多了,管理很乱(弹出后不再次入栈就会丢失),如果是我设计的话,应该是能让用户自定义画笔预设,不同的画笔预设起自定义的名字,用一个对象Object去存储,用户想要什么画笔,就用Object[‘key’]去取得对应的画笔状态会更方便些.
四,基本图形绘制
有了上文的了解,就能够开始绘制一些基本的图形.
4.1,线条的绘制
4.1.1,单条直线的绘制
想象下,当我们在一个画板上绘制一条线,是不是需要先将笔落点,然后绘制一条直线.于是需要用到这两个方法:
ctx.moveTo(x1, y1):起点坐标 (x, y),也就是将笔尖移动到这一点 ctx.lineTo(x2, y2):下一个点的坐标 (x, y)
需要注意的是,在canvas的绘画体系中,执行了上述方法,只是告诉了canvas我们要画的东西,而实际上还需要执行:
ctx.stroke():将所有坐标用一条线连起来
来将刚刚描述好的图案绘制到画板上.
于是绘制线条的代码和图案如下:
4.1.2,多条直线的绘制
多条直线其实和单条直线差不多,无非就是每画一条新的线的时候,需要先把画笔抬起来,然后重新落点,也就是ctx.moveTo()方法.
4.1.3,折线的绘制
折线的绘制,其实就是一次落笔,多次移动,所以是一次ctx.moveTo,多次lineTo.
4.2,矩形的绘制
4.2.1.矩形的生成
canvas提供了一种矩形的绘制方法
ctx.rect(x, y, width, height)//参数为左上角的xy值和矩形的长宽
当然,这种方法只是告诉了canvas需要画怎样的图形,还没有开始画,如果想要在画板上画出来,还是需要结合ctx.stroke()来画线,或者结合ctx.fill()来填充
既然是描边和填充,肯定需要预先定义画笔状态(strokeStyle和fillStyle),于是代码和效果如下:
canvas还提供了直接绘制描边矩形和填充矩形的方法:
ctx.strokeRect(x,y,with,height); ctx.fillRect(x,y,width,height);
这种写法和上文rect绘制的矩形效果一样:
ctx.beginPath() ctx.lineWidth = 10 ctx.strokeStyle = 'pink' ctx.fillStyle = 'blue' ctx.strokeRect(20,20,100,100) ctx.fillRect(20,20,100,100)
4.2.2,矩形的清除
矩形的清除,其实就是绘制一个canvas背景色的矩形,看起来就是擦除了一块矩形.
clearRect(x, y, width, height)//擦除一个矩形区域 clearRect(0, 0, canvas.width, canvas.height)//清除画布
4.3,多边形的绘制
多边形的绘制,其实就是折线的绘制,但是要处理下折线的闭合,于是相较于折线的绘制,在多边形的绘制上,canvas提供了一个新的api来完成折线的闭合.
它的作用就是将结束点和开始点连接起来,形成一个闭环.
ctx.closePath()
4.4,圆形的绘制
canvas提供了圆形的绘制方法,因为圆形是闭合的几何体,所以必须调用ctx.closePath().
arc(x, y, r, sAngle, eAngle,counterclockwise)//xy是圆心坐标,r是半径,sAngle是开始角度,eAngle是结束角度,counterclockwise是方向,true是逆时针,false是顺时针,默认false
4.4.1,圆形的绘制
圆形的绘制如下图:
又因为我们使用了闭合路径,所以我们可以只绘制一部分圆:
4.4.2,弧线的绘制
通过上文对closePath的理解,想画一段圆弧的话,只要不执行这个api,让圆形不闭合就行.
当然canvas还提供了一个专门绘制圆弧的api
arcTo(cx, cy, x2, y2, radius)//(切线交点x,切线交点y,结束点x,结束点y,半径)
arcTo()
方法利用 开始点、控制点和结束点形成的夹角,绘制一段与夹角的两边相切并且半径为 radius
的圆弧。
4.5,贝塞尔曲线
贝塞尔曲线一般用来绘制有规律的复杂图案,根据控制点的多少,可以分为二次贝塞尔曲线和三次贝塞尔曲线.贝塞尔曲线,实际上就是控制点连接线为切点形成的曲线.
4.5.1,二次贝塞尔曲线
需要先确定一个起始点,然后利用quadraticCurveTo进行绘制,对于起点的设置,就是画图中的笔尖落点moveTo.
这里有个在线调试贝塞尔曲线的网址:二次贝塞尔曲线
quadraticCurveTo(cp1x, cp1y, x, y)//(控制点的x坐标,控制点的y坐标,结束点的x坐标,结束点的y坐标)
4.5.2,三次贝塞尔曲线
三次贝塞尔曲线,实际上就是具备两个控制点的曲线,于是就是在二次贝塞尔曲线的基础上增加一个控制点.
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)//控制点1的坐标,控制点2的坐标,结束点的坐标
这里有个在线调试三次贝塞尔曲线的网址:三次贝塞尔曲线
4.6,path2D对象存储和复用路径
Canvas 2D API 的接口 Path2D
用来声明路径,此路径稍后会被CanvasRenderingContext2D
对象使用.CanvasRenderingContext2D
接口的路径方法也存在于 Path2D 这个接口中,允许你在 canvas 中根据需要创建可以保留并重用的路径。
在上文的绘图中,我们一直都是利用var ctx = canvasEl.getContext(“2d”),在整个画布的绘图上下文ctx上不断地绘制路径,那如果我们想重用某段路径,就需要一个对象来保存该路径.这就是path2D的最大作用.
4.6.1,path2D的创建和复用路径
如下图,通过var path1=new Path2D()创建了一条新的路径,并且通过path1这个变量完成对它的存储.
而后,创建一个新路径pathOther,可以利用addPath命令添加到原有的路径path1上.这样就构建出一条新的路径.
当我们想要复用之前的路径的时候,就可以利用path2=new Path2D(path1)来基于path1创建新的路径,这样就复用了path1.
值得注意的是,我们使用ctx.stroke(path2)的时候,绘制的只是一条路径path2,那也就是说它的颜色等样式只能通过ctx设置一次,无法做到其中的每条子路径不同的样式.
4.6.2,path2D的SVG解析
Path2D API 有另一个强大的功能,可以使用 SVG path data 来初始化 canvas 上的路径,于是我们可以用它来解析SVG.
4.7,判断点是否在路径上
canvas提供了ctx.isPointInStroke(x,y)来判断一个点是否在路径上的方法,在则返回true,否则返回false.
ctx.isPointInStroke(100,0)
ctx.beginPath() ctx.moveTo(50,50) ctx.lineTo(200,50) ctx.stroke() console.log("判断是否在路径上",ctx.isPointInStroke(100,50))//true
4.8,判断点是否在填充区域内
canvas提供了ctx.isPointInPath(x,y)来判断一个点是否在填充范围内的方法,在则返回true,否则返回false.
ctx.isPointInPath(x,y)
ctx.beginPath() ctx.rect(50,50,200,200) ctx.stroke() console.log("判断是否在填充范围内",ctx.isPointInPath(100,100))//true
五,颜色的填充
我们想为图形添加样式和颜色,可以用上这两个属性:
fillStyle
:设置图形的填充颜色。strokeStyle
:设置图形轮廓的颜色。
它俩的默认值都是黑色(#000000
),接受色值、渐变对象和图案对象.
5.1,色值的设置
canvas的颜色支持这几种:
// 色值 ctx.fillStyle = "orange"; ctx.fillStyle = "#FFA500"; ctx.fillStyle = "rgb(255,165,0)"; ctx.fillStyle = "rgba(255,165,0,0.2)"; // 透明度 0.2
5.2,填充对象
填充对象有两种,一种是径向的,一种是线性的.
也是用 线性 或者 径向 的两种渐变形式来新建一个 canvasGradient
对象,并且赋给图形的 fillStyle
或 strokeStyle
属性。
createLinearGradient(x1, y1, x2, y2):渐变的起点 (x1,y1) ,终点 (x2,y2) createRadialGradient(x1, y1, r1, x2, y2, r2):定义了两个圆 - 一个以 (x1,y1) 为原点,半径为 r1 的圆 - 一个以 (x2,y2) 为原点,半径为 r2 的圆
创建出 canvasGradient
对象后,用 addColorStop
方法给它上色:
gradient.addColorStop(position, color);//position是0到1.0之间的数字.表示所在的位置比例,color是需要有效的css值
先看线性的渐变:
再看径向的渐变:
5.3,填充图像
对于图片的填充,第一步,肯定是拿到图片,然后createPattern创建图片填充
ctx.fillStyle = ctx.createPattern(img, 'repeat')
六,线条的样式
6.1,lineWidth线条宽度
设置当前绘线的粗细。默认值是 1.0,必须为正数。
6.2,lineCap线帽
决定了线段端点显示的样子。它的取值如下图所示,从上到下分别是 butt
(默认)、round
、square
。
6.3,虚线
6.3.1,虚线的绘制
ctx.setLineDash(segments);//segments是个数组,用来描述线段和间距,应该是偶数
6.3.2,虚线的偏移量lineDashOffset
lineDashOffset
可以设置虚线的偏移量,默认值为 0.0。
ctx.lineDashOffset = value;
利用这个偏移量,我们可以让虚线“动起来”,俗称“蚂蚁线”。
let offset = 0; function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.setLineDash([4, 4, 12, 4]); ctx.lineDashOffset = offset; ctx.strokeRect(20, 20, 150, 150); } function march() {
offset++; if (offset > 24) {
offset = 0; } draw(); setTimeout(march, 20); } march();
七,阴影
shadowOffsetX|Y:用来设定阴影在 X 和 Y 轴的延伸距离,它们是不受变换矩阵所影响的。 shadowBlur:用于设定阴影的模糊程度,默认为 0。 shadowColor:用于设定阴影颜色效果,默认是全透明的黑色
八,填充规则
填充规则有两种,一种是默认的非零填充和奇偶环绕填充.
在 canvas 中,可以通过给 fill
(或者 clip
和 isPointinPath
)传参指定填充规则,默认为 nonzero
:
ctx.fill(); ctx.fill(fillRule); ctx.fill(path, fillRule);
8.1,非零填充规则
因为每次在画板上绘制路径肯定是有顺序的,所以必然是按照顺时针绘制或者逆时针绘制.
于是非零填充其实就是,从任意一点绘制射线,如果与顺时针路径相交,则计数器+1,如果与逆时针的路径相交,则计数器-1,当该射线的所有交点计数器和为0时不填充,否则填充.
如下图:
A:先遇到顺时针的路径,计数器+1,然后遇到一条逆时针的路径,计数器-1,遇到逆时针-1,又遇到顺时针的+1,最后得0,于是A点不填充.8.2,奇偶环绕填充
奇偶环绕规则更加简单,和上文一样绘制一样的图案,任意一点发射射线,与路径交点总数为偶数则不填充,为奇数则填充.
九,文本的绘制
canvas提供了两种绘制文本的方法:
fillText(text, x, y [, maxWidth])//在指定的 (x,y) 位置填充指定的文本,绘制的最大宽度是可选的。 strokeText(text, x, y [, maxWidth])//在指定的 (x,y) 位置绘制文本边框,绘制的最大宽度是可选的。
具体的属性设置有:
属性名称 | 属性值 | 使用案例 |
---|---|---|
font | 默认字体是 10px sans-serif |
ctx.font = “bold 48px serif”; |
textAlign | “left” / “right” /“center” / “start” / “end” | ctx.textAlign = “left” |
textBaseline | top ,文本基线在文本块的顶部。middle ,文本基线在文本块的中间。alphabetic ,文本基线是标准的字母基线。ideographic ,文字基线是表意字基线;如果字符本身超出了 alphabetic 基线,那么 ideograhpic 基线位置在字符本身的底部。hanging ,文本基线是悬挂基线。bottom ,文本基线在文本块的底部。 |
ctx.textBaseline=‘top’ |
direction | “ltr” / “rtl” /“inherit” | ctx.direction=rtl |
预测量文本宽度 | measureText 方法将返回一个 TextMetrics对象,只包含部分能体现文本特性的属性(宽度、所在像素): |
var text = ctx.measureText(“foo”); // TextMetrics object |
十,绘制图像
10.1,canvas支持获取的图片类型HTMLImageElement
主要有两种,一种是js动态创建的Image对象,另外一种是html中的image标签.
10.1.1,动态创建的Image图像
动态创建的图像,当我们想要绘制到canvas上的时候,需要先确保图片已经加载完全,所以需要放置在img.onload的回调函数中:
let img = new Image(); img.src = imgUrl;//引入的图片地址('../test.png') img.onload = function () {
// drawImage to canvas };
10.1.2,HTML中的image标签
这个因为页面已经存在,就可以直接获取使用了:
<img id="img" src="XXX.png" /> <script> let img = document.getElementById("img"); img.onload = function () {
// drawImage to canvas }; </script>
10.2,drawImage()绘制图像到canvas中
获得图片对象后,我们通过 drawImage
方法将它渲染到 canvas 里。这个api有三种使用方法,主要是参数数量的不同.
10.2.1,drawImage(image, x, y)直接绘制图像
drawImage(image, x, y);//image就是上文获取到的图片对象,xy是开始绘制的左上角坐标
10.2.2,drawImage(image, x, y, width, height);对图像进行缩放
五个参数的时候,可以指定图片的宽高,这样一来,就可以进行图片的缩放.
10.2.3,drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);对图像进行截取并缩放
drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight); //sx, sy,源目标的左上角左上角开始点 //想要从开始点截取指定长宽的范围 //dx, dy绘制图像的左上角开始点 //图片的指定宽高,实现缩放
也就是这个九个参数实现的事从原图像中抠出一部分内容,缩放一下,绘制在canvas中.
10.3,图片的裁剪
canva提供了一个裁剪图片的api:clip()
会将当前正在构建的路径转换为当前的裁剪路径,所有在路径以外的部分都会被隐藏。
于是乎,使用clip方法一般要经历以下三步:
1,绘制基本图形 2,使用clip()方法将该图形转化为裁剪路径 3,绘制图片
也就是说,原来的图片照常绘制,只不过只显示裁剪路径内容罢了.
当我们进行裁剪的时候,需要注意一点,因为裁剪范围是属于绘图状态的,所以为了避免后续的绘图被这个裁剪路径所裁剪(裁剪会作用于当前裁剪后的绘图状态下的所有绘制的图片,也就是裁剪后再绘图都是被裁剪的),就可以在裁剪前ctx.save()保存下未裁剪前的绘图状态,然后在执行裁剪后,确认不再需要裁剪了,就ctx.restore()来恢复未裁剪的绘图状态.
十一,变形操作
canvas的移动、旋转、变形斗士针对画布进行操作的,对于canvas而言,这些变形定义的是状态,所以应该在fill、stroke、drawImage等操作之前完成.
另外值得注意的是:
当你使用Canvas的变形操作(如ctx.rotate
)时,它只会影响之后绘制的图形,而不会影响之前已经绘制的图形。
Canvas绘图上下文(ctx
)维护了一个绘图状态栈(graphics state stack)(这个上文3.1中的绘图状态提到过),其中包含了当前的变换矩阵(transformation matrix)。当你调用变形操作时,比如ctx.rotate(angle)
,它会修改当前的变换矩阵,然后之后的绘制操作都会基于这个新的变换矩阵进行变换。
但是,已经绘制在画布上的图形是不可更改的,它们已经被固定在画布上。变形操作只会影响之后的绘制操作,不会改变已经绘制的图形,如果要都变形,则需要在执行变形状态后重新绘制.
11.1,移动translate
ctx.fillRect(0, 0, 100, 100); ctx.translate(50,100)
就是将该矩形移动(50,100)
11.2,旋转 rotate
旋转依然是以原点为圆心进行的旋转操作.
rotate(弧度);//正值以x轴顺时针,负值逆时针,弧度=角度*PI/180
11.3,缩放scale
对形状,位图进行缩小或者放大,即增减图形在 canvas 中的像素数目。
scale(x, y);//默认是1,比1大则放大,比1小则缩小,如果是负数,相当于以 x 或 y 轴作为对称轴镜像反转
11.4,变形矩阵transform
就是实使用这个api对图形一次性进行多角度更改:
transform(a, b, c, d, e, f); a:水平方向的缩放 b:竖直方向的倾斜偏移 c:水平方向的倾斜偏移 d:竖直方向的缩放 e:水平方向的移动 f:竖直方向的移动
11.5,将变换的中心点移动到矩形中间
上文的变化基点都是以坐标原点为基准,我们可以使用translate方法将该绘图状态的坐标原点移动到canvas上任意我们想要的位置上.然后基于这个位置进行绘图和变化,这样就能实现变化中心点的指定.
如上图,我们就是将当前绘图状态的坐标原点切换到原先坐标系的(200,200)位置.然后在该绘图状态下的坐标系下绘制图案,注意到这时图案的左上角我用了负数的坐标,以此来保证该坐标原点在图案的中心,于是就可以基于该点做变换了.
十二,像素的操作与滤镜
12.1,图片像素数据的获取
canvas提供了图片像素的获取api,它可以帮助我们获取图片中指定区域的像素信息.
let imageData=ctx.getImageData(x,y,width,height)//xy是定位左上角,width和height是取对应的区域范围
使用的示例如下:
let img = new Image(); img.src = imgUrl;//引入的图片地址('../test.png') img.onload = function () { // 绘制图像 ctx.drawImage(img,0, 0,300,300); let imageData=ctx.getImageData(10,10,20,20) console.log("图片像素数据",imageData.data) };
获取到的data是个数组,里面的内容大致如下:
[r1,g1,b1,a1,r2,g2,b2,a2,r3,g3,b3,a3……]
可以看到,每四个为一组,表示着一个像素点的rgba值.这样,我们就能拿到指定区域的像素信息了.
但是我们一般说的图片信息就是imageData而不是imageData.data.
12.2,使用像素数据绘制图片
同样的,我们也可以直接利用一系列像素数据在canvas上绘制出图案.
ctx.putImageData(imageData,x,y)//参数依次为像素数据组,开始绘制的左上角坐标
到目前为止,我们已经能获取图片指定位置的像素,要是将这些像素再处理一下,然后再进行绘制,不就能够实现图片的滤镜功能了嘛.
12.3,反相滤镜效果
反相操作的算法是:红绿蓝三个通道的值取相反值,即255-原值
let img = new Image(); img.src = imgUrl;//引入的图片地址('../test.png') img.onload = function () {
ctx.drawImage(img,0, 0,300,300);//绘制图像 let imageData=ctx.getImageData(0,0,300,300)//获取像素信息 let resultData=invert(imageData)//像素处理:反相 ctx.putImageData(imageData,300,0)//绘制反相后的图片 }; function invert(imageData){
//反相操作的算法是:红绿蓝三个通道的值取相反值,即255-原值 let data=imageData.data for(let i=0;i<data.length;i+=4){
data[i]=255-data[i] data[i+1]=255-data[i+1] data[i+2]=255-data[i+2] } return imageData }
值得注意的是imageData里面的属性都是只读的,不能直接更改和赋值。所以这里我直接在invert函数中传入imageData,不修改imageData的属性data(引用地址),而修改data的值(它不是只读了).
实现的效果:
12.4,黑白效果滤镜
黑白滤镜又叫做灰度图,是指将彩色照片转化成黑白的照片.实现的算法是:将红绿蓝的变成取红、绿、蓝三个通道的平均值.但是通常为了达到更好的效果,一般会给每个通道一个加权系数:
let img = new Image(); img.src = imgUrl;//引入的图片地址('../test.png') img.onload = function () {
ctx.drawImage(img,0, 0,300,300);//绘制图像 let imageData=ctx.getImageData(0,0,300,300)//获取像素信息 let resultData=average(imageData)//像素处理:反相 ctx.putImageData(imageData,300,0)//绘制反相后的图片 }; //黑白滤镜:红绿蓝三个通道取三通道的平均值 function average(imageData){
let data=imageData.data for(let i=0;i<data.length;i+=4){
let averageValue=data[i]*0.3+data[i+1]*0.6+data[i+2]*0.1 data[i]=data[i+1]=data[i+2]=averageValue } return imageData }
实现的效果:
12.5,对区域进行像素操作
上文中,我们是通过在canvas中先绘制一个图片,利用图片得到可操作的像素主体,而有的时候,我们希望能够直接创建一块区域,能够让我们进行像素操作.
ctx.createImageData(width,height)//参数表示创建的可操作像素区域大小(不在canvas上,可以类比于游离的dom元素)
let imageData=ctx.createImageData(30,30)//创建可编辑像素区域 ctx.putImageData(imageData,20,20)
到此这篇canvas学习笔记的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!
版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/rgzn-sdxx/10900.html