Answers:
有两个最佳竞争者:
创建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 );
使用fillRect()
绘制像素(应该没有走样的问题):
ctx.fillStyle = "rgba("+r+","+g+","+b+","+(a/255)+")";
ctx.fillRect( x, y, 1, 1 );
您可以在以下位置测试其速度:http : //jsperf.com/setting-canvas-pixel/9或此处https://www.measurethat.net/Benchmarks/Show/1664/1
我建议针对您关心的浏览器进行测试以实现最大速度。截至2017年7月,fillRect()
在Firefox v54和Chrome v59(Win7x64)上速度提高了5-6倍。
其他更明智的选择是:
使用getImageData()/putImageData()
整个画布上; 这比其他选项要慢100倍。
使用数据网址创建自定义图片并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()
仅涂抹您想要的像素。这可能会非常快,但是有局限性,您需要预先计算所需的像素。
请注意,我的测试并不尝试保存和恢复canvas上下文fillStyle
;这会降低fillRect()
性能。还要注意,我不是从一开始就开始,也不是在每次测试中都测试完全相同的像素集。
fillRect()
最近的速度几乎比Chromev24上的1x1 putimagedata快10倍。因此...如果速度至关重要,并且您了解目标受众,请不要犹豫过时的答案(甚至是我的答案)。相反:测试!
还没有提到的一种方法是使用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);
我没考虑过fillRect()
,但是答案促使我以此为基准putImage()
。
在旧版MacBook Pro上使用Chrome 9.0.597.84将100,000个随机着色的像素放置在随机位置上,使用花费的时间少于100毫秒putImage()
,但使用的花费不到900 毫秒fillRect()
。(位于http://pastebin.com/4ijVKJcC的基准代码)。
相反,如果我在循环之外选择一种颜色,然后在随机位置绘制该颜色,则putImage()
需要59ms vs 102ms fillRect()
。
似乎rgb(...)
大多数语法差异都是由语法生成和解析CSS颜色规范引起的。
ImageData
另一方面,将原始RGB值直接放入一个块中不需要进行字符串处理或解析。
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;
}
putImageData()
在该函数之后调用,否则上下文将通过引用进行更新?
看起来很奇怪,但是HTML5仍然支持绘制线条,圆形,矩形和许多其他基本形状,但它没有适合绘制基本点的任何内容。这样做的唯一方法是使用您拥有的任何东西来模拟点。
因此,基本上有3种可能的解决方案:
他们每个人都有缺点
线
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();
}
所有这些解决方案的问题:
如果您想知道“ 绘制点的最佳方法是什么? ”,我将使用实心矩形。您可以在这里查看我的jsperf以及比较测试。
矩形呢?这必须比创建ImageData
对象更有效。
putImageData
它,则它的速度比fillRect
Chrome 快10倍。(有关更多信息,请参见我的回答。)
嗯,您也可以只画一条宽度为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以及转换矩阵,线帽等,通过添加矩形(不是像素)来使用上下文进行绘制。第二个模型替换了整个像素集(也许是一个,但是为什么) ?)
结果与jsperf所示不同。
没有人愿意一次设置一个像素(意味着在屏幕上绘制)。这就是为什么没有特定的API可以这样做的原因(正确的做法是这样做)。
在性能方面,如果目标是生成图片(例如,光线跟踪软件),则始终希望使用通过getImageData()
优化Uint8Array 获得的数组。然后,您使用呼叫putImageData()
一次或每秒几次setTimeout/seTInterval
。
fillRect
很痛苦,因为Chrome的硬件加速无法满足它对GPU的单独调用。我最终不得不使用1:1的像素数据,然后使用CSS缩放来获得所需的输出。丑陋的:(
get/putImageData
,而速度为194,893 fillRect
。1x1 image data
是125,102 Ops / sec。因此fillRect
,到目前为止,在Firefox中胜出。因此,从2012年到今天,情况发生了很大变化。与往常一样,永远不要依赖旧的基准测试结果。
快速的HTML演示代码: 基于我对SFML C ++图形库的了解:
使用UTF-8编码将其另存为HTML文件并运行。 随意重构,我喜欢使用日语变量,因为它们简洁明了,并且不会占用太多空间
很少要设置一个任意像素并将其显示在屏幕上。所以用
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>
HANDY和放置像素(pp)函数(ES6)的命题(此处为 read-pixel ):
let pp= ((s='.myCanvas',c=document.querySelector(s),ctx=c.getContext('2d'),id=ctx.createImageData(1,1)) => (x,y,r=0,g=0,b=0,a=255)=>(id.data.set([r,g,b,a]),ctx.putImageData(id, x, y),c))()
pp(10,30,0,0,255,255); // x,y,r,g,b,a ; return canvas object
该功能使用putImageData
并具有初始化部分(第一行)。在开始时,请s='.myCanvas'
使用CSS选择器选择画布。
如果您想将参数标准化为0-1的值,则应将默认值更改a=255
为a=1
并与:
id.data.set([r,g,b,a]),ctx.putImageData(id, x, y)
匹配
id.data.set([r*255,g*255,b*255,a*255]),ctx.putImageData(id, x*c.width, y*c.height)
上面方便的代码适合临时测试图形算法或进行概念验证,但不适用于代码应易读且清晰的生产环境。
putImageData
可能比fillRect
本地人快。我认为这是因为第五个参数可以使用必须解释的字符串来分配不同的方式(矩形颜色)。
假设您正在这样做:
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
调用中的第五个参数是更长的字符串。
context.fillStyle = ...
。developer.mozilla.org/en-US/docs/Web/API/…–