【问题标题】:Precise subpixel line drawing algorithm (rasterization algorithm)精确的亚像素画线算法(光栅化算法)
【发布时间】:2014-09-01 00:43:39
【问题描述】:

我需要一种算法,它可以(有点)比Bresenham line drawing algorithm 慢,但必须更精确。我的意思是“精确”:每个触摸的像素都应该被打印出来。不多,但也不少!这意味着不能选择使用更粗的线或类似的线,因为会涉及太多像素。我也不需要像asked before 那样的图形框架或类似的东西,我需要算法!该应用程序实际上并不在“图形”中,而是在像素为“图块”的geography area 中。

对我来说主要的问题是我需要亚像素精度,这意味着一条线可以从 0.75/0.33 开始,而不仅仅是像整数值那样从 0/0 开始。在过去的几个小时里,我尝试创建一个可行的解决方案,但无法使其正常工作 - 边缘情况太多。

首先,我认为像 Wu 的算法这样的抗锯齿版本应该可以实现,但它打印的像素太多(尤其是起点和终点),在某些情况下它仍然会丢失一些像素,例如对于非常短的行。

然后我试图让 Bresenham 工作,我将第二个“if”替换为“else if”,正如 here 指出的那样,它更接近但仍然不存在。然后我尝试将 Bresenham 从整数精度移动到浮点精度,这导致了无限循环(因为 x,y 值跳过了结束条件if (y1 == y2 && x1 == x2))。

我可以使用naive line drawing 解决方案,但我应该使用哪个delta?例如。如果我使用 0.1,我仍然会丢失一些像素,而使用较小的值可能会花费太长时间(并且仍然会丢失像素)。

C/Java/... 中的工作解决方案将不胜感激。至少它应该适用于 octant 1,但完整的解决方案会更好。

更新:我想出了以下想法:使用朴素的线光栅化,您可以为每个点计算 4 个像素候选。然后检查这 4 个像素,如果线真的穿过它们。但我不确定线/框相交是否足够快。

【问题讨论】:

  • “每个触摸的像素都应该被打印”,即使 0.01 个或更少的像素与线相交?线的两端是什么形状(圆、凹、凸、平)?
  • 是的,如果存在数学交集,则应该包含它(当然我们可以假设常见的舍入错误)。线的末端应该是平的(没有绒毛或抗锯齿,只有“数学”端)
  • 颜色呢?线条颜色是常数还是应该像抗锯齿一样根据使用的像素区域进行插值?
  • 颜色不是必须的。这确实是 MBo 在他的回答(实际实现)中指出的同一个问题:空间细分,所以我只需要知道“射线”是否击中像素。
  • 嗯,我看到我的回答here 可能比那个更适合这个问题。我相信我的解决方案正是这个问题所要求的。

标签: algorithm math graphics raytracing bresenham


【解决方案1】:

如果您只需要恒定的颜色(不通过使用的像素区域进行插值),请使用 DDA

void line_DDA_subpixel(int x0,int y0,int x1,int y1,int col) // DDA subpixel -> thick
    {
    int kx,ky,c,i,xx,yy,dx,dy;
    x1-=x0; kx=0; if (x1>0) kx=+1; if (x1<0) { kx=-1; x1=-x1; } x1++;
    y1-=y0; ky=0; if (y1>0) ky=+1; if (y1<0) { ky=-1; y1=-y1; } y1++;
    if (x1>=y1)
     for (c=x1,i=0;i<x1;i++,x0+=kx)
        {
        pnt(x0,y0,col); // this is normal pixel the two below are subpixels 
        c-=y1; if (c<=0) { if (i!=x1-1) pnt(x0+kx,y0,col); c+=x1; y0+=ky; if (i!=x1-1) pnt(x0,y0,col); }
        }
    else
     for (c=y1,i=0;i<y1;i++,y0+=ky)
        {
        pnt(x0,y0,col); // this is normal pixel the two below are subpixels 
        c-=x1; if (c<=0) { if (i!=y1-1) pnt(x0,y0+ky,col); c+=y1; x0+=kx; if (i!=y1-1) pnt(x0,y0,col); }
        }
    }

地点:

void pnt(int x,int y,int col);

是用颜色 col 栅格化像素(x,y) 的例程源代码是 C++

我认为这是海峡前进,但无论如何

DDA 使用参数线方程 y=k*x+qx=ky+q 取决于差异(如果更大 xy 差异,则没有孔)。 kdy/dxdx/dy 并且整个除法减少为循环内的减法+加法(每个循环的最后一行)。这可以很容易地修改为任意数量的维度(我通常使用 7D 或更多)。在现代机器上,速度有时比 Bresenham 更好(取决于平台和使用情况)。

这就是它与简单的 DDA

相比的样子

[edit2]双坐标 //原来是[edit1]

好的,这是新代码:

void line_DDA_subpixel1(double x0,double y0,double x1,double y1,int col)    // DDA subpixel -> thick
    {
    int i,n,x,y,xx,yy;
    // prepare data n-pixels,x1,y1 is line dx,dy step per pixel
    x1-=x0; i=ceil(fabs(x1));
    y1-=y0; n=ceil(fabs(y1));
    if (n<i) n=i; if (!n) n=1;
    x1/=double(n);
    y1/=double(n); n++;
    // rasterize DDA line
    for (xx=x0,yy=y0,i=0;i<=n;i++,x0+=x1,y0+=y1)
        {
        // direct pixel
        pnt(x,y,col);
        // subpixels on change in both axises
        x=x0; y=y0;
        if ((i<n)&&(x!=xx)&&(y!=yy)) { pnt(xx,y,col); pnt(x,yy,col); }
        xx=x; yy=y;
        }
    }

这就是它的样子:

现在角度应该在 double 精度,但 pnt(x,y,col) 仍然是整数!!!

[edit3] 像素网格交叉

void DDAf_line_subpixel(float x0,float y0,float x1,float y1,int col)    // DDA subpixel -> thick
    {
    int i,n; float a,a0,a1,aa,b,d;
    // end-points
    pnt(x0,y0,col);
    pnt(x1,y1,col);
    // x-axis pixel cross
    a0=1; a1=0; n=0;
    if (x0<x1) { a0=ceil(x0); a1=floor(x1); d=(y1-y0)/(x1-x0); a=a0; b=y0+(a0-x0)*d; n=fabs(a1-a0); } else
    if (x0>x1) { a0=ceil(x1); a1=floor(x0); d=(y1-y0)/(x1-x0); a=a0; b=y1+(a0-x1)*d; n=fabs(a1-a0); }
    if (a0<=a1) for (aa=a,i=0;i<=n;i++,aa=a,a++,b+=d) { pnt(aa,b,col); pnt( a,b,col); }
    // y-axis pixel cross
    a0=1; a1=0; n=0;
    if (y0<y1) { a0=ceil(y0); a1=floor(y1); d=(x1-x0)/(y1-y0); a=a0; b=x0+(a0-y0)*d; n=fabs(a1-a0); } else
    if (y0>y1) { a0=ceil(y1); a1=floor(y0); d=(x1-x0)/(y1-y0); a=a0; b=x1+(a0-y1)*d; n=fabs(a1-a0); }
    if (a0<=a1) for (aa=a,i=0;i<=n;i++,aa=a,a++,b+=d) { pnt(b,aa,col); pnt(b, a,col); }
    }

终于有一些时间,所以我稍微调整了 DDA,但 id 导致了许多 ifs,所以我对光栅化进行了相当多的更改。现在计算所有像素网格交叉(交叉点),然后为每个正确的子像素添加。这是它的样子(没有错误的子像素):

对于每个xy 网格线是计算(a,b)step 在一个轴上1 像素的第一个交叉点,其余根据dy/dxdx/dy 在第二个。在此之后 for 循环填充子像素 ...

【讨论】:

  • 如果需要面积百分比,那么它可以从变量 c 的状态推导出来,但我最近使用它,因为我不需要它。
  • 对于 OP 正在寻找的内容,这是一个很好的特定情况算法,但是 AFAICT 这并不能解决行开始和结束坐标为非整数值的极有可能的情况。
  • 如果我将 int 参数替换为例如双倍?
  • @Karussell 是的,但是以经典方式制作 dy/dx 或 dx/dy 会更快。然后你应该通过 if ((x-floor(x))>0.0) ... 添加子像素点,对于 y
  • @Karussell 我会把它保留在整数上,如果需要的话,可以为每个端点添加这 2 个子点。
【解决方案2】:

如果你的线条很细并且像素是矩形(正方形):

然后考虑使用体素网格遍历算法,例如,参见 Woo 和 Amanatides 的文章“Fast Voxel Traversal Algorithm...”。

Practical implementation(在网格遍历部分)

回复评论:
X坐标变量的正确初始化(Y相同)

  DX = X2 - X1
  tDeltaX = GridCellWidth / DX
  tMaxX = tDeltaX * (1.0 - Frac(X1 / GridCellWidth)) 
  //Frac if fractional part of float, for example, Frac(1.3) = 0.3

我的回答中的示例here

【讨论】:

  • 谢谢!这看起来不错 - 我会调查的!我的像素是矩形的,但不是二次的,所以我认为这可以工作。您的图像表明起点或终点只能在中心 - 任意起点或终点也可能吗?
  • 您从包含任意起始位置的图块开始,并在包含任意结束位置的图块中结束。遍历沿着与起点和终点相交的线进行。所以,是的,任意的开始和结束都是可能的。
  • 是的,任意位置都是可能的。
  • 谢谢,看起来不错。我在这里实现了它:gist.github.com/karussell/df699108fd8fbe0d44e1 将在接下来的几天里继续为其他想法开放答案
  • 我在 python 中实现了您的 java 代码(github 链接)以进行原型设计。而且它不适用于所有情况(起点和终点的所有组合)。我的单元格以整数为中心,我的起点和终点是任意浮点值。我更改了以下内容并且它起作用了:if stepX &lt; 0: maxX = deltaX * (0.5 + (tmp - np.round(tmp))); else: maxX = deltaX * (0.5 - (tmp - np.round(tmp))); 使用 stepY 的 maxY 相同。在代码中添加; 作为行结束符
猜你喜欢
  • 1970-01-01
  • 2019-06-18
  • 1970-01-01
  • 1970-01-01
  • 2019-09-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-04
相关资源
最近更新 更多