context.putImageData(data, x, y)
将其放置在适当的位置。
有人能描述一种高效可靠的方法来完成这个任务吗?
context.putImageData(data, x, y)
将其放置在适当的位置。
有人能描述一种高效可靠的方法来完成这个任务吗?
有两个最佳选择:
创建一个1x1像素的图像数据,设置颜色,并使用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倍。
使用数据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 或 canvas,并使用 drawImage()
转移所需的像素。这可能非常快,但其限制在于您需要预先计算所需的像素。
请注意,我的测试未尝试保存和恢复画布上下文中的 fillStyle
; 这会减慢 fillRect()
的性能。还请注意,我没有从一个干净的画布开始,也没有针对每个测试测试完全相同的像素集。
还有一种方法没有被提到,就是使用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()
进行对比测试。
在Chrome 9.0.597.84上的(旧)MacBook Pro上,在随机位置放置100,000个随机颜色像素,使用putImage()
不到100毫秒,而使用fillRect()
则需要近900毫秒。(基准代码在http://pastebin.com/4ijVKJcC中)。
如果我选择循环外的单个颜色,并在随机位置绘制该颜色,则putImage()
需要59ms,而fillRect()
需要102ms。
看来在rgb(...)
语法中生成和解析CSS颜色规范的开销是造成大部分差异的原因。
另一方面,直接将原始RGB值放入ImageData
块中不需要字符串处理或解析。
fillRect()方法:363毫秒
putImage()方法:716毫秒
- undefinedfunction 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()
函数,还是上下文会通过引用更新? - Lucas Sousafunction 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();
}
与矩形相同的思路,你可以通过使用fill实现。
function point(x, y, canvas){
canvas.beginPath();
canvas.arc(x, y, 1, 0, 2 * Math.PI, true);
canvas.fill();
}
所有这些解决方案的问题:
如果你在想,“什么是绘制点的最佳方式?”,我会选择填充矩形。您可以查看我的jsperf测试结果进行比较。
由于不同的浏览器似乎更喜欢不同的方法,因此在加载过程中进行一次包含所有三种方法的小测试,以找出最佳方法并在整个应用程序中使用可能是有意义的。
那矩形呢?相比创建一个 ImageData
对象来说,这肯定更加高效。
putImageData
,在Chrome浏览器中比使用fillRect
快10倍。(详情请参见我的回答。) - Phrogz按照sdleihssirhc的说法,画一个矩形!
ctx.fillRect (10, 10, 1, 1);
^-- 应该在x:10,y:10处绘制一个1x1的矩形
快速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>
嗯,你也可以制作一个长度为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();
fillRect()
函数最近比Chromev24版本的1x1 putimagedata快了近10倍。因此,如果速度至关重要且您了解目标受众,请不要相信过时的答案(包括我的)。而是请进行测试!test! - Phrogz