在HTML5canvas中设置单个像素的最佳方法是什么?

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

使用非常短的线条设置像素可能是有可能的,但是,antialising和line cap可能会产生干扰。

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

 context.putImageData(data, x, y) 

把它放到位。

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

有两个最好的竞争者:

  1. 创build一个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 ); 

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

我build议您针对您关心的浏览器进行testing,以获得最大速度。 截至2017年7月,Firefox v54和Chrome v59(Win7x64)的fillRect()速度提高了5-6倍。

其他更可怕的select是:

  • 在整个canvas上使用getImageData()/putImageData() ; 这比其他选项慢100倍左右。

  • 使用数据url创build一个自定义图像并使用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 
  • 创build另一个img或canvas,用所有你想要的像素填充,并使用drawImage()来将你想要的像素进行blit处理。 这可能会非常快,但有一个限制,你需要预先计算你需要的像素。

请注意,我的testing不会尝试保存和恢复canvas上下文fillStyle ; 这会减慢fillRect()性能。 另外请注意,我并没有从一个干净的石板开始,也没有为每个testingtesting完全相同的一组像素。

我没有考虑fillRect() ,但答案使我不能以putImage()为基准。

在随机位置随机放置100,000个随机颜色的像素,在旧的MacBook Pro上使用Chrome 9.0.597.84,使用putImage()less于100ms,但使用fillRect()less于900ms。 (基准代码http://pastebin.com/4ijVKJcC )。

如果我在循环之外select一种颜色,并在随机位置绘制该颜色,则putImage()需要59ms和102ms( fillRect()

似乎在rgb(...)语法中生成和parsingCSS颜色规范的开销是造成大部分区别的原因。

另一方面,将原始RGB值直接放入ImageData块中,不需要string处理或parsing。

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

那长方形呢? 这要比创build一个ImageData对象更有效率。

由于不同的浏览器似乎更喜欢不同的方法,也许这样做是有道理的做一个更小的testing所有三种方法作为加载过程的一部分,找出哪个是最好的使用,然后在整个应用程序中使用?

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

所以基本上有三种可能的解决scheme:

  • 画一条线
  • 将点画成多边形
  • 将点画成一个圆

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


线

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

请记住,我们正在向东南方向发展,如果这是边缘,可能会出现问题。 但是你也可以在其他方向上画画。


长方形

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

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

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


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

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

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

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

所有这些解决scheme的问题:

  • 很难跟踪你要画的所有点。
  • 当你放大时,它看起来很丑。

如果你想知道“什么是最好的方法来画一个点? ”,我会去填充矩形。 你可以在这里看到我的jsperf与比较testing 。

嗯,你也可以制作一个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(); 

为了完成Phrogz非常全面的答案, fillRect()putImageData()之间有一个关键的区别。
第一个使用上下文通过添加一个矩形(不是一个像素),使用fillStyle alpha值和上下文globalAlpha转换matrix线条帽等来绘制
第二个replace了一整套像素 (也许是一个,但是为什么?)
结果是不同的,你可以看到jsperf 。

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

画一个像sdleihssirhc矩形说!

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

^ – 应该在x:10,y:10处绘制一个1×1矩形

putImageData可能比fillRect本身更快。 我认为这是因为第五个参数可以有不同的方式来分配(矩形的颜色),使用必须解释的string。

假设你正在这样做:

 context.fillRect(x, y, 1, 1, "#fff") context.fillRect(x, y, 1, 1, "rgba(255, 255, 255, 0.5)")` context.fillRect(x, y, 1, 1, "rgb(255,255,255)")` context.fillRect(x, y, 1, 1, "blue")` 

所以,线路

 context.fillRect(x, y, 1, 1, "rgba(255, 255, 255, 0.5)")` 

是所有之间最重的。 fillRect调用中的第五个参数是一个稍长的string。

快速HTML演示代码:基于我所了解的关于SFML C ++graphics库:

将其另存为具有UTF-8编码的HTML文件并运行它。 随意重构,我只是喜欢使用日语variables,因为它们简洁,不占用太多的空间

你很less会想要设置一个任意像素并将其显示在屏幕上。 所以使用

 PutPix(x,y, r,g,b,a) 

方法来绘制大量的任意像素到后台缓冲区。 (便宜的电话)

然后当准备展示,打电话给

 Apply() 

方法来显示更改。 (昂贵的通话)

下面是完整的.HTML文件代码:

 <!DOCTYPE HTML > <html lang="en"> <head> <title> back-buffer demo </title> </head> <body> </body> <script> //Main function to execute once //all script is loaded: function main(){ //Create a canvas: var canvas; canvas = attachCanvasToDom(); //Do the pixel setting test: var test_type = FAST_TEST; backBufferTest(canvas, test_type); } //Constants: var SLOW_TEST = 1; var FAST_TEST = 2; function attachCanvasToDom(){ //Canvas Creation: //cccccccccccccccccccccccccccccccccccccccccc// //Create Canvas and append to body: var can = document.createElement('canvas'); document.body.appendChild(can); //Make canvas non-zero in size, //so we can see it: can.width = 800; can.height= 600; //Get the context, fill canvas to get visual: var ctx = can.getContext("2d"); ctx.fillStyle = "rgba(0, 0, 200, 0.5)"; ctx.fillRect(0,0,can.width-1, can.height-1); //cccccccccccccccccccccccccccccccccccccccccc// //Return the canvas that was created: return can; } //THIS OBJECT IS SLOOOOOWW! // 筆 == "pen" //T筆 == "Type:Pen" function T筆(canvas){ //Publicly Exposed Functions //PEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPE// this.PutPix = _putPix; //PEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPE// if(!canvas){ throw("[NilCanvasGivenToPenConstruct]"); } var _ctx = canvas.getContext("2d"); //Pixel Setting Test: // only do this once per page //絵 =="image" //資 =="data" //絵資=="image data" //筆 =="pen" var _絵資 = _ctx.createImageData(1,1); // only do this once per page var _筆 = _絵資.data; function _putPix(x,y, r,g,b,a){ _筆[0] = r; _筆[1] = g; _筆[2] = b; _筆[3] = a; _ctx.putImageData( _絵資, x, y ); } } //Back-buffer object, for fast pixel setting: //尻 =="butt,rear" using to mean "back-buffer" //T尻=="type: back-buffer" function T尻(canvas){ //Publicly Exposed Functions //PEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPE// this.PutPix = _putPix; this.Apply = _apply; //PEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPE// if(!canvas){ throw("[NilCanvasGivenToPenConstruct]"); } var _can = canvas; var _ctx = canvas.getContext("2d"); //Pixel Setting Test: // only do this once per page //絵 =="image" //資 =="data" //絵資=="image data" //筆 =="pen" var _w = _can.width; var _h = _can.height; var _絵資 = _ctx.createImageData(_w,_h); // only do this once per page var _筆 = _絵資.data; function _putPix(x,y, r,g,b,a){ //Convert XY to index: var dex = ( (y*4) *_w) + (x*4); _筆[dex+0] = r; _筆[dex+1] = g; _筆[dex+2] = b; _筆[dex+3] = a; } function _apply(){ _ctx.putImageData( _絵資, 0,0 ); } } function backBufferTest(canvas_input, test_type){ var can = canvas_input; //shorthand var. if(test_type==SLOW_TEST){ var t筆 = new T筆( can ); //Iterate over entire canvas, //and set pixels: var x0 = 0; var x1 = can.width - 1; var y0 = 0; var y1 = can.height -1; for(var x = x0; x <= x1; x++){ for(var y = y0; y <= y1; y++){ t筆.PutPix( x,y, x%256, y%256,(x+y)%256, 255 ); }}//next X/Y }else if(test_type==FAST_TEST){ var t尻 = new T尻( can ); //Iterate over entire canvas, //and set pixels: var x0 = 0; var x1 = can.width - 1; var y0 = 0; var y1 = can.height -1; for(var x = x0; x <= x1; x++){ for(var y = y0; y <= y1; y++){ t尻.PutPix( x,y, x%256, y%256,(x+y)%256, 255 ); }}//next X/Y //When done setting arbitrary pixels, //use the apply method to show them //on screen: t尻.Apply(); } } main(); </script> </html>