使用 Canvas 绘制像素

这篇文章由 Paul Rouget 撰写,他是 Mozilla 布道团队的成员。Paul 居住在法国巴黎,因其在 网络开放视频 等方面的一些出色工作而闻名。

从最简单的层面上讲,Canvas 是一种将位图数据绘制到 HTML 页面中的简单方法。它具有允许您绘制矩形、弧形、曲线和其他简单图元的方法。但是,对于某些类型的效果和绘图,您需要直接访问像素。

在 Firefox 3.5 中,我们向 canvas 元素添加了一种新方法 - createImageDatacreateImageData 调用是一种便捷方法,用于创建一组空白像素以进行操作,最终可以将其复制回画布。

由于我们正在讨论单个调用,因此我们认为值得遍历所有允许您直接读取、操作和更新画布中像素的调用,并将 createImageData 置于其完整上下文中。

检索像素数据

您无法直接操作画布中的像素。为了更改画布中的数据,您首先需要将数据复制出来,进行更改,然后将更改后的数据复制回目标画布。

The getImageData 调用允许您从画布中复制出一个矩形像素。获取画布中所有像素数据的调用如下所示

var canvas = document.getElementById('myCanvasElt');
var ctx = canvas.getContext('2d');
var canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height);

canvasData 对象包含像素数据。它具有以下成员

canvasData {
    width: unsigned long, // the width of the canvas
    height: unsigned long, // the height of the canvas
    data: CanvasPixelArray // the values of the pixels
}

数据是一个扁平的值数组,每个像素的每个分量都有一个值,从左到右、从上到下排列,每个像素都表示为 RGBA 顺序的四个值。

例如,在 2×2 画布中,将有 4 个像素表示为 16 个值,如下所示

0,0  0,1  1,0  1,1
RGBA RGBA RGBA RGBA

因此,您可以使用以下公式计算该数组的长度:width * height * 4

在一个较大的画布中,如果您想知道 x = 10、y = 20 处像素的蓝色值,则可以使用以下代码

var x = 10;
var y = 10;
var blue = canvasData.data[(y * width + x) * 4 + 2];

请注意,每个 RGB 像素的值为 0..255,alpha 位的值为 0..255,其中 0 表示完全透明,255 表示完全不透明。

创建新的像素集

如果要从头开始创建新的矩阵,只需使用 createImageData 调用,它需要两个参数:矩阵的高度和宽度。

请注意,createImageData 调用不会从现有画布中复制像素,它会生成一个空白像素矩阵,其值设置为透明黑色 (255,255,255,0)。

以下是一个示例,您希望创建一个适合画布大小的像素集

var canvas = document.getElementById('myCanvasElt');
var ctx = canvas.getContext('2d');
var canvasData = ctx.createImageData(canvas.width, canvas.height);

请注意,这是您应该用于创建像素数据的方法。早期版本的 Firefox 允许您从一个简单的 JavaScript 对象中创建一个 canvasData 对象,并在后续调用中使用它来更新画布数据。添加此调用是为了与 WebKit 保持兼容性,WebKit 在底层使用专门的对象而不是通用 JavaScript 对象。

更新像素

获得 canvasData 对象后,您可以通过数组更新像素值。以下是如何遍历数组读取和更新值的示例。

for (var x = 0; x < canvasData.width; x++)  {
    for (var y = 0; y < canvasData.height; y++)  {

        // Index of the pixel in the array
        var idx = (x + y * width) * 4;

        // If you want to know the values of the pixel
        var r = canvasData.data[idx + 0];
        var g = canvasData.data[idx + 1];
        var b = canvasData.data[idx + 2];
        var a = canvasData.data[idx + 3];

        //[...] do what you want with these values

        // If you want to update the values of the pixel
        canvasData.data[idx + 0] = ...; // Red channel
        canvasData.data[idx + 1] = ...; // Green channel
        canvasData.data[idx + 2] = ...; // Blue channel
        canvasData.data[idx + 3] = ...; // Alpha channel
    }
}

更新画布

现在您已经获得了一组更新的像素,您可以使用简单的 putImageData 调用。此调用接受 canvasData 对象以及您希望在画布中绘制像素数据矩形的 x,y 位置

var canvas = document.getElementById('myCanvasElt');
var ctx = canvas.getContext('2d');
var canvasData = ctx.putImageData(canvasData, 0, 0);

getImageData 的完整示例

以下代码将彩色图像转换为图像的灰度版本。您还可以查看 Paul 网站上的此演示的实时版本。

var canvas = document.getElementById('myCanvasElt');
var ctx = canvas.getContext('2d');
var canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height);
for (var x = 0; x < canvasData.width; x++) {
    for (var y = 0; y < canvasData.height; y++) {
        // Index of the pixel in the array
        var idx = (x + y * canvas.width) * 4;

        // The RGB values
        var r = canvasData.data[idx + 0];
        var g = canvasData.data[idx + 1];
        var b = canvasData.data[idx + 2];

        // Update the values of the pixel;
        var gray = (r + g + b) / 3;
        canvasData.data[idx + 0] = gray;
        canvasData.data[idx + 1] = gray;
        canvasData.data[idx + 2] = gray;
    }
}
ctx.putImageData(canvasData, 0, 0);

createImageData 的完整示例

此代码段将分形绘制到画布中。同样,您可以在 Paul 的网站上查看此代码的实时 演示

var canvas = document.getElementById('myCanvasElt');
var ctx = canvas.getContext('2d');
var canvasData = ctx.createImageData(canvas.width, canvas.height);

// Mandelbrot
function computeColor(x, y) {
    x = 2.5 * (x/canvas.width - 0.5);
    y = 2 * (y/canvas.height - 0.5);
    var x0 = x;
    var y0 = y;

    var iteration = 0;
    var max_iteration = 100;

    while (x * x + y * y <= 4 && iteration < max_iteration ) {
        var xtemp = x*x - y*y + x0;
        y = 2*x*y + y0;
        x = xtemp;
        iteration++;
    }

    return Math.round(255 * iteration / max_iteration);
}

for (var x = 0; x < canvasData.width; x++) {
    for (var y = 0; y < canvasData.height; y++) {
        var color = computeColor(x, y);

        // Index of the pixel in the array
        var idx = (x + y * canvas.width) * 4;

        // Update the values of the pixel;
        canvasData.data[idx + 0] = color;
        canvasData.data[idx + 1] = color;
        canvasData.data[idx + 2] = color;
        canvasData.data[idx + 3] = 255;
    }
}

ctx.putImageData(canvasData, 0, 0);

更多文档

如果您想了解有关 Canvas 的更多信息,我们强烈建议您浏览我们为 canvas 提供的 MDC 文档

我们希望这对每个人都有用,并将单个调用置于其完整上下文中。

关于 Christopher Blizzard

一次发布一个,让网络变得更好。

更多 Christopher Blizzard 的文章…


17 条评论

  1. […] 原文地址:https://hacks.mozilla.ac.cn/2009/06/pushing-pixels-with-canvas/ […]

    2009 年 6 月 9 日 01:51

  2. […] 使用 Canvas 绘制像素 […]

    2009 年 6 月 10 日 11:16

  3. […] 使用 Canvas 绘制像素 […]

    2009 年 6 月 11 日 05:57

  4. […] 原文地址:https://hacks.mozilla.ac.cn/2009/06/pushing-pixels-with-canvas/ […]

    2009 年 7 月 3 日 03:20

  5. Franz

    嗨,我只是想抱怨一下,CanvasPixelArray 很糟糕,它不是一个真正的数组(至少在 WebKit 中不是),它是一个类似数组的对象。但它缺少所有很酷的 JS 内部数组函数。而且它很慢。在 Firefox 中几乎慢到无法使用。不知道为什么。

    2010 年 11 月 11 日 00:59

    1. David Bradbury

      它的工作原理与图形缓冲区长期以来的工作原理完全相同。您根据图像的宽度、高度和 bpp 指向所需的像素,然后获取该像素的颜色值。根据需要进行操作,然后将其放入屏幕缓冲区。至于它是否慢,好吧,这需要时间。

      2011 年 7 月 18 日 11:59

      1. 室内设计师

        我试过了,但没有奏效 :(

        我认为我的操作中有一些错误

        2012 年 5 月 19 日 20:56

  6. Dwight Vietzke

    不错的帖子。谢谢。一年后,它仍然是迄今为止最好的参考。

    2010 年 12 月 24 日 13:10

  7. Geil Ficken

    非常感谢,Canvas 的东西很乱,但现在我已经做到了,这要感谢这篇文章。

    2012 年 8 月 14 日 08:44

  8. Antonio

    文章很有趣,但不幸的是,对我来说,这个技巧不起作用……

    2012 年 8 月 18 日 09:43

  9. Absensi Sidik Jari

    我对这个技巧很感兴趣,
    但为什么要这么努力 :(

    2012 年 9 月 16 日 22:41

  10. kulturystyka sklep

    不错的帖子。谢谢。一年后,它仍然是迄今为止最好的参考。

    2012 年 9 月 26 日 10:20

  11. Peter

    @Dwight Vietzke
    3 年后仍然是最好的,谢谢!:-)

    2012 年 10 月 26 日 10:05

  12. Pick A Part

    现在应该是在 4K 屏幕上播放 3D 视频了,我开始在 Firefox 和 Chrome 上进行一些像素操作,两者都运行良好,IE 仍然停留在 1999 年,真是浪费!

    2012 年 11 月 13 日 02:23

  13. Cris

    我想知道为什么在我的代码中,我首先应用了 translate(x,y),现在我的 0,0 点在画布上的另一个位置。但是当我执行
    var imageData = context.putImageData(imageData, 0,0 );
    像素绘制在原始的 0,0 坐标处,而不是在新坐标处。

    2012 年 12 月 19 日 03:13

  14. youjizz

    我无法使其工作,新版本可能是问题所在吗?我看到这是一篇旧文章

    2013 年 1 月 30 日 03:54

  15. SEO Translator

    我在 Firefox 19.0.2 上试过了,但无法使其工作……这不再兼容了吗?

    2013 年 3 月 19 日 13:09

本文的评论已关闭。