canvas像素操作、save与restore、合成与变形

一、canvas像素操作

(1)获取图像像素

imageData = ctx.getImageData(x,y,w,h)
imageData  对象
		   width
		   height
		   data
getImageData返回 ImageData 对象,该对象拷贝了画布指定矩形的像素数据

获取图像像素后的imageData中包含三项:
在这里插入图片描述
其中宽高分别为获取图像宽高时的大小,data则存放的是在该大小内所有的像素点的详细信息。data是一个数组,每四位秒数一个像素点,分别代表rgba。例如(创建像素的代码中也会提到):
在这里插入图片描述

(2)写入像素数据

ctx.putImageData(imgData,x,y,dirtyX,dirtyY,dirtyWidth,dirtyHeight);
  1. imgData : 规定要放回画布的 ImageData 对象。
  2. x,y: ImageData 对象左上角的 x y坐标,以像素计。
  3. dirtyX,dirtyY: 可选。水平值(x)、垂直值(y),以像素计,在画布上放置图像的位置。
  4. dw,dh :可选。在画布上绘制图像所使用的宽度与高度。

(3)创建像素数据

ctx.createImageData(width, height);

width : ImageData 新对象的宽度。
height: ImageData 新对象的高度

注意:创建一个ImageData对象,默认创建出来的是个透明的

<script type="text/javascript">
			// 获取画布
			var canvas = document.getElementById('canvas');

			// 设置画布的宽高
			canvas.width = 800;
			canvas.height = 600;
			// 获取画笔
			var ctx = canvas.getContext('2d');

			ctx.fillStyle = 'red';
			/* 
			 imageData
				width :横向像素点的个数
				height:纵向像素点的个数
				
				data: 数组 Uint8ClampedArray(40000) 类型的一维数组
				100x100x4 (40000);
				
				每个像素 的rgba信息
				r : 0 -255
				g : 0 -255
				b : 0 -255
				a : 0 -255 //透明度 (0-255)
				
			 */

			// 创建imageData对象
			var imageData = ctx.createImageData(100, 100); //默认创建出来是一个透明的

			for (var i = 0; i < imageData.data.length; i++) {
				// 0 3   4  7
				// 000 255 000 255
				imageData.data[4 * i + 3] = 255; //设置该对象的A 为255 的透明的
			}


			// 写入像素
			// x,y 左上角的 xy坐标位置
			ctx.putImageData(imageData, 100, 100);
			console.log(imageData);
		</script>

(4)小案例,将canvasA画布的图像复制到canvasB中

第一步:分别获取画布
第二步:将第一个画布添加一张图片,让其在加载时渲染
第三部:书写点击事件函数,获取A画布内容,渲染到B画布中

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<style type="text/css">
			body {
				background-color: #99CCFF;
			}

			.canvas {
				background-color: #fff;
			}

			button {
				margin: 50px;
			}
		</style>
	</head>
	<body>
		<canvas id="canvasA" class="canvas" width="600" height="360">
			您的浏览器不支持,canvas标签
		</canvas>

		<canvas id="canvasB" class="canvas" width="600" height="360">
			您的浏览器不支持,canvas标签
		</canvas>

		<button type="button" onclick="copyImg()">复制图像</button>
		<script type="text/javascript">
			// 获取画布 A
			var canvasA = document.getElementById('canvasA');
			// 获取画笔 A
			var ctxA = canvasA.getContext('2d');

			// 获取画布 B
			var canvasB = document.getElementById('canvasB');
			// 获取画笔 B
			var ctxB = canvasB.getContext('2d');

			/* 
			 imageData
				width :横向像素点的个数
				height:纵向像素点的个数
				
				data: 数组 Uint8ClampedArray(40000) 类型的一维数组
				100x100x4 (40000);
				
				每个像素 的rgba信息
				r : 0 -255
				g : 0 -255
				b : 0 -255
				a : 0 -255 //透明度 (0-255)
				
			 */
			// 给canvasA 绘制图像
			var img = new Image();
			// 设置图片路径
			img.src = './img/hl.jpg';
			// 图片加载完成时绘制
			img.onload = function() {
				ctxA.drawImage(img, 0, 0, img.width / 1.5, img.height / 1.5)
			}

			function copyImg() {
				// 获取图像像素
				var imageData = ctxA.getImageData(0, 0, canvasA.width, canvasA.height);
				for (var i = 0; i < imageData.data.length; i++) {
					// imageData.data[4*i+0]=0;//r
					// imageData.data[4*i+1]=1;//g
					imageData.data[4 * i + 2] = 2; //b
				}
				// 写入图像像素
				ctxB.putImageData(imageData, 0, 0, 0, 0, canvasB.width, canvasB.height);
				// x,y dx,dy 会进行累加
				// ctxB.putImageData(imageData,50,50,50,50,canvasB.width,canvasB.height);
				console.log(imageData);
			}
		</script>
	</body>
</html>

二、canvas的save与restore

save:Canvas状态存储在栈中,每当save()方法被调用后,当前的状态就被推送到栈中保存。可以调用任意多次 save方法
restore :每一次调用 restore 方法,上一个保存的状态就从栈中弹出,所有设定都恢复。(类似数组的pop())

  • canvas的save和restore的存储方式是一个栈(先进后出,后进先出),每次调用save及向栈中压入一个复杂的状态(包含样式等等)–进栈,每次调用一个restore及从栈顶中抽出一个复杂的状态(包含样式等等)–出栈。用户所当前调用的样式等等由当前栈顶状态所决定。结构如下图:
    在这里插入图片描述

示例:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<style type="text/css">
			body {
				background-color: #99CCFF;
			}

			#canvas {
				background-color: #fff;
				color: rgba(255, 0, 0, 1);
				position: absolute;
				top: 0;
				left: 0;
				right: 0;
				bottom: 0;
				margin: auto;

			}
		</style>
	</head>
	<body>
		<canvas id="canvas">
			您的浏览器不支持,canvas标签
		</canvas>
		<script type="text/javascript">
			// 获取画布
			var canvas = document.getElementById('canvas');

			// 设置画布的宽高
			canvas.width = 800;
			canvas.height = 600;
			// 获取画笔
			var ctx = canvas.getContext('2d');

			ctx.fillStyle = '#fa6900';
			ctx.shadowOffsetX = 10;
			ctx.shadowOffsetY = 10;
			ctx.shadowBlur = 4;
			ctx.shadowColor = 'rgba(204,204,204,0.5)';
			ctx.fillRect(10, 10, 15, 150);
			ctx.save(); //将第一个状态推入到栈中

			ctx.fillStyle = '#f36';
			ctx.shadowOffsetX = 10;
			ctx.shadowOffsetY = 10;
			ctx.shadowBlur = 4;
			ctx.shadowColor = 'rgba(204,204,204,0.5)';
			ctx.fillRect(50, 10, 30, 150);
			ctx.save(); //将第二个状态推入到栈中


			ctx.fillStyle = '#a7dbd7';
			ctx.shadowOffsetX = 10;
			ctx.shadowOffsetY = 10;
			ctx.shadowBlur = 4;
			ctx.shadowColor = 'rgba(204,204,204,0.5)';
			ctx.fillRect(110, 10, 45, 150);
			ctx.save(); //将第三个状态推入到栈中

			ctx.restore(); //取出堆栈 3 (第三个状态)
			ctx.beginPath();
			ctx.arc(225, 75, 22, 0, Math.PI * 2, true);
			ctx.closePath();
			ctx.fill();

			ctx.restore(); //取出堆栈 2 (第二个状态)
			ctx.beginPath();
			ctx.arc(320, 75, 15, 0, Math.PI * 2, true);
			ctx.closePath();
			ctx.fill();

			ctx.restore(); //取出堆栈 3 (第三个状态)
			ctx.beginPath();
			ctx.arc(400, 75, 8, 0, Math.PI * 2, true);
			ctx.closePath();
			ctx.fill();
		</script>
	</body>
</html>

三、canvas的图像合成与变形

(1)canvas的图像合成

在canvas中提供globalCompositeOperation即Canvas中的合成操作。

说明
source-over这是默认设置,新图像会覆盖在原有图像。
source-in仅仅会出现新图像与原来图像重叠的部分,其他区域都变成透明的。
source-out仅仅显示新图像与老图像没有重叠的部分,其余部分全部透明。
source-atop新图像仅仅显示与老图像重叠区域。老图像仍然可以显示。
destination-over新图像会在老图像的下面。
destination-in仅仅新老图像重叠部分的老图像被显示,其他区域全部透明。
destination-out仅仅老图像与新图像没有重叠的部分。 注意显示的是老图像的部分区域。
destination-atop老图像仅仅仅仅显示重叠部分,新图像会显示在老图像的下面。
lighter新老图像都显示,但是重叠区域的颜色做加处理
darken保留重叠部分最黑的像素。(每个颜色位进行比较,得到最小的)

示例:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<style type="text/css">
			body {
				background-color: #99CCFF;
			}

			#canvas {
				background-color: #fff;
				color: rgba(255, 0, 0, 1);
				position: absolute;
				top: 0;
				left: 0;
				right: 0;
				bottom: 0;
				margin: auto;

			}
		</style>
	</head>
	<body>
		<canvas id="canvas">
			您的浏览器不支持,canvas标签
		</canvas>
		<script type="text/javascript">
			// 获取画布
			var canvas = document.getElementById('canvas');

			// 设置画布的宽高
			canvas.width = 800;
			canvas.height = 600;
			// 获取画笔
			var ctx = canvas.getContext('2d');

			/* 
				  1.source-over  这是默认设置,新图像会覆盖在原有图像。		
				  2.source-in	 仅仅会出现新图像与原来图像重叠的部分,其他区域都变成透明的。			
				  3.source-out	 仅仅显示新图像与老图像没有重叠的部分,其余部分全部透明。
				  4.source-atop  新图像仅仅显示与老图像重叠区域。老图像仍然可以显示。
				  
				  5.destination-over 新图像会在老图像的下面。
				  6.destination-in  仅仅新老图像重叠部分的老图像被显示,其他区域全部透明。
				  7.destination-out 仅仅老图像与新图像没有重叠的部分。 注意显示的是老图像的部分区域。
				  8.destination-atop 老图像仅仅仅仅显示重叠部分,新图像会显示在老图像的下面。
				  
				  9.lighter 新老图像都显示,但是重叠区域的颜色做加处理
				  10.darken 保留重叠部分最黑的像素。(每个颜色位进行比较,得到最小的)

			 
			 */
			ctx.fillStyle = 'blue';
			ctx.fillRect(0, 0, 200, 200); //原有矩形 (目标)
			// ctx.globalCompositeOperation='source-over';//源对象在在下面,新的图像在上面
			// ctx.globalCompositeOperation='source-in';//只留下源与目标重叠的部分
			// ctx.globalCompositeOperation='source-out';//只留下源超出目标的部分
			// ctx.globalCompositeOperation='source-atop';//砍掉了源超出的部分

			// ctx.globalCompositeOperation='destination-over';//目标在上面,旧的图像层级比较高
			// ctx.globalCompositeOperation='destination-in';//z只留下源与目标重叠部分(目标的那部分)
			// ctx.globalCompositeOperation='destination-out';//只留下目标超过源的部分
			// ctx.globalCompositeOperation='destination-atop';// 砍掉目标溢出的部分

			// ctx.globalCompositeOperation='lighter';//源与目标重叠,样式处理

			ctx.globalCompositeOperation = 'darken'; //源与目标重叠,样式处理(每个颜色位进行比较,得到最小的)



			ctx.fillStyle = 'red';
			ctx.fillRect(100, 100, 200, 200); //新的矩形 (源)
		</script>
	</body>
</html>

(2)canvas变形

translate(x,y) 移动

  • translate 方法用来移动 canvas 和它的原点到一个不同的位置。
  • translate(x, y) translate 方法接受两个参数。x 是左右偏移量,y 是上下偏移量,如图所示。
    在这里插入图片描述
    样例:
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<style type="text/css">
			body {
				background-color: #99CCFF;
			}

			#canvas {
				background-color: #fff;
				color: rgba(255, 0, 0, 1);
				position: absolute;
				top: 0;
				left: 0;
				right: 0;
				bottom: 0;
				margin: auto;

			}
		</style>
	</head>
	<body>
		<canvas id="canvas">
			您的浏览器不支持,canvas标签
		</canvas>
		<script type="text/javascript">
			// 获取画布
			var canvas = document.getElementById('canvas');

			// 设置画布的宽高
			canvas.width = 800;
			canvas.height = 600;
			// 获取画笔
			var ctx = canvas.getContext('2d');

			/* 
				 由于canvas是基于转态绘制的。在我们第一次平移之后,坐标系已经在(100,100),所以需要继续平移,再基于新的坐标系
				 继续平移坐标系。那怎么解决?
				 1. 每次使用完变换之后,记得将坐标系平移到原点,调用translate(-x,-y);
				 
				 2.在每次平移之前使用save(),在每次绘制之后使用restore()恢复
			 
			 */

			// ctx.save();
			// ctx.fillStyle='orange';
			// ctx.translate(100,100);
			// ctx.fillRect(0,0,200,100);
			// ctx.restore();//恢复状态


			ctx.fillStyle = 'orange';
			ctx.translate(100, 100);
			ctx.fillRect(0, 0, 200, 100);

			ctx.translate(-100, -100);

			ctx.fillStyle = 'green';
			ctx.translate(200, 200);
			ctx.fillRect(0, 0, 200, 100);
		</script>
	</body>
</html>

rotate(angle)角度

  • rotate 方法用于以原点为中心旋转 canvas。
  • rotate(angle)这个方法只接受一个参数:旋转的角度(angle),它是顺时针方向的,以弧度为单位的值。
    在这里插入图片描述

样例:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<style type="text/css">
			body {
				background-color: #99CCFF;
			}

			#canvas {
				background-color: #fff;
				color: rgba(255, 0, 0, 1);
				position: absolute;
				top: 0;
				left: 0;
				right: 0;
				bottom: 0;
				margin: auto;

			}
		</style>
	</head>
	<body>
		<canvas id="canvas">
			您的浏览器不支持,canvas标签
		</canvas>
		<script type="text/javascript">
			// 获取画布
			var canvas = document.getElementById('canvas');

			// 设置画布的宽高
			canvas.width = 800;
			canvas.height = 600;
			// 获取画笔
			var ctx = canvas.getContext('2d');


			// rotate 旋转 不是针对图形旋转,而是针对画布本身

			// 顺时针 往下旋转,-° 逆时针旋转
			ctx.fillStyle = 'orange';
			ctx.fillRect(50, 50, 100, 50);
			ctx.rotate(30 * Math.PI / 180); //顺时针旋转30°
			ctx.fillStyle = 'green';
			ctx.fillRect(50, 50, 100, 50);
		</script>
	</body>
</html>

样例用旋转绘制折扇图:

<script type="text/javascript">
			// 获取画布
			var canvas = document.getElementById('canvas');

			// 设置画布的宽高
			canvas.width = 800;
			canvas.height = 600;
			// 获取画笔
			var ctx = canvas.getContext('2d');


			// rotate 旋转 不是针对图形旋转,而是针对画布本身
			// 顺时针 往下旋转,-° 逆时针旋转

			var colors = ['#999933', '#FFFFCC', '#CC99CC', '#CC9966', '#999999', '#9966CC'];

			// 利用多次旋转绘制折扇图
			for (var i = 0; i < colors.length; i++) {
				ctx.fillStyle = colors[i];
				ctx.fillRect(0, 0, 200, 50);
				ctx.rotate(15 * Math.PI / 180);
			}
		</script>

scale(x,y)缩放

  • 缩放用来增减图形在 canvas 中的像素数目,对形状,位图进行缩小或者放大。
  • scale(x, y) 该方法接受两个参数。x,y 分别是横轴和纵轴的缩放因子,它们都必须是正值。 值比 1.0 小表示缩小,比 1.0大表示放大,值为 1.0 时什么效果都没有。
    在这里插入图片描述
    样例:
<script type="text/javascript">
			// 获取画布
			var canvas = document.getElementById('canvas');

			// 设置画布的宽高
			canvas.width = 800;
			canvas.height = 600;
			// 获取画笔
			var ctx = canvas.getContext('2d');

			ctx.fillStyle = 'orange';
			ctx.fillRect(50, 50, 100, 50);


			/* 
				scale 缩放的是画布上1个单位距离,不是缩放的画布;
				缩放并非是图像,而是整个坐标系
			 */
			// ctx.scale(0.5,2);//正数
			ctx.scale(-0.5, 2); //负数
			ctx.translate(-100, 50);
			ctx.fillStyle = 'green';
			ctx.fillRect(0, 0, 100, 50);
		</script>

(3)使用canvas变形设计小球匀速运动

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<style type="text/css">
			body {
				background-color: #99CCFF;
			}

			#canvas {
				background-color: #fff;
				color: rgba(255, 0, 0, 1);
				position: absolute;
				top: 0;
				left: 0;
				right: 0;
				bottom: 0;
				margin: auto;

			}
		</style>
	</head>
	<body>
		<canvas id="canvas">
			您的浏览器不支持,canvas标签
		</canvas>
		<script type="text/javascript">
			// 获取画布
			var canvas = document.getElementById('canvas');

			// 设置画布的宽高
			canvas.width = 800;
			canvas.height = 600;
			// 获取画笔
			var ctx = canvas.getContext('2d');

			// 小球左右来回均速运动


			var ball = {
				x: 10,
				y: 50,
				direction: 1, //小球方向
				draw: function() {
					ctx.beginPath();
					ctx.arc(this.x, this.y, 10, 0, 360 * Math.PI / 180);
					ctx.fillStyle = 'orange';
					ctx.closePath();
					ctx.fill();
				}
			}

			// 设置小球左右移动
			function ball_animate() {
				ctx.clearRect(0, 0, canvas.width, canvas.height);
				ctx.beginPath();
				ctx.moveTo(0, 100);
				ctx.lineTo(400, 100);
				ctx.fillStyle = 'orange';
				ctx.stroke();

				ball.draw();
				// 设置小球从左往右跑
				if (ball.direction == 1) {
					ball.x += 100 / 60; //16.6
				} else {
					// 从右 往左边跑
					ball.x -= 200 / 60; //3.3
				}

				// 设置运动的边界
				if (ball.x > 400) {
					ball.x = 400;
					ball.direction = 0;
				}

				if (ball.x < 0) {
					ball.x = 0;
					ball.direction = 1;
				}
				window.requestAnimationFrame(ball_animate);
			}

			window.requestAnimationFrame(ball_animate);
		</script>
	</body>
</html>

(4)使用canvas变形设计行星围绕太阳转

从帖子中看到一个非常好的案例分享给大家(以下是原文代码,给大家整理好了): 点击跳转.

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>模拟太阳系旋转</title>
		<style type="text/css">
			body {
				background-color: #99CCFF;
			}

			#canvas {
				background-color: #fff;
				color: rgba(255, 0, 0, 1);
				position: absolute;
				top: 0;
				left: 0;
				right: 0;
				bottom: 0;
				margin: auto;

			}
		</style>
	</head>
	<body>
		<canvas id="canvas" width="1000" height="1000" style="background:#000">
			您的浏览器不支持,canvas标签
		</canvas>
		<script>
			//设置2d绘图环境
			var ctx = document.getElementById("canvas").getContext("2d");

			//画轨道
			function drawTrack() {
				for (var i = 0; i < 8; i++) {
					ctx.beginPath();
					ctx.arc(500, 500, (i + 1) * 50, 0, 360, false);
					ctx.closePath();
					ctx.strokeStyle = "#fff";
					ctx.stroke();
				}
			}

			//画星球的类
			function Star(x, y, radius, cycle, sColor, eColor) {

				//设置星球类的属性
				this.x = x; //星球的坐标点
				this.y = y;

				this.radius = radius; //星球的半径
				this.cycle = cycle; //设置周期
				this.sColor = sColor; //星球的颜色,起始颜色和结束颜色
				this.eColor = eColor;

				this.color = null;
				//设置一个计时器
				this.time = 0;

				//给星球类定义一个方法
				this.draw = function() {
					ctx.save(); //保存之前的内容
					ctx.translate(500, 500); //重置0,0坐标     
					ctx.rotate(this.time * (360 / this.cycle) * Math.PI / 180); //旋转角度

					//画星球
					ctx.beginPath();
					ctx.arc(this.x, this.y, this.radius, 0, 360, false);
					ctx.closePath();
					//设置星球的填充颜色
					this.color = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.radius);
					this.color.addColorStop(0, this.sColor);
					this.color.addColorStop(1, this.eColor);
					ctx.fillStyle = this.color;
					ctx.fill();
					//恢复之前画布的内容
					ctx.restore();
					this.time += 1;
				}
			}

			//创建一个太阳的构造函数
			function Sun() {
				Star.call(this, 0, 0, 20, 0, "#FFFF00", "#FF9900");
			}

			//创建一个水星的构造函数
			function Mercury() {
				Star.call(this, 0, -50, 10, 87.70, "#A69697", "#5C3E40");
			}

			//创建一个金星的构造函数
			function Venus() {
				Star.call(this, 0, -100, 10, 224.701, "#C4BBAC", "#1F1315");
			}

			//创建一个地球的构造函数
			function Earth() {
				Star.call(this, 0, -150, 10, 365.2422, "#78B1E8", "#050C12");
			}

			//创建一个火星的构造函数
			function Mars() {
				Star.call(this, 0, -200, 10, 686.98, "#CEC9B6", "#76422D");
			}

			//创建一个木星的构造函数
			function Jupiter() {
				Star.call(this, 0, -250, 10, 4332.589, "#C0A48E", "#322222");
			}
			//创建一个土星的构造函数
			function Saturn() {
				Star.call(this, 0, -300, 10, 10759.5, "#F7F9E3", "#5C4533");
			}
			//创建一个天王星的构造函数
			function Uranus() {
				Star.call(this, 0, -350, 10, 30799.095, "#A7E1E5", "#19243A");
			}
			//创建一个海王星的构造函数
			function Neptune() {
				Star.call(this, 0, -400, 10, 60152, "#0661B2", "#1E3B73");
			}

			var sun = new Sun();
			var mercury = new Mercury();
			var venus = new Venus();
			var earth = new Earth();
			var mars = new Mars();
			var jupiter = new Jupiter();
			var saturn = new Saturn();
			var uranus = new Uranus();
			var neptune = new Neptune();

			function Move() {
				ctx.clearRect(0, 0, 1000, 1000);
				drawTrack();
				sun.draw();
				mercury.draw();
				venus.draw();
				earth.draw();
				mars.draw();
				jupiter.draw();
				saturn.draw();
				uranus.draw();
				neptune.draw();
			}

			setInterval(Move, 10);
		</script>
	</body>
</html>

制作整理不易,参考了部分官方文档、帖子和老师整理的案例。如要引用请附上本文链接,如有疑问可以在评论区畅所欲言,作者看到会第一时间回复,欢迎交流!

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

么贺贵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值