Html 在 HTML5 画布中设置单个像素的最佳方法是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4899799/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-29 06:31:50  来源:igfitidea点击:

What's the best way to set a single pixel in an HTML5 canvas?

htmlcanvaspixel

提问by Alnitak

The HTML5 Canvas has no method for explicitly setting a single pixel.

HTML5 Canvas 没有明确设置单个像素的方法。

It might be possible to set a pixel using a very short line, but then antialising and line caps might interfere.

使用非常短的线设置像素是可能的,但是抗锯齿和线帽可能会干扰。

Another way might be to create a small ImageDataobject and using:

另一种方法可能是创建一个小ImageData对象并使用:

context.putImageData(data, x, y)

to put it in place.

将其放置到位。

Can anyone describe an efficient and reliable way of doing this?

任何人都可以描述一种有效且可靠的方法吗?

回答by Phrogz

There are two best contenders:

有两个最好的竞争者:

  1. Create a 1×1 image data, set the color, and putImageDataat the location:

    var id = myContext.createImageData(1,1); // only do this once per page
    var d  = id.data;                        // only do this once per page
    d[0]   = r;
    d[1]   = g;
    d[2]   = b;
    d[3]   = a;
    myContext.putImageData( id, x, y );     
    
  2. Use fillRect()to draw a pixel (there should be no aliasing issues):

    ctx.fillStyle = "rgba("+r+","+g+","+b+","+(a/255)+")";
    ctx.fillRect( x, y, 1, 1 );
    
  1. 创建一个 1×1 的图像数据,设置颜色,并putImageData在位置:

    var id = myContext.createImageData(1,1); // only do this once per page
    var d  = id.data;                        // only do this once per page
    d[0]   = r;
    d[1]   = g;
    d[2]   = b;
    d[3]   = a;
    myContext.putImageData( id, x, y );     
    
  2. 使用fillRect()绘制像素(应该没有走样的问题):

    ctx.fillStyle = "rgba("+r+","+g+","+b+","+(a/255)+")";
    ctx.fillRect( x, y, 1, 1 );
    

You can test the speed of these here: http://jsperf.com/setting-canvas-pixel/9or here https://www.measurethat.net/Benchmarks/Show/1664/1

您可以在此处测试这些速度:http: //jsperf.com/setting-canvas-pixel/9或此处https://www.measurethat.net/Benchmarks/Show/1664/1

I recommend testing against browsers you care about for maximum speed.As of July 2017, fillRect()is 5-6× faster on Firefox v54 and Chrome v59 (Win7x64).

我建议针对您关心的浏览器进行测试以获得最大速度。截至 2017 年 7 月,fillRect()在 Firefox v54 和 Chrome v59 (Win7x64) 上快 5-6 倍。

Other, sillier alternatives are:

其他更愚蠢的选择是:

  • using getImageData()/putImageData()on the entire canvas; this is about 100× slower than other options.

  • creating a custom image using a data url and using drawImage()to show it:

    var img = new Image;
    img.src = "data:image/png;base64," + myPNGEncoder(r,g,b,a);
    // Writing the PNGEncoder is left as an exercise for the reader
    
  • creating another img or canvas filled with all the pixels you want and use drawImage()to blit just the pixel you want across. This would probably be very fast, but has the limitation that you need to pre-calculate the pixels you need.

  • 使用getImageData()/putImageData()整个画布上; 这比其他选项慢大约 100 倍。

  • 使用数据 url 创建自定义图像并drawImage()用于显示它:

    var img = new Image;
    img.src = "data:image/png;base64," + myPNGEncoder(r,g,b,a);
    // Writing the PNGEncoder is left as an exercise for the reader
    
  • 创建另一个 img 或画布,其中填充了您想要的所有像素,并用于drawImage()仅对您想要的像素进行 blit。这可能会非常快,但有一个限制,即您需要预先计算所需的像素。

Note that my tests do not attempt to save and restore the canvas context fillStyle; this would slow down the fillRect()performance. Also note that I am not starting with a clean slate or testing the exact same set of pixels for each test.

请注意,我的测试不会尝试保存和恢复画布上下文fillStyle;这会降低fillRect()性能。另请注意,我不是从干净的石板开始,也不是为每个测试测试完全相同的像素集。

回答by Alnitak

I hadn't considered fillRect(), but the answers spurred me to benchmark it against putImage().

我没有考虑过fillRect(),但答案促使我将其与putImage().

Putting 100,000 randomly coloured pixels in random locations, with Chrome 9.0.597.84 on an (old) MacBook Pro, takes less than 100ms with putImage(), but nearly 900ms using fillRect(). (Benchmark code at http://pastebin.com/4ijVKJcC).

将 100,000 个随机着色的像素放置在随机位置,使用 Chrome 9.0.597.84 在(旧)MacBook Pro 上,使用 花费不到 100 毫秒putImage(),但使用fillRect(). (http://pastebin.com/4ijVKJcC 上的基准代码)。

If instead I choose a single colour outside of the loops and just plot that colour at random locations, putImage()takes 59ms vs 102ms for fillRect().

相反,如果我在循环之外选择一种颜色并在随机位置绘制该颜色,则putImage()需要 59ms vs 102ms for fillRect().

It seems that the overhead of generating and parsing a CSS colour specification in rgb(...)syntax is responsible for most of the difference.

似乎在rgb(...)语法中生成和解析 CSS 颜色规范的开销是造成大部分差异的原因。

Putting raw RGB values straight into an ImageDatablock on the other hand requires no string handling or parsing.

ImageData另一方面,将原始 RGB 值直接放入块中不需要字符串处理或解析。

回答by PAEz

One method that hasnt been mentioned is using getImageData and then putImageData.
This method is good for when you want to draw a lot in one go, fast.
http://next.plnkr.co/edit/mfNyalsAR2MWkccr

一种尚未提及的方法是使用 getImageData 然后使用 putImageData。
当您想一次性快速绘制大量内容时,此方法很有用。
http://next.plnkr.co/edit/mfNyalsAR2MWkccr

  var canvas = document.getElementById('canvas');
  var ctx = canvas.getContext('2d');
  var canvasWidth = canvas.width;
  var canvasHeight = canvas.height;
  ctx.clearRect(0, 0, canvasWidth, canvasHeight);
  var id = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
  var pixels = id.data;

    var x = Math.floor(Math.random() * canvasWidth);
    var y = Math.floor(Math.random() * canvasHeight);
    var r = Math.floor(Math.random() * 256);
    var g = Math.floor(Math.random() * 256);
    var b = Math.floor(Math.random() * 256);
    var off = (y * id.width + x) * 4;
    pixels[off] = r;
    pixels[off + 1] = g;
    pixels[off + 2] = b;
    pixels[off + 3] = 255;

  ctx.putImageData(id, 0, 0);

回答by Vit Kaspar

function setPixel(imageData, x, y, r, g, b, a) {
    var index = 4 * (x + y * imageData.width);
    imageData.data[index+0] = r;
    imageData.data[index+1] = g;
    imageData.data[index+2] = b;
    imageData.data[index+3] = a;
}

回答by Daniel

Since different browsers seems to prefer different methods, maybe it would make sense to do a smaller test with all three methods as a part of the loading process to find out which is best to use and then use that throughout the application?

由于不同的浏览器似乎更喜欢不同的方法,也许在加载过程中对所有三种方法进行较小的测试以找出最适合使用的方法,然后在整个应用程序中使用它是有意义的?

回答by Salvador Dali

It seems strange, but nonetheless HTML5 supports drawing lines, circles, rectangles and many other basic shapes, it does not have anything suitable for drawing the basic point. The only way to do so is to simulate point with whatever you have.

看起来很奇怪,但 HTML5 支持绘制线条、圆形、矩形等许多基本形状,它没有任何适合绘制基本点的东西。这样做的唯一方法是用你拥有的任何东西来模拟点。

So basically there are 3 possible solutions:

所以基本上有3种可能的解决方案:

  • draw point as a line
  • draw point as a polygon
  • draw point as a circle
  • 将点画成一条线
  • 将点绘制为多边形
  • 将点画成一个圆圈

Each of them has their drawbacks

他们每个人都有自己的缺点



Line

线

function point(x, y, canvas){
  canvas.beginPath();
  canvas.moveTo(x, y);
  canvas.lineTo(x+1, y+1);
  canvas.stroke();
}

Keep in mind that we are drawing to South-East direction, and if this is the edge, there can be a problem. But you can also draw in any other direction.

请记住,我们正在向东南方向绘制,如果这是边缘,则可能会有问题。但是您也可以向任何其他方向绘制。



Rectangle

长方形

function point(x, y, canvas){
  canvas.strokeRect(x,y,1,1);
}

or in a faster way using fillRect because render engine will just fill one pixel.

或者以更快的方式使用 fillRect 因为渲染引擎只会填充一个像素。

function point(x, y, canvas){
  canvas.fillRect(x,y,1,1);
}

Circle

圆圈



One of the problems with circles is that it is harder for an engine to render them

圆圈的问题之一是引擎更难渲染它们

function point(x, y, canvas){
  canvas.beginPath();
  canvas.arc(x, y, 1, 0, 2 * Math.PI, true);
  canvas.stroke();
}

the same idea as with rectangle you can achieve with fill.

与使用填充可以实现的矩形相同的想法。

function point(x, y, canvas){
  canvas.beginPath();
  canvas.arc(x, y, 1, 0, 2 * Math.PI, true);
  canvas.fill();
}

Problems with all these solutions:

所有这些解决方案的问题:

  • it is hard to keep track of all the points you are going to draw.
  • when you zoom in, it looks ugly.
  • 很难跟踪您要绘制的所有点。
  • 当你放大时,它看起来很丑。

If you are wondering, "What is the best way to draw a point?", I would go with filled rectangle. You can see my jsperf here with comparison tests.

如果您想知道“绘制点的最佳方法是什么”,我会选择实心矩形。你可以在这里看到我的jsperf 对比测试

回答by sdleihssirhc

What about a rectangle? That's got to be more efficient than creating an ImageDataobject.

长方形呢?这必须比创建ImageData对象更有效。

回答by the_e

Draw a rectangle like sdleihssirhc said!

像sdleihssirhc说的那样画一个矩形!

ctx.fillRect (10, 10, 1, 1);

^-- should draw a 1x1 rectangle at x:10, y:10

^-- 应该在 x:10, y:10 处绘制一个 1x1 的矩形

回答by Boing

To complete Phrogz very thorough answer, there is a critical difference between fillRect()and putImageData().
The first uses context to draw overby addinga rectangle (NOT a pixel), using the fillStylealpha value AND the context globalAlphaand the transformation matrix, line caps etc..
The second replaces an entire set of pixels(maybe one, but why ?)
The result is different as you can see on jsperf.

要完成 Phrogz 非常彻底的回答,fillRect()和之间存在关键区别putImageData()
第一用途上下文绘制通过添加矩形(NOT的像素),使用填充样式α值和上下文globalAlpha的变换矩阵线帽等。
所述第二内容替换整个的像素的集合(也许有,但为什么?)
结果与您在jsperf 上看到的不同。


Nobody wants to set one pixel at a time (meaning drawing it on screen). That is why there is no specific API to do that (and rightly so).
Performance wise, if the goal is to generate a picture (for example a ray-tracing software), you alwayswant to use an array obtained by getImageData()which is an optimized Uint8Array. Then you call putImageData()ONCE or a few times per second using setTimeout/seTInterval.


没有人想一次设置一个像素(意味着在屏幕上绘制它)。这就是为什么没有特定的 API 来做到这一点(而且是正确的)。
性能方面,如果目标是生成图片(例如光线追踪软件),您总是希望使用getImageData()优化的 Uint8Array获得的数组。然后你putImageData()使用setTimeout/seTInterval.

回答by trusktr

Hmm, you could also just make a 1 pixel wide line with a length of 1 pixel and make it's direction move along a single axis.

嗯,您也可以制作一条 1 像素宽、1 像素长的线,并使其方向沿单个轴移动。

            ctx.beginPath();
            ctx.lineWidth = 1; // one pixel wide
            ctx.strokeStyle = rgba(...);
            ctx.moveTo(50,25); // positioned at 50,25
            ctx.lineTo(51,25); // one pixel long
            ctx.stroke();