17.访问像素值

虽然调整尺寸,裁剪和变形可用来创建有趣的图像效果,但画布还有另一个更强大的特性:像素处理。通过访问2D渲染上下文的各个像素,我们就能够得到每一个像素的颜色和阿尔法值等信息。我们还能够修改每一个像素的颜色,使之显示出截然不同的效果。

在画布中访问像素的方法是getImageData。这个方法有4个参数:要访问的像素区域原点坐标(x,y)、像素区域的宽度和高度(参见图1)。它可以用代码表示为:

context.getImageData(x, y, width, height); 

调用getImageData ,但是它会返回一个2D渲染上下文ImageData 对象,这个ImageData对象包含3个属性:width 表示所访问像素区域的宽度,height 表示像素区域的高度,data 是一个包含所访问区域中全部像素信息的CanvasPixelArray。

图1
图1 调用getImageData方法的示意图

width和height属性不需要多做解释,此处我们真正关注的是data属性。data属性存储的是一个CanvasPixelArray,它是一个JavaScript一维数组,每一个像素用4个整数值表示,范围从0至255,分别表示红(r)、绿(g)、蓝(b)和阿尔法值(a)(参见图2)。所以,数组的前4项(0-3)是第一个像素的颜色值,接下来4项(4-7)是第二个像素的颜色值,以此类推。CanvasPixelArray 在这里是关键,所以一定要正确理解它的工作原理。

图2
图2 3×3区域的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)。

图3
图3 访问CanvasPixelArray中的像素

一旦得到红色像素的索引值,其他部分就很简单了。只需要给红色索引值分别加上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)。

图4
图4 找到鼠标点击位置在画布中的(x,y)坐标值

现在,我们得到了点击位置在画布中的(x,y)位置,下一步是查询该点的颜色值。为此,我们将canvasXcanvasY 传入getImageData方法。我们只需要一个像素的数据,这就是把getImageData调用的宽度和高度都设为1的原因,这样可以保持数据尽可能小。

一旦得到ImageData对象,就可以将它保存在一个变量中,然后访问data属性中的CanvasPixelArray。由于只得到一个像素的数据,所以检索颜色值就简单到只需访问CanvasPixelArray中的前4个索引。我们将修改整个网页的css背景,所以要用这些值创建一个表示CSS RGBA颜色值的字符串。

最后一步是将这个css颜色值传递给jQuery的css方法,它可以修改HTML body元素的background-color CSS属性。如果一切正常,这会把网页的背景颜色设置为你在画布中点击的那个像素的颜色,参见图5。

图5 根据画布像素颜色修改背景颜色

安全问题

如果在自己的计算机上操作这些例子,而不是将它上传到Web服务器上,那么你可能不会看到任何结果或者会遇到一个安全错误。这是因为,如果图像与控制画布的JavaScript不在同一个位置,那么画布对于访问这个图像的像素级数据会有严格的限制。解决这个问题的最简单方法就是将这些例子上传到一个Wcb服务器上,或者上传到一个本地开发环境中,如Mac的MAMP或Windows的WAMP。这种解决方法的关键在于JavaScript和所访问的图像必须通过相同的域名访问。

按照一般的经验,如果执行像素操作时出现问题,那么一定要确认所有内容都是位于同一个域名下。

文件下载(已下载 2755 次)

发布时间:2013/2/2 下午7:28:05  阅读次数:7519

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号