HTML5 Canvas没有显式设置单个像素的方法。

可以使用非常短的行设置像素,但反字符和行大写可能会干扰。

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

context.putImageData(data, x, y)

把它放在合适的位置。

有人能描述一种高效可靠的方法吗?


当前回答

纯粹出于诊断目的,我使用这个简单的函数。

请注意。如果不使用整数坐标,得到的图像是模糊的。

setPixel (context, 100, 100, 'blue');

function setPixel (ctx, x, y, c) {

//  integer coordinates are required.

    ctx.save ();
    ctx.fillStyle = c;
    ctx.fillRect (x, y, 1, 1);
    ctx.restore ();

}

其他回答

我没有考虑过fillRect(),但答案促使我对putImage()进行基准测试。

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

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

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

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

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

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

画一个点作为一条线 画一个多边形 画一个圆

每一种都有其缺点


Line

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。

快捷方便

下面的类实现了本文中描述的快速方法,并包含了所有您需要的:readPixel, putPixel,获取宽度/高度。类在调用refresh()方法后更新画布。算例求解二维波动方程的简单情形

class Screen{ constructor(canvasSelector) { this.canvas = document.querySelector(canvasSelector); this.width = this.canvas.width; this.height = this.canvas.height; this.ctx = this.canvas.getContext('2d'); this.imageData = this.ctx.getImageData(0, 0, this.width, this.height); this.buf = new ArrayBuffer(this.imageData.data.length); this.buf8 = new Uint8ClampedArray(this.buf); this.data = new Uint32Array(this.buf); } // r,g,b,a - red, gren, blue, alpha components in range 0-255 putPixel(x,y,r,g,b,a=255) { this.data[y * this.width + x] = (a<<24) | (b<<16) | (g<<8) | r; } readPixel(x,y) { let p= this.data[y * this.width + x] return [p&0xff, p>>8&0xff, p>>16&0xff, p>>>24]; } refresh() { this.imageData.data.set(this.buf8); this.ctx.putImageData(this.imageData, 0, 0); } } // -------- // TEST // -------- let s= new Screen('#canvas'); // initialise function draw() { for (var y = 1; y < s.height-1; ++y) { for (var x = 1; x < s.width-1; ++x) { let a = [[1,0],[-1,0],[0,1],[0,-1]].reduce((a,[xp,yp])=> a+= s.readPixel(x+xp,y+yp)[0] // read pixel ,0); let v= a/1.99446-tmp[x][y]; tmp[x][y]=v<0 ? 0:v; } } for (var y = 1; y < s.height-1; ++y) { for (var x = 1; x < s.width-1; ++x) { let v=tmp[x][y]; tmp[x][y]= s.readPixel(x,y)[0]; // read pixel s.putPixel(x,y, v,0,0); // put pixel } } s.refresh(); window.requestAnimationFrame(draw) } // temporary 2d buffer ()for solving wave equation) let tmp = [...Array(s.width)].map(x => Array(s.height).fill(0)); function move(e) { s.putPixel(e.x-10, e.y-10, 255,255,255);} draw(); <canvas id="canvas" height="150" width="512" onmousemove="move(event)"></canvas> <div>Move mouse on black square</div>

如果你担心速度,那么你也可以考虑WebGL。

有两个最好的竞争者:

创建一个1×1图像数据,设置颜色,putImageData在位置: var id = myContext.createImageData(1,1);//每页只做一次 Var d = id.data;//每页只做一次 D [0] = r; D [1] = g; D [2] = b; D [3] = a; myContext。putImageData(id, x, y); 使用fillRect()绘制一个像素(应该没有混叠问题): ctx。fillStyle = " rgba(“+ r +”、“+ g +”、“+ b + +(一个/ 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倍。

其他更愚蠢的选择有:

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.

注意,我的测试没有尝试保存和恢复画布上下文fillStyle;这将降低fillRect()的性能。还要注意,我并没有从头开始,也没有为每个测试测试完全相同的像素集。