Javascript 用于HTML画布的更快的Bresenham行 慢渲染

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)}

我使用实时渲染像素艺术线条。它一次渲染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
但是它可以在最小长度+1eg
Math.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();
}