17.访问像素值
虽然调整尺寸,裁剪和变形可用来创建有趣的图像效果,但画布还有另一个更强大的特性:像素处理。通过访问2D渲染上下文的各个像素,我们就能够得到每一个像素的颜色和阿尔法值等信息。我们还能够修改每一个像素的颜色,使之显示出截然不同的效果。
在画布中访问像素的方法是getImageData
。这个方法有4个参数:要访问的像素区域原点坐标(x,y)、像素区域的宽度和高度(参见图1)。它可以用代码表示为:
context.getImageData(x, y, width, height);
调用getImageData
,但是它会返回一个2D渲染上下文ImageData
对象,这个ImageData对象包含3个属性:width
表示所访问像素区域的宽度,height
表示像素区域的高度,data
是一个包含所访问区域中全部像素信息的CanvasPixelArray。
width和height属性不需要多做解释,此处我们真正关注的是data属性。data属性存储的是一个CanvasPixelArray
,它是一个JavaScript一维数组,每一个像素用4个整数值表示,范围从0至255,分别表示红(r)、绿(g)、蓝(b)和阿尔法值(a)(参见图2)。所以,数组的前4项(0-3)是第一个像素的颜色值,接下来4项(4-7)是第二个像素的颜色值,以此类推。CanvasPixelArray
在这里是关键,所以一定要正确理解它的工作原理。
在详细解释之前,先看一个简单示例。我们使用图2所定义的索引数字来访问CanvasPixelArray
中第一个像素的RGBA值。
var numPixels = imageData.width*imageData.height; for (var i = 0; i < numPixels; i++) { pixels[i*4] = 255; // Red pixels[i*4+1] = 0; // Green pixels[i*4+2] = 0; // Blue pixels[i*4+3] = 255; // Alpha };
CanvasPixelArray
本身绝对不知道所访问的像素区域的尺寸。相反,返回的数组实际上只是一长串RGBA颜色值,它的长度等于所访问区域的像素个数乘以4(每个像素有4个颜色值)。例如,如果访问一个宽度和高度均为3个像素的像素栅格,那么CanvasPixelArray
的长度就是36(3×3×4),宽度和高度为200时,则长度为160 000 (200×200×4),以此类推。
CanvasPixelArray
中的像素排列顺序很简单:左上角像素位于数组开头(从位置0红色到位置3阿尔法值),而右下角像素位于数组末尾。这意味着,在所访问的区域中,每一行像素是从左到右访问的,直至到达行尾,然后再同样从左到右访问下一行(参见图2的栅格)。所以,如果CanvasPixelArray
只是一长串颜色值,而不知道像素区域的尺寸,那么应该如何从数组访问一个具体像素呢?在图2所示的例子中,应该如何访问(x,y)坐标位置为(2,2)的中心像素呢?通过查看图2,我们很容易发现它从数组索引16开始,但是如果没有这个图,我们应该如何确定呢?一些聪明的人已经帮我们计算出一个公式,我们可以用这个公式准确地计算出你需要从CanvasPixelArray中访问的像素,而且它非常简单:
var imageData = context.getImageData(0, 0, 3, 3); // 3x3 grid var width = imageData.width; var x = 2; var y = 2; var pixelRed = ((y-1)*(width*4))+((x-1)*4); var pixelGreen = pixelRed+1; var pixelBlue = pixelRed+2; var pixelAlpha = pixelRed+3;
现在,我们最关注的地方是计算像素红色值索引位置的公式。我们拆解分析这个公式,以了解它的计算原理:
(y-1)
因为我们使用非0坐标值定义像素的(x,y)坐标位置,所以需要将坐标值减1。它的作用只是将画布所使用的坐标系统转换为数组所使用的从0开始的坐标系统。
(width*4)
这会得到图像中每一行的颜色值个数。通过将(y-1)的结果与这个数相乘,就能够得到所访问行的开头位置的数组索引值(y坐标位置)。在这个例子中,索引值是12,这对应图2第二行。
(x-1)*4
这里我们对y坐标位置重复相同的计算一一将它转换成从0开始的坐标系统。然后,将列(x位置)乘以4,得到所访问列的前一行颜色值个数。
将列索引值与行索引值相加,最终可以得到所访问像素的第一个颜色(红色)的索引值。在这个例子中,它应该是16(参见图3)。
一旦得到红色像素的索引值,其他部分就很简单了。只需要给红色索引值分别加上1、2或3,就可以得到其他三种颜色——绿、蓝和阿尔法值。
下面来创建一个有趣的颜色拾取器。
var image = new Image(); image.src = "example.jpg"; $(image).load(function() { context.drawImage(image, 0, 0, 500, 333); }); canvas.click(function(e) { var canvasOffset = canvas.offset(); var canvasX = Math.floor(e.pageX-canvasOffset.left); var canvasY = Math.floor(e.pageY-canvasOffset.top); var imageData = context.getImageData(canvasX, canvasY, 1, 1); var pixel = imageData.data; var pixelColor = "rgba("+pixel[0]+", "+pixel[1]+", "+pixel[2]+", "+pixel[3]+")"; $("body").css("backgroundColor", pixelColor); });
我们要关注的是jQuery的click
方法,它是在指定元素上发生鼠标点击事件时调用的。在这里,元素就是画布。click方法中的回调函数会传递给你一个包含事件信息的参数,这里是e。这个参数包含了相对于整个浏览器窗口的鼠标点击位置的(x,y)坐标,它可用来处理画布上发生的点击事件。
通过使用jQuery的offset
方法,我们就能够得到画布与浏览器窗口顶部和左边的像素距离。然后,用鼠标点击位置的x坐标(pageX)减去画布的左侧偏移量,就可以得到点击位置在画布上的x坐标。如果对鼠标点击位置y坐标和顶部偏移量进行相同的计算,将得到鼠标点击位置相对于画布原点的(x,y)坐标值(参见图4)。
现在,我们得到了点击位置在画布中的(x,y)位置,下一步是查询该点的颜色值。为此,我们将canvasX
和canvasY
传入getImageData方法。我们只需要一个像素的数据,这就是把getImageData调用的宽度和高度都设为1的原因,这样可以保持数据尽可能小。
一旦得到ImageData对象,就可以将它保存在一个变量中,然后访问data属性中的CanvasPixelArray。由于只得到一个像素的数据,所以检索颜色值就简单到只需访问CanvasPixelArray中的前4个索引。我们将修改整个网页的css背景,所以要用这些值创建一个表示CSS RGBA颜色值的字符串。
最后一步是将这个css颜色值传递给jQuery的css方法,它可以修改HTML body元素的background-color CSS属性。如果一切正常,这会把网页的背景颜色设置为你在画布中点击的那个像素的颜色,参见图5。
安全问题
如果在自己的计算机上操作这些例子,而不是将它上传到Web服务器上,那么你可能不会看到任何结果或者会遇到一个安全错误。这是因为,如果图像与控制画布的JavaScript不在同一个位置,那么画布对于访问这个图像的像素级数据会有严格的限制。解决这个问题的最简单方法就是将这些例子上传到一个Wcb服务器上,或者上传到一个本地开发环境中,如Mac的MAMP或Windows的WAMP。这种解决方法的关键在于JavaScript和所访问的图像必须通过相同的域名访问。
按照一般的经验,如果执行像素操作时出现问题,那么一定要确认所有内容都是位于同一个域名下。
文件下载(已下载 2755 次)发布时间:2013/2/2 下午7:28:05 阅读次数:8111