Canvas 图像绘制
一、图像绘制
drawImage()
用于图像绘制,有三种调用方式:
drawImage(image, dx, dy)
drawImage(image, dx, dy, dWidth, dHeight)
drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
参数:
image
:图像源,例如:HTMLImageElement
、SVGImageElement
、HTMLVideoElement
、HTMLCanvasElement
、ImageBitmap
、OffscreenCanvas
、VideoFrame
;sx
(可选):开始剪切的 x 坐标位置;sy
可选:开始剪切的 y 坐标位置;sWidth
:可选,被剪切图像的宽度;sHeight
:可选,被剪切图像的高度;dx
:在画布上放置图像的 X 轴坐标;dy
:在画布上放置图像的 Y 轴坐标;dWidth
:所绘制图像的宽度(伸展或缩小图像)dHeight
:所绘制图像的高度(伸展或缩小图像)
示例:
// HTML
<canvas id="canvas" width="200" height="150" style="border: 1px dashed gray;"></canvas>
<img id="image" src="./image.png" alt="image" />
// JS
const cvs = document.getElementById('canvas')
const ctx = cvs.getContext('2d')
const image = document.getElementById("image");
image.addEventListener('load', (e) => {
// 将图像绘制到画布的 (50, 0) 坐标处
ctx.beginPath();
ctx.drawImage(image, 50, 0);
// 将图像绘制到画布的 (50, 60) 坐标处
// 并将其缩放为宽 100、高 30 的图像
ctx.beginPath();
ctx.drawImage(image, 50, 60, 100, 30);
// 原图像从坐标 (10,20) 处
// 截取一个宽为 36 高为 37 的图像
// 并将其绘制到画布的 (50, 100) 坐标处
// 再将其缩放为宽 51、高 52 的图像
ctx.beginPath();
ctx.drawImage(image, 10, 20, 36, 37, 50, 100, 51, 52);
});
效果如下:
二、图像平铺
ctx.createPattern(image, repetition)
用于在指定的方向内重复图像。
参数:
image
:图片、画布或视频元素;repetition
:指定如何重复图像,可选值如下:repeat(默认)
:在水平和垂直方向重复;repeat-x
:只在水平方向重复;repeat-y
:只在垂直方向重复;no-repeat
:只显示一次(不重复)
示例:
image.addEventListener('load', (e) => {
const pattern = ctx.createPattern(image, "repeat");
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, 200, 150);
});
效果如下:
三、像素级图像应用
1、getImageData 方法
使用 CanvasRenderingContext2D 提供的 getImageData
来获取获取 Canvas 画布的设定区域的 ImageData 对象,从而拿到图片像素数据。
ctx.getImageData(x, y, width, height);
参数:
x
:将要被提取的图像数据矩形区域的左上角 x 坐标。y
:将要被提取的图像数据矩形区域的左上角 y 坐标。width
:将要被提取的图像数据矩形区域的宽度。height
:将要被提取的图像数据矩形区域的高度。
返回值:一个 ImageData 对象,用来描述 canvas 区域隐含的像素数据。
1-1、基本用法
// HTML
<canvas id="canvas" width="200" height="150" style="border: 1px dashed gray;"></canvas>
<img id="image" src="./image.png" alt="image" />
// JS
const cvs = document.getElementById('canvas')
const ctx = cvs.getContext('2d')
const image = document.getElementById("image");
image.addEventListener('load', (e) => {
ctx.drawImage(image, 50, 0);
console.log(ctx.getImageData(0, 0, 200, 150))
});
运行控制台可能会报错:
这是由于 getImageData()
需要以服务器形式启动才能使用(跨域限制),以文件形式打开会报错,点击查看解决 canvas 图片 getImageData、toDataURL 跨域问题
如果是用 VSCode,可以安装 Live Server 插件,然后右键运行 Open with Live Server
即可。
运行正常后控制台输出:
其中,
width、height
:表示所选区域的宽高;data
:保存了区域的像素数据数组,数组取值如[r1, g1, b1, a1, r2, g2, b2, a2]
,每四个数存储着 1 个像素的rgba
颜色值,data.length
表示图片像素总量。
1-2、实现简易取色器
// HTML
<canvas id="canvas" width="200" height="150" style="border: 1px dashed gray;"></canvas>
<img id="image" src="./image.png" alt="image" />
// JS
const cvs = document.getElementById('canvas')
const ctx = cvs.getContext('2d')
const image = document.getElementById("image");
const getPixelColor = (e) => {
const pixel = ctx.getImageData(e.offsetX, e.offsetY, 1, 1).data;
const rgba = `rgba(${pixel[0]}, ${pixel[1]}, ${pixel[2]}, ${pixel[3] / 255})`;
console.log(rgba);
}
image.addEventListener('load', (e) => {
ctx.drawImage(image, 50, 0);
cvs.addEventListener("mousemove", getPixelColor);
});
鼠标悬浮画布时控制台会打印指针指向位置的颜色值。
2、putImageData 方法
在获取到图片的像素数据后,就可以对获取的像素数据进行处理,比如进行灰度化或反色处理。完成处理后,可以使用 CanvasRenderingContext2D 提供的另一个 API —— putImageData
在页面上显示处理效果。
ctx.putImageData(imagedata, dx, dy);
ctx.putImageData(imagedata, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight);
参数:
imageData
:包含图片像素信息的 ImageData 数组对象,用于替换当前 Canvas 画布上的 ImageData。dx
:目标 Canvas 中被图像数据替换的起点横坐标。dy
:目标 Canvas 中被图像数据替换的起点纵坐标。dirtyX
(可选):图像数据渲染区域的左上角横坐标,默认值为 0。dirtyY
(可选):图像数据渲染区域的左上角纵坐标,默认值为 0。dirtyWidth
(可选):图像数据渲染区域的宽度,默认为 imagedata 图像的宽度。dirtyHeight
(可选):图像数据渲染区域的高度,默认为 imagedata 图像的高度。
2-1、实现图片灰度化
点击查看利用 getImageData 和 putImageData 实现图片灰度化
四、图像保存
可通过 toDataURL
实现图像压缩及保存,点击查看详情
五、Canvas 图像优化
- 避免浮点数的坐标点,用整数取而代之:
// ❌
ctx.drawImage(myImage, 0.3, 0.5);
// ✅
ctx.drawIamge(myImage, Math.floor(0.3), Math.floor(0.5));
- 不要在用
drawImage
时缩放图像,可以缓存图片的不同尺寸; - 使用多层画布去画一个复杂的场景,比如静态背景和频繁发生移动的对象;
- 用 CSS 设置大的背景图,避免在每一帧在画布上反复地绘制大图;
- 用 CSS transform 特性缩放画布以开启硬件加速。当然最好是不直接缩放画布,或者让小画布按比例放大,而非大画布按比例缩小;
- 关闭透明度:
const ctx = canvas.getContext("2d", { alpha: false });
- 将画布的函数调用集合到一起(例如,画一条折线,而不要画多条分开的直线)
- 避免不必要的画布状态改变;
- 渲染画布中的不同点,而非整个新状态;
- 尽可能避免
shadowBlur
特性; - 尽可能避免
text rendering
; - 不同的场景可以选择不同的清除画布的方法:
clearRect()
、fillRect()
、调整 canvas 大小; - 使用 window.requestAnimationFrame() 而非
window.setInterval()
; - 谨慎使用大型物理库。