渲染缓慢
我正在使用 Bresenham's line algorithm实时渲染像素艺术线条。它一次渲染 1 个像素 ctx.rect(x,y,1,1)
,这是一个缓慢的操作。我不能使用像素缓冲区,这会大大减少渲染开销,因为我正在使用复合操作、alpha 和过滤器(其中一些会污染 Canvas )。
函数
function pixelArtLine(ctx, x1, y1, x2, y2) {
x1 = Math.round(x1);
y1 = Math.round(y1);
x2 = Math.round(x2);
y2 = Math.round(y2);
const dx = Math.abs(x2 - x1);
const sx = x1 < x2 ? 1 : -1;
const dy = -Math.abs(y2 - y1);
const sy = y1 < y2 ? 1 : -1;
var e2, er = dx + dy, end = false;
ctx.beginPath();
while (!end) {
ctx.rect(x1, y1, 1, 1);
if (x1 === x2 && y1 === y2) {
end = true;
} else {
e2 = 2 * er;
if (e2 > dy) {
er += dy;
x1 += sx;
}
if (e2 < dx) {
er += dx;
y1 += sy;
}
}
}
ctx.fill();
};
如何改进此功能?
最佳答案
既然你在做像素艺术,为什么不在像素级别上做呢:直接操作 ImageData。
您声明您的 Canvas 很可能已被污染,并且将设置过滤器和 gCO。这些都不重要。
使用第二个屏幕外 Canvas ,仅用于生成像素艺术效果图。
将其大小设置为您渲染的像素网格之一(即 originalCanvasSize/pixelSize)。
直接在屏幕外的 ImageData 上执行数学运算。
将 ImageDaat 放在屏幕外 Canvas 上
使用 gCO 设置像素艺术颜色。
使用 drawImage
在渲染的 Canvas 上拉回屏幕外 Canvas 没有图像平滑(imageSmoothingEnbaled = false
)。
您希望应用于路径绘图的过滤器和 gCO 也将应用于此最终 drawImage(offscreenCanvas)
我相信您将能够以更简洁的方式重写它,但这里是一个粗略的概念证明:
class PixelArtDrawer {
constructor(ctx, options = {}) {
if (!(ctx instanceof CanvasRenderingContext2D)) {
throw new TypeError('Invalid Argument 1, not a canvas 2d context');
}
this.cursor = {
x: 0,
y: 0
};
this.strokeStyle = '#000';
this.renderer = ctx;
this.ctx = document.createElement('canvas').getContext('2d');
this.setPixelSize((options && options.pixelSize) || 10);
}
setPixelSize(pixelSize) {
this.pixelSize = pixelSize;
const ctx = this.ctx;
const canvas = ctx.canvas;
const renderer = this.renderer.canvas;
canvas.width = (renderer.width / pixelSize) | 0;
canvas.height = (renderer.height / pixelSize) | 0;
ctx.globalCompositeOperation = 'source-in';
this.image = ctx.createImageData(canvas.width, canvas.height);
this.data = new Uint32Array(this.image.data.buffer);
}
beginPath() {
this.data.fill(0);
this.cursor.x = this.cursor.y = null;
}
stroke() {
const renderer = this.renderer
const currentSmoothing = renderer.imageSmoothingEnbaled;
const ctx = this.ctx;
ctx.putImageData(this.image, 0, 0);
// put the color
ctx.fillStyle = this.strokeStyle;
ctx.fillRect(0, 0, this.image.width, this.image.height);
renderer.imageSmoothingEnabled = false;
renderer.drawImage(ctx.canvas, 0, 0, renderer.canvas.width, renderer.canvas.height);
renderer.imageSmoothingEnabled = currentSmoothing;
}
moveTo(x, y) {
this.cursor.x = (x / this.pixelSize) | 0;
this.cursor.y = (y / this.pixelSize) | 0;
}
lineTo(x, y) {
if (this.cursor.x === null) {
this.moveTo(x, y);
return;
}
const data = this.data;
const width = this.image.width;
const height = this.image.height;
var x1 = this.cursor.x;
var y1 = this.cursor.y;
const x2 = (x / this.pixelSize) | 0;
const y2 = (y / this.pixelSize) | 0;
// from here it is OP's code
const dx = Math.abs(x2 - x1);
const sx = x1 < x2 ? 1 : -1;
const dy = -Math.abs(y2 - y1);
const sy = y1 < y2 ? 1 : -1;
var e2, er = dx + dy,
end = false;
var index;
while (!end) {
// this check would probably be better done out of the loop
if (x1 >= 0 && x1 <= width && y1 >= 0 && y1 <= height) {
// here we need to convert x, y coords to array index
index = ((y1 * width) + x1) | 0;
data[index] = 0xff000000;
}
if (x1 === x2 && y1 === y2) {
end = true;
} else {
e2 = 2 * er;
if (e2 > dy) {
er += dy;
x1 += sx;
}
if (e2 < dx) {
er += dx;
y1 += sy;
}
}
}
this.cursor.x = x2;
this.cursor.y = y2;
}
}
const ctx = renderer.getContext('2d');
const pixelArt = new PixelArtDrawer(ctx);
const points = [{
x: 0,
y: 0
}, {
x: 0,
y: 0
}];
draw();
renderer.onmousemove = function(e) {
const rect = this.getBoundingClientRect();
const lastPoint = points[points.length - 1];
lastPoint.x = e.clientX - rect.left;
lastPoint.y = e.clientY - rect.top;
};
renderer.onclick = e => {
const lastPoint = points[points.length - 1];
points.push({
x: lastPoint.x,
y: lastPoint.y
});
};
function draw() {
ctx.clearRect(0, 0, renderer.width, renderer.height);
pixelArt.beginPath();
points.forEach(drawLine);
pixelArt.stroke();
requestAnimationFrame(draw);
}
function drawLine(pt) {
pixelArt.lineTo(pt.x, pt.y);
}
color_picker.onchange = function() {
pixelArt.strokeStyle = this.value;
}
<input type="color" id="color_picker"><br>
<canvas id="renderer" width="500" height="500"></canvas>
关于javascript - 更快的 HTML Canvas Bresenham 线,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50625903/