Skip to content

五、图像处理

1.图片变换

1.1 状态的保存和恢复

由于canvas只是一个元素,并且只有一个上下文对象,不像div一样每个dom都可以单独设置css属性,当我们给上下文设置过颜色时,绘制下一个图形时使用的还是这个上下文对象,这样就给我们带来了不便

使用如下两个函数,可以将上下文对象在合适的地方进行保存,以及回退,从而达到每个元素的样式隔离

  • save():用来保存最近一次的Canvas上下文的状态和属性。
  • restore():用来获取save保存的Canvas之前的状态和属性。防止save后对Canvas执行的平移、放缩、旋转、错切、裁剪等可以改变画布的操作对后续的绘制的影响。

我们可以把save看成是一个入栈的过程,save一次,就向栈里面push一次cnavas的状态,而restore就是一个出栈的过程,每restore一次,就相当于弹出一次之前save的canvas状态,当前的canvas状态也就恢复成出栈的那个canvas状态,后面的图形的绘制就会在这个canvas状态之上绘制

image-20230528211504227

TIP

save()restore()必须成对使用,不然当使用requestAnimationFrame()时会造成页面卡顿

1.2 translate()

translate(x,y)方法用于平移图像。它接受两个参数,分别是 x 轴和 y 轴移动的距离(单位像素)。

js
var canvas = document.querySelector("canvas");
var ctx = canvas.getContext("2d");

ctx.fillRect(0, 0, 100, 100);

ctx.translate(50, 50);
ctx.fillStyle = "red";
ctx.fillRect(0, 0, 100, 100);
ctx.beginPath()ctx.translate(0, 0)ctx.fillStyle = "aqua"ctx.rect(0, 0, 100, 100)ctx.fill()ctx.closePath()
水平位移
垂直位移

TIP

translate()方法必须在绘制路径方法之前使用。

1.3 rotate()

rotate(deg)方法用于图像旋转。它接受一个弧度值作为参数,表示顺时针旋转的度数。

js
ctx.rotate(radian)

参数:

  • radian:旋转的弧度(π/180) radian= 1 deg
ctx.beginPath()ctx.rotate((Math.PI / 180) * 0)ctx.fillStyle = "aqua"ctx.rect(0, 0, 100, 100)ctx.fill()ctx.closePath()
旋转角度

TIP

rotate()方法必须在绘制路径方法之前使用。

旋转中心点始终是画布左上角的原点。如果要更改中心点,需要使用translate()方法移动画布。

1.4 scale()

scale(x,y)方法可以缩放画布的水平和垂直的单位。两个参数都是实数,可以为负数,x 为水平缩放因子,y 为垂直缩放因子,如果比 1 小,会缩小图形,如果比 1 大会放大图形。默认值为 1

js
ctx.scale(scaleX, scaleY)

参数:

  • scaleX:水平缩放
  • scaleY:垂直缩放
ctx.beginPath()ctx.scale(1, 1)ctx.fillStyle = "aqua"ctx.rect(0, 0, 100, 100)ctx.fill()ctx.closePath()
水平缩放
垂直缩放

TIP

scale()方法必须在绘制路径方法之前使用。

缩放的是整个ctx对象,也就是整个canvas画布,需要根据需求使用translate()改变位置

1.5 transform()

transform()方法接受一个变换矩阵的六个元素作为参数,完成缩放、旋转、移动和倾斜等变形

js
ctx.transform(
    scaleX,
    skewX,
    skewY,
    scaleY,
    translateX,
    translateY
);

参数:

  • scaleX:水平缩放(默认值1,单位倍数)
  • skewX:水平倾斜(默认值0,单位弧度)
  • skewY:垂直倾斜(默认值0,单位弧度)
  • scaleY:垂直缩放(默认值1,单位倍数)
  • translateX:水平位移(默认值0,单位像素)
  • translateY:垂直位移(默认值0,单位像素)
ctx.beginPath()ctx.transform(1, 0, 0, 1, 0, 0)ctx.fillStyle = "aqua"ctx.rect(0, 0, 100, 100)ctx.fill()ctx.closePath()
水平位移
垂直位移
水平缩放
垂直缩放
水平倾斜
垂直倾斜

1.6 setTransform()

setTransform()方法取消前面的图形变换,将画布恢复到该方法指定的状态。该方法的参数与transform()方法完全一致。

js
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');

ctx.translate(50, 50);
ctx.fillRect(0, 0, 100, 100);

ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.fillRect(0, 0, 100, 100);

上面代码中,第一个fillRect()方法绘制的矩形,左上角从(0, 0)平移到(50, 50)setTransform()方法取消了这个变换(已绘制的图形不受影响),将画布恢复到默认状态(变换矩形1, 0, 0, 1, 0, 0),所以第二个矩形的左上角回到(0, 0)

2.图像合成

globalCompositeOperation 是 HTML5 Canvas 中的一个重要属性,它定义了源图像(绘制的新形状)与目标图像(现有的图像)之间的合成规则。不同的合成规则可以产生多种效果

在Canvas中globalCompositeOperation属性的值总共有26种类型,如下所示

目标图像
源图像
source-over:新的图像将覆盖在旧图像上。

3.绘制图片

Canvas API 允许将图像文件写入画布,做法是读取图片后,使用drawImage()方法将这张图片放上画布。

drawImage()有三种固定传参的方式,分别代表

  • 三个参数:将图片的原始尺寸放在画布的指定位置
  • 五个参数:将图片按照指定尺寸放在画布的指定位置
  • 九个参数:在图片上截取区域,并按照指定大小放在画布指定位置
js
var img = new Image();
img.src = 'image.png';

ctx.drawImage(img, dx, dy);
ctx.drawImage(img, dx, dy, dWidth, dHeight);
ctx.drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

各个参数的含义如下。

参数描述
img图像元素
sx开始裁剪的横坐标,注意此参数以图片原尺寸为标准
sy开始裁剪的纵坐标,注意此参数以图片原尺寸为标准
sWidth裁剪图像的宽度,注意此参数以图片原尺寸为标准
sHeight裁剪图像的高度,注意此参数以图片原尺寸为标准
dx图片相对于画布的横坐标
dy图片相对于画布的纵坐标
dWidth图像在画布内部呈现的宽度,会产生缩放效果。
dHeight图像在画布内部呈现的高度,会产生缩放效果。

绘制图像分为以下三步:

  1. 实例化一个Image对象
  2. 将图片的路径赋值给实例对象的src属性
  3. 在实例对象的onload事件里使用drawImage()方法
js
function draw() {
    const ctx = document.getElementById("canvas").getContext("2d");
    
    const img = new Image();
    img.src ='img.png'
    img.onload = function () {
        ctx.drawImage(img, 0, 0,800,500);
    };
}

4.像素操作

对canvas画布中的像素进行操作,我们首先要了解ImageData对象

4.1 ImageData 对象

ImageData 实例对象中存储着 canvas 对象真实的像素数据,它包含以下几个只读属性

  • width:图片宽度,单位是像素
  • height:图片高度,单位是像素
  • dataUint8ClampedArray 类型的一维数组,数组中每四个值分别代表R、G、B、A
    • R - 红色 (0-255)
    • G - 绿色 (0-255)
    • B - 蓝色 (0-255)
    • A - alpha 通道 (0-255; 0 是透明的,255 是完全可见的)

通过构造函数创建ImageData对象

js
const img = new ImageData(array, width, height);
const img2 = new ImageData(width, height);

参数:

  • array:包含图像隐藏像素的Uint8ClampedArray数组。如果数组没有给定,指定大小的黑色矩形图像将会被创建。
  • width:无符号长整型(unsigned long)数值,描述图像的宽度
  • height:无符号长整型(unsigned long)数值,描述图像的高度。 如果已给定数组,这个值是可选的:它将通过它的大小和给定的宽度进行推断。

4.2 createImageData()

作用:创建一个新的,空白的ImageData 对象,有以下两种创建方法

1.按照宽高创建

js
var img = ctx.createImageData(width, height);

2.按照已有的imageData对象创建

js
var img = ctx.createImageData(width, height);

var newImg = ctx.createImageData(img);

4.3 getImageData()

作用:这个方法会返回一个 ImageData 对象,它代表了画布区域的对象数据

语法

js
var imageData = ctx.getImageData(x, y, width, height);

参数

  • x:需要提取的图像数据矩形区域的左上角横坐标
  • y:需要提取的图像数据矩形区域的左上角数纵坐标
  • width:需要提取的图像数据矩形区域的宽度
  • height:需要提取的图像数据矩形区域的高度

4.4 putImageData()

作用:在场景中写入数据

语法

  • 三个参数:将像素数据原样绘制到画布中
  • 七个参数:将像素数据中截取一部分绘制到画布中
js
ctx.putImageData(myImageData, dx, dy);
ctx.putImageData(myImageData, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight);

参数

  • myImageData:ImageData对象

  • dx, dy:目标画布中的横纵偏移量

  • dirtyXdirtyY:源图像中截取区域的左上角坐标

  • dirtyWidthdirtyHeight:源图像中截取区域的宽高

5.区域检测

注意:以下两个方法不支持canvas自带的两个方法fillRect(),strokeRect()

使用以下方法代替

js
ctx.rect(x,y,w,h);
ctx.stroke(); //替代strokeRect();

ctx.rect(x,y,w,h);
ctx.fill(); //替代fillRect();

5.1 isPointInPath()

isPointInPath(x,y)判断点(x,y)是否在路径被围成的区域内,返回布尔值

js
ctx.rect(100, 100, 100, 100);
ctx.stroke();

console.log(ctx.isPointInPath(150,150))  // true
x:0y:0
是否在图形内:否

5.2 isPointInStroke()

isPointInPath(x,y)判断点(x,y)是否在路径的线上,返回布尔值

  • 在区域内或在区域外都返回flase,只有在路径边框上才返回true
js
ctx.rect(100, 100, 100, 100);
ctx.stroke();

console.log(ctx.isPointInPath(100,100))  // true
x:0y:0
是否在图形边缘上:否

6.裁剪路径

6.1 clip()

作用:将当前创建的路径设置裁剪的范围,对后面的图像进行裁剪

语法

clip()有以下四个重载

js
clip()
clip(path)
clip(fillRule)
clip(path, fillRule)

参数

示例

JS
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");

ctx.beginPath();
ctx.arc(canvas.width/2, canvas.height/2, 100, 0, Math.PI * 2);
ctx.stroke()
ctx.clip()

const img = new Image();
img.src = "./lufy.png";
img.onload = () => {
    ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
};

以上代码以一个原型为裁剪范围,对后面的图像进行裁剪

image-20230713164856156

7.保存图片

使用canvas.toDataURL() 方法可将canvas画布保存为图片

它返回一个包含被类型参数规定的图像表现格式的数据链接。返回的图片分辨率是 96dpi。

7.1 toDataUrl()

toDataUrl()canvas对象上的一个方法,用于将canvas中的像素保存为图片

js
const canvas = document.querySelector("canvas");
// ...
canvas.toDataURL(type, encoderOptions);

参数

  • type:图片的格式,一个字符串,可以为'image/png' | 'image/jpeg' | 'image/webp',默认为 image/png
  • encoderOptions:
    • 在指定图片格式为 image/jpegimage/webp 的情况下,可以从 0 到 1 的区间内选择图片的质量。
    • 值为一个0-1的数字,默认值为0.92,如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。

下面是一个使用toDataUrl()保存图片的示例

js
// 绘制水印
const drawWatermark = (ctx) => {
  for (let i = 0; i < 10; i++) {
    for (let j = 0; j < 10; j++) {
      ctx.save();
      ctx.beginPath();
      ctx.font = "15px Arial";
      ctx.textAlign = "center";
      ctx.rotate((-30 * Math.PI) / 180);
      ctx.fillStyle = "gray";
      ctx.fillText("水印文字", -100 + i * 100, 10 + j * 60);
      ctx.closePath();
      ctx.restore();
    }
  }
};

const drawRandomGraph=()=>{/*...*/}		// 绘制随机图形

// 保存图片
const saveImage = () => {
  const canvas = canvasRef.value;
  const img = canvas.toDataURL("image/png", 1);
  const a = document.createElement("a");
  a.href = img;
  a.download = "watermark.png";
  a.click();
};

7.2 toBlob()

toBlob()也是canvas对象上的方法,用于把canvas中的像素保存为Blob对象

js
canvas.toBlob(callback(blob), type, encoderOptions)

参数:

  • callback:转化后的回调
    • blob:转化后的Blob对象
  • type:图片的格式,一个字符串,可以为'image/png' | 'image/jpeg' | 'image/webp',默认为 image/png
  • encoderOptions:
    • 在指定图片格式为 image/jpegimage/webp 的情况下,可以从 0 到 1 的区间内选择图片的质量。
    • 值为一个0-1的数字,默认值为0.92,如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。

上面保存图片的示例修改为toBlob()如下所示

js
const saveImage = () => {
  const canvas = canvasRef.value;
  canvas.toBlob((blob)=>{
      const a = document.createElement("a");
      a.href = URL.createObjectURL(blob);
      a.download = "watermark.png";
      a.click();
    },
    "image/png",
    1
  );
};

8.解决canvas模糊

js
function createHDCanvas(canvas, w, h) {
    const ratio = window.devicePixelRatio || 1;
    canvas.width = w * ratio; // 实际渲染像素
    canvas.height = h * ratio; // 实际渲染像素
    canvas.style.width = `${w}px`; // 控制显示大小
    canvas.style.height = `${h}px`; // 控制显示大小
    const ctx = canvas.getContext("2d");
    ctx.scale(ratio, ratio);
    // canvas 绘制
    return canvas;
}
createHDCanvas(canvas, window.innerWidth, window.innerHeight);
window.onresize = () => {
    createHDCanvas(canvas, window.innerWidth, window.innerHeight);
};

MIT Licensed