最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to draw on an HTML5 Canvas, pixel-by-pixel - Stack Overflow

programmeradmin4浏览0评论

Suppose that I have a 900x900 HTML5 Canvas element.

I have a function called computeRow that accepts, as a parameter, the number of a row on the grid and returns an array of 900 numbers. Each number represents a number between 0 and 200. There is an array called colors that contains an array of strings like rgb(0,20,20), for example.

Basically, what I'm saying is that I have a function that tells pixel-by-pixel, what color each pixel in a given row on the canvas is supposed to be. Running this function many times, I can compute a color for every pixel on the canvas.

The process of running computeRow 900 times takes about 0.5 seconds.

However, the drawing of the image takes much longer than that.

What I've done is I've written a function called drawRow that takes an array of 900 numbers as the input and draws them on the canvas. drawRow takes lots longer to run than computeRow! How can I fix this?

drawRow is dead simple. It looks like this:

function drawRow(rowNumber, result /* array */) {
    var plot, context, columnNumber, color;
    plot = document.getElementById('plot');
    context = plot.getContext('2d');
    // Iterate over the results for each column in the row, coloring a single pixel on
    // the canvas the correct color for each one.
    for(columnNumber = 0; columnNumber < width; columnNumber++) {
        color = colors[result[columnNumber]];
        context.fillStyle = color;
        context.fillRect(columnNumber, rowNumber, 1, 1);
    }
}

Suppose that I have a 900x900 HTML5 Canvas element.

I have a function called computeRow that accepts, as a parameter, the number of a row on the grid and returns an array of 900 numbers. Each number represents a number between 0 and 200. There is an array called colors that contains an array of strings like rgb(0,20,20), for example.

Basically, what I'm saying is that I have a function that tells pixel-by-pixel, what color each pixel in a given row on the canvas is supposed to be. Running this function many times, I can compute a color for every pixel on the canvas.

The process of running computeRow 900 times takes about 0.5 seconds.

However, the drawing of the image takes much longer than that.

What I've done is I've written a function called drawRow that takes an array of 900 numbers as the input and draws them on the canvas. drawRow takes lots longer to run than computeRow! How can I fix this?

drawRow is dead simple. It looks like this:

function drawRow(rowNumber, result /* array */) {
    var plot, context, columnNumber, color;
    plot = document.getElementById('plot');
    context = plot.getContext('2d');
    // Iterate over the results for each column in the row, coloring a single pixel on
    // the canvas the correct color for each one.
    for(columnNumber = 0; columnNumber < width; columnNumber++) {
        color = colors[result[columnNumber]];
        context.fillStyle = color;
        context.fillRect(columnNumber, rowNumber, 1, 1);
    }
}
Share Improve this question asked Dec 30, 2013 at 1:57 Vivian RiverVivian River 32.4k64 gold badges210 silver badges323 bronze badges
Add a comment  | 

3 Answers 3

Reset to default 9

I'm not sure exactly what you are trying to do, so I apologize if I am wrong.

If you are trying to write a color to each pixel on the canvas, this is how you would do it:

var ctx = document.getElementById('plot').getContext('2d');
var imgdata = ctx.getImageData(0,0, 640, 480);
var imgdatalen = imgdata.data.length;
for(var i=0;i<imgdatalen/4;i++){  //iterate over every pixel in the canvas
  imgdata.data[4*i] = 255;    // RED (0-255)
  imgdata.data[4*i+1] = 0;    // GREEN (0-255)
  imgdata.data[4*i+2] = 0;    // BLUE (0-255)
  imgdata.data[4*i+3] = 255;  // APLHA (0-255)
}
ctx.putImageData(imgdata,0,0);

This is a lot faster than drawing a rectangle for every pixel. The only thing you would need to do is separate you color into rgba() values.

If you read the color values as strings from an array for each pixel it does not really matter what technique you use as the bottleneck would be that part right there.

For each pixel the cost is split on (roughly) these steps:

  • Look up array (really a node/linked list in JavaScript)
  • Get string
  • Pass string to fillStyle
    • Parse string (internally) into color value
  • Ready to draw a single pixel

These are very costly operations performance-wise. To get it more efficient you need to convert that color array into something else than an array with strings ahead of the drawing operations.

You can do this several ways:

  • If the array comes from a server try to format the array as a blob / typed array instead before sending it. This way you can copy the content of the returned array almost as-is to the canvas' pixel buffer.
  • Use a web workers to parse the array and pass it back as a transferable object which you them copy into the canvas' buffer. This can be copied directly to the canvas - or do it the other way around, transfer the pixel buffer to worker, fill there and return.
  • Sort the array by color values and update the colors by color groups. This way you can use fillStyle or calculate the color into an Uint32 value which you copy to the canvas using a Uint32 buffer view. This does not work well if the colors are very spread but works ok if the colors represent a small palette.

If you're stuck with the format of the colors then the second option is what I would recommend primarily depending on the size. It makes your code asynchronous so this is an aspect you need to deal with as well (ie. callbacks when operations are done).

You can of course just parse the array on the same thread and find a way to camouflage it a bit for the user in case it creates a noticeable delay (900x900 shouldn't be that big of a deal even for a slower computer).

If you convert the array convert it into unsigned 32 bit values and store the result in a Typed Array. This way you can iterate your canvas pixel buffer using Uint32's instead which is much faster than using byte-per-byte approach.

fillRect is meant to be used for just that - filling an area with a single color, not pixel by pixel. If you do pixel by pixel, it is bound to be slower as you are CPU bound. You can check it by observing the CPU load in these cases. The code will become more performant if

  • A separate image is created with the required image data filled in. You can use a worker thread to fill this image in the background. An example of using worker threads is available in the blog post at http://gpupowered.org/node/11

  • Then, blit the image into the 2d context you want using context.drawImage(image, dx, dy).

发布评论

评论列表(0)

  1. 暂无评论