Javascript 用于HTML画布的更快的Bresenham行 慢渲染
我使用实时渲染像素艺术线条。它一次渲染1个像素Javascript 用于HTML画布的更快的Bresenham行 慢渲染,javascript,canvas,html5-canvas,2d,Javascript,Canvas,Html5 Canvas,2d,我使用实时渲染像素艺术线条。它一次渲染1个像素ctx.rect(x,y,1,1),这是一个缓慢的操作。我不能使用像素缓冲区,这将大大减少渲染开销,因为我使用的是复合操作、alpha和过滤器(其中一些会污染画布) 功能 功能像素基线(ctx、x1、y1、x2、y2){ x1=数学四舍五入(x1); y1=数学四舍五入(y1); x2=数学四舍五入(x2); y2=数学四舍五入(y2); 常数dx=Math.abs(x2-x1); 常数sx=x10){ctx.rect(x1,y2,x2-x1,1)}
ctx.rect(x,y,1,1)
,这是一个缓慢的操作。我不能使用像素缓冲区,这将大大减少渲染开销,因为我使用的是复合操作、alpha和过滤器(其中一些会污染画布)
功能
功能像素基线(ctx、x1、y1、x2、y2){
x1=数学四舍五入(x1);
y1=数学四舍五入(y1);
x2=数学四舍五入(x2);
y2=数学四舍五入(y2);
常数dx=Math.abs(x2-x1);
常数sx=x1dy){
er+=dy;
x1+=sx;
}
if(e2
如何改进此功能?Fast Bresenham的HTML5画布系列。
解决方案
如果我减少路径调用的数量,渲染可以得到改进。例如减少对ctx.rect(x,y,1,1)的调用代码>
一个1像素长的矩形或20像素长的矩形之间的渲染时间差太小了,我无法测量它。因此,减少通话次数将带来显著的改善
查看从1,1到15,5的一行,需要10次调用ctx.rect
// shows 10 pixels render of line 1,1 to 15,5
// ###
// ###
// ###
// ###
// ###
但是,使用3像素宽的矩形,只需5次调用就可以渲染它
标准算法要求最大坐标长度加上一个路径调用。例如1,1到15,5是Math.max(15-1,5-1)+1==15
但是它可以在最小长度+1egMath.min(15-1,5-1)+1==5
新算法
使用与Bresenham线相同的误差方法,并以八分之一为单位,可以根据累积误差值计算到下一个y步(八分之一0)或x步(八分之一1)的距离。该距离给出了要绘制的ctx.rect
长度(以像素为单位)以及要添加到下一行错误中的量
水平线和垂直线在单个路径调用中呈现。45度的行需要最多的路径调用,但由于这是一种特殊情况,函数可以获得javascript性能优势
对于随机选择的行,它应该将draw调用的数量减少到42%
function BMFastPixelArtLine(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 error, len, rev, count = dx;
ctx.beginPath();
if (dx > dy) {
error = dx / 2;
rev = x1 > x2 ? 1 : 0;
if (dy > 1) {
error = 0;
count = dy - 1;
do {
len = error / dy + 2 | 0;
ctx.rect(x1 - len * rev, y1, len, 1);
x1 += len * sx;
y1 += sy;
error -= len * dy - dx;
} while (count--);
}
if (error > 0) {ctx.rect(x1, y2, x2 - x1, 1) }
} else if (dx < dy) {
error = dy / 2;
rev = y1 > y2 ? 1 : 0;
if (dx > 1) {
error = 0;
count --;
do {
len = error / dx + 2 | 0;
ctx.rect(x1 ,y1 - len * rev, 1, len);
y1 += len * sy;
x1 += sx;
error -= len * dx - dy;
} while (count--);
}
if (error > 0) { ctx.rect(x2, y1, 1, y2 - y1) }
} else {
do {
ctx.rect(x1, y1, 1, 1);
x1 += sx;
y1 += sy;
} while (count --);
}
ctx.fill();
}
函数BMFastPixelArtLine(ctx、x1、y1、x2、y2){
x1=数学四舍五入(x1);
y1=数学四舍五入(y1);
x2=数学四舍五入(x2);
y2=数学四舍五入(y2);
常数dx=Math.abs(x2-x1);
常数sx=x1dy){
误差=dx/2;
rev=x1>x2?1:0;
如果(dy>1){
误差=0;
计数=dy-1;
做{
len=误差/dy+2 | 0;
ctx.rect(x1-透镜*rev,y1,透镜,1);
x1+=len*sx;
y1+=sy;
误差-=len*dy-dx;
}而(数--),;
}
if(error>0){ctx.rect(x1,y2,x2-x1,1)}
}else if(dxy2?1:0;
如果(dx>1){
误差=0;
计数--;
做{
len=误差/dx+2 | 0;
ctx.rect(x1,y1-透镜*rev,1,len);
y1+=len*sy;
x1+=sx;
误差-=len*dx-dy;
}而(数--),;
}
if(error>0){ctx.rect(x2,y1,1,y2-y1)}
}否则{
做{
ctx.rect(x1,y1,1,1);
x1+=sx;
y1+=sy;
}而(数--),;
}
ctx.fill();
}
- 缺点:生成的函数稍长,与原始函数的像素不完全匹配,错误仍然会使像素保持在直线上
- 优点:随机均匀分布的线路平均性能提高55%。最坏的情况是(45度附近的线路(45度上的线路更快))太小以至于无法呼叫。最佳情况(接近或水平或垂直)70-80%+更快。还有一个额外的好处,因为该算法更适合渲染像素艺术多边形
既然你在做像素艺术,为什么不在像素级别上做呢:直接操作图像数据
您声明您的画布可能已被污染,并且将设置过滤器和gCO。这些都不重要
使用第二个屏幕外画布,仅生成像素艺术渲染。
将其大小设置为一个渲染像素网格(即originalCanvasSize/pixelSize)。
直接在屏幕外的图像数据上执行数学运算。
将ImageDaat放在屏幕外的画布上
使用gCO设置像素艺术颜色。
使用drawImage
在渲染画布上绘制屏幕外画布,但不进行图像平滑(imagesmoothingballed=false
)
您希望应用于路径图的过滤器和gCO也将应用于此最终drawImage(offscreenCanvas)
我相信您将能够以更简洁的方式重写它,但这里有一个粗略的概念证明:
class PixelArtDrawer{
构造函数(ctx,选项={}){
如果(!(CanvasRenderingContext2D的ctx实例)){
抛出新的TypeError('无效参数1,不是画布2d上下文');
}
this.cursor={
x:0,,
y:0
};
this.strokeStyle='000';
this.renderer=ctx;
this.ctx=document.createElement('canvas').getContext('2d');
此.setPixelSize((options&&options.pixelSize)| | 10);
}
设置像素大小(像素大小){
this.pixelSize=像素大小;
有限公司
function BMFastPixelArtLine(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 error, len, rev, count = dx;
ctx.beginPath();
if (dx > dy) {
error = dx / 2;
rev = x1 > x2 ? 1 : 0;
if (dy > 1) {
error = 0;
count = dy - 1;
do {
len = error / dy + 2 | 0;
ctx.rect(x1 - len * rev, y1, len, 1);
x1 += len * sx;
y1 += sy;
error -= len * dy - dx;
} while (count--);
}
if (error > 0) {ctx.rect(x1, y2, x2 - x1, 1) }
} else if (dx < dy) {
error = dy / 2;
rev = y1 > y2 ? 1 : 0;
if (dx > 1) {
error = 0;
count --;
do {
len = error / dx + 2 | 0;
ctx.rect(x1 ,y1 - len * rev, 1, len);
y1 += len * sy;
x1 += sx;
error -= len * dx - dy;
} while (count--);
}
if (error > 0) { ctx.rect(x2, y1, 1, y2 - y1) }
} else {
do {
ctx.rect(x1, y1, 1, 1);
x1 += sx;
y1 += sy;
} while (count --);
}
ctx.fill();
}