canvas 处理图像(上)
本文将介绍在 Canvas 中使用图像的知识,包括加载图像和处理图像中的单个像素。Canvas 的这个功能可以用来创建一些炫丽的效果。本文还将教会你一般图像处理的知识。
1. 加载图像
canvas 高级功能(下)讲述了如何将画布导出为图像,将它保存到本地和与他人共享。现在,我们将学习如何实现完全相反的操作:将图像加载到画布中。介绍这个功能的主要原因是,它使我们能够用2D渲染上下文方法对原本不是在画布中创建的图像进行处理。我们还可以使用几种特殊的像素处理 方法,对图像执行一些有趣的特殊操作,这将在下面介绍。
❝注意:在画布中进行像素处理实际上并不要求真加载图像,如照片。相反,画布本身就是作为图像进行处理的,这意味着你在上面绘制的所有内容都可以使用本文介绍的方法进行处理。
❞
将图像加载到画布中实际上与绘制图像一样简单——只涉及一个方法。在调用drawImage
方法时,至少需要三个参数:所绘制的图像和图像绘制位置的(x, y)
坐标。这个方法的完整形式是:context.drawImage(image, x, y);
参数image
可以是HTML img
元素、HTML5 canvas
元素或HTML5 video
元素。
❝注意:实际上,
❞drawImage
方法有两种调用方式,这两种方式所使用的参数个数是不同的。我们将在下面详细介绍这两种方式。
首先,让我们使用与 HTML 文件位于相同目录的一个图像,将一个HTML img
元素绘制到画布中。
const image = new Image(); image.src = 'picture.jpg'; image.onload = function() { context.drawImage(image, 0, 0); }
这里所做的第一件事是使用Image
类为HTML img
元素赋一个空的DOM对象。然后,通过把它的src
属性设置为一个有效的图像文件路径,就可以将该图像加载到图像元素中,这就好像是设置了HTML img
元素的src
属性。实际上这创建了一个普通的HTML img
元素,但是并没有将它显示在浏览器上。如果只希望给画布传递一个图像,而实际上不将它添加到HTML代码中,那么就可以使用这种方法。如果你就是想要看到这个HTML图像,那么完全可以跳过这些步骤,将image
变量的值赋给现有HTML img
元素的 DOM 对象。
❝注意:在这个例子中,我们使用的是本地存储的图像文件,但是只要愿意,你也可以轻松地加载其他网站的图像。然而,使用外部图像有一些限制。现在,你只需要知道在使用外部图像时,画布会限制一些特定的功能就可以了。
❞
无论使用哪一种方法,现在我们都应该能够访问图像的 DOM 对象了。最后要做的一件事是将这个图像对象传递给2D渲染上下文的drawImage
方法,但是在这之前,我们需要确认这个图像已经完全加载。为此,可以使用image
的load
方法,它是在一个元素完全加载后触发load
事件时调用的方法。
现在,我们知道这个图像在什么时候完成加载:我们将drawImage
方法置于load
事件被触发之后运行的回调事件中。drawImage
方法的参数就是刚刚创建的图像对象,以及绘制图像的原点(x, y)
坐标值。
如果一切正常,我们就能够将图像绘制到画布上,尽管图像可能被剪掉一部分。然而,不需要担心,因为剪掉的原因是画布小于所绘制的图像尺寸,而图像是以完整尺寸绘制的。
然而,无法看到另一半图像很让人失望,所以让我们看看如何使它适合画布的尺寸。
2. 调整和裁剪图像
我们现在知道调用drawImage
方法的第一种方式,即将完整尺寸的图像绘制到画布上,但超过画布边界的部分被剪掉了。为了解决这个问题,需要调整图像大小或者控制图像的裁剪。通过drawImage
方法的最后两种调用方式都能够完成这两个任务,第一种调用可以调整图像大小,第二种可以同时调整和裁剪图像。drawImage
的所有调用方式的唯一区别是所使用参数的个数和类型不同。
2.1 调整图像大小
实际上,调整图像大小与绘制完整尺寸的图像一样简单,只需要传入希望绘制的图像宽度和高度。用代码来表示,带有调整大小的参数的drawImage
方法:context.drawImage(image, x, y, width, height);
的确非常简单。
将前一个例子的drawImage
方法修改为以下形式,图像就能够被调整为在画布中完全显示:context.drawImage(image, 0, 0, 500, 333);
其中,宽度为500像素,与画布的宽度相等。而333像素的高度是按照原始图像的高宽比(高度与宽度的比例)计算得来的。要计算这个高宽比,只需要用高度除以宽度,对于原始图像(宽1024像素,高683像素),计算得到的高宽比为0.666992188(683÷1024)。然后,用宽度乘以这个比例就可以计算出调整后的图像高度。
如果要绘制完整的图像,那么调整大小是很有用的,但是有时候我们需要进一步控制图像绘制的部分,那么它就缺少足够的支持了。这时,我们需要使用裁剪功能。
2.2 裁剪图像
裁剪的目的是将图像剪切为较小尺寸,这通常是因为我们只需要使用被裁剪对象的一部分。裁剪画布所采取的方法与流行的照片编辑应用程序(如Adobe Photoshop)是完全相同的:划定一个希望保留的矩形区域,然后将矩形以外的全部内容删除。
裁剪是drawImage
方法的最后一种用法,它总共有9个参数:源图像、源图像的裁剪区原点坐标(x, y)
、源图像的裁剪区宽度和高度、在画布(目标)上绘制图像的原点坐标(x, y)
及在画布上绘制图像的宽度和高度。用代码表示,这些参数如下所示(w表示宽度,h表示高度):
context.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh);
我们可以从前面的图像中裁剪出一小部分,然后将它绘制到画布中:
context.drawImage(image, 0, 0, 250, 250, 0, 0, 250, 250);
在这个例子中,我们从源图像的左上角(0, 0)
开始裁剪出250像素的正方形,然后以相同的宽度和高度将它绘制到画布的左上角。
在将裁剪的图像绘制到画布时,还可以调整它的尺寸,例如:
context.drawImage(image, 0, 0, 250, 250, 0, 0, 500, 500);
这段代码实际上与前一个例子是完全相同的,只是所绘制的图像不再保留裁剪区域的原始尺寸,而是将它放大两倍。
2.3 阴影
简单强调一下在进行裁剪时的阴影效果,这是很重要的。简言之,在调整图像尺寸时,阴影效果应该也显示得很好。
context.shadowBlur = 20; context.shadowColor = "rgb(0, 0, 0)"; const image = new Image(); image.src = 'picture.jpg'; image.onload = function() { context.drawImage(image, 50, 50, 300, 200); }
然而,在一些浏览器中,对图像进行裁剪时阴影效果似乎会完全消失。官方规范规定了图像在绘制到画布时应当支持阴影效果,只是有些浏览器还没有完全支持这一点。
这就是关于在画布中调整和裁剪图像的全部内容。如果希望执行更多的操作,可以使用 2D 渲染上下文的变形功能,我们马上开始介绍这部分内容。
3. 图像变形
正如前面介绍的,在画布中绘制图像之后,我们就可以对它执行所有的 2D 渲染上下文方法。变形作为一组方法使我们能够在图像上做出一些非常漂亮的效果。现在继续学习如何使用它们来操作图像。
3.1 平移
这是到目前为止最简单的图像变形方法:
context.translate(100, 100); const image = new Image(); image.src = 'picture.jpg'; image.onload = function() { context.drawImage(image, 50, 50, 300, 200); }
它在绘制图像之前将画布平移,代码是我们已经熟悉的。
3.2 旋转
以前,在浏览器中旋转图像是很难实现的,但是利用画布这个操作变得很容易。
context.translate(250, 250); context.rotate(0.7854): // 旋转 45 度 const image = new Image(); image.src = 'picture.jpg'; image.onload = function() { context.drawImage(image, 0, 0, 500, 500, -150, -150, 300, 300); }
同样,这段代码中并没有什么新东西。
3.3 缩放与翻转
所有的变形方法中最随机的一个就是完全翻转图像。例如,通过各种方式对同一图像进行翻转,可以创建出万花筒效果。
const image = new Image(); image.src = 'picture.jpg'; image.onload = function () { // 左上角 context.translate(50, 50); context.drawImage(image, 0, 0, 500, 500, 0, 0, 200, 200); //左下角 context.setTransform(1, 0, 0, 1, 0, 0); // 重置变换矩阵 context.translate(50, 450); context.scale(1, -1); context.drawImage(image, 0, 0, 500, 500, 0, 0, 200, 200); //右下角 context.setTransform(1, 0, 0, 1, 0, 0); context.translate(450, 450); context.scale(-1, -1); context.drawImage(image, 0, 0, 500, 500, 0, 0, 200, 200); //右上角 context.setTransform(1, 0, 0, 1, 0, 0); context.translate(450, 50); context.scale(-1, 1); context.drawImage(image, 0, 0, 500, 500, 0, 0, 200, 200); }
这段代码太长了可能有点难以理解,但是其过程实际上很简单。它所执行的操作就是在 4 个不同位置绘制同一个图像,每一个都具有不同的缩放因子。如果使用负数缩放因子,就会使图像翻转。一定要记住,当图像翻转时,原点会转到图像右边,所以你必须移动原点进行补偿,以便从右到左进行绘制。例如,右上角的图像是在位置(450, 50)
上绘制的,因为它已经在 x 轴方向翻转,这意味着现在它是从 x 轴450像素位置画到 x 轴250像素位置(从右到左)。
这个过程有点违反直觉,但是这确实是一种能够创建有趣图像效果的简单方法。例如,它完全可以用来在画布中绘制出人造的反射效果。