【问题标题】:Drawing ellipse with Bresenham's algorithm使用 Bresenham 算法绘制椭圆
【发布时间】:2018-03-26 18:52:22
【问题描述】:

您好,

我正在尝试使用 Bresenham 算法绘制一个与正交系统平行的椭圆。我想画椭圆的左上角(W,SW,S)四分之一,然后推导出其他的。

为此,我使用了具有二阶逻辑的增量算法。我是通过另一个算法先绘制 右上角的四分之一,但我正在做的事情不起作用。

在绘制第二个区域的时候出现问题,不知道是从哪里来的。

您可以看到我拥有的(黑色)和我期望的(绿色): (椭圆的中心 (xc, yc) 和右上角的按钮 (x2,y2) 在本例中为 ~(xc+30,yc+20)) (a 是 abs(x2-xc),b 是 abs(y2-yc)) 第一个参数是椭圆的中间(xc,yc),第二个是右上点建立的x和y半径。您可以看到椭圆走得太远(左侧和右侧 2 个点)。你可以看看另一个例子 (椭圆的中心 (xc, yc) 和右上角的按钮 (x2,y2) 在此示例中为 ~(xc+15,yc+18))

算法是从增量算法推导出来的。

这是我的代码,(a 是 abs(x2-xc),b 是 abs(y2-yc))

ellipse(int a, int b, int xc, int yc) {
    int a2 = a*a, b2 = b*b;
    int x = 0, y = b; //Starting point

    int incSW = b2*2 + a2*2;

    int deltaW = b2*(-2*x + 3); //deduced from incremental algorithm with the second-order logic
    int deltaS = a2*(-2*y + 3);
    int deltaSW = deltaW + deltaS;

    int d1 = b2 - a2*b + a2/4; //dp starting value in the first region
    int d2 = b2*(x - 0.5)*(x - 0.5) + a2*(y - 1)*(y - 1) - a2*b2; //dp starting value in the second region

    //First region
    while(a2*(y-0.5) >= b2*(-x-1)) {
        DrawPixel(g,-x+xc, -y+yc); // 1st case
        DrawPixel(g,-x+xc, y+yc); // 2nd case
        DrawPixel(g,x+xc, y+yc); // 3rd case
        DrawPixel(g,x+xc, -y+yc); // 4th case
        if(d1>0) {
            d1+=deltaSW;
            deltaW+=b2*2;
            deltaSW+=incSW;
            y--;
        }
        else {
            d1+=deltaW;
            deltaW+=2*b2;
            deltaSW+=2*b2;
        }
        x--;
    }

    deltaSW = b2*(2 - 2*x) + a2*(-2*y + 3);

    //Second region
    while(y>=0) {
        DrawPixel(g,-x+xc, -y+yc); // 1st case
        DrawPixel(g,-x+xc, y+yc); // 2nd case
        DrawPixel(g,x+xc, y+yc); // 3rd case
        DrawPixel(g,x+xc, -y+yc); // 4th case
        if(d2>0) {
            d2+=deltaS;
            deltaS+=a2*2;
            deltaSW+=a2*2;
        }
        else {
            d2+=deltaSW;
            deltaSW+=incSW;
            deltaS+=a2*2;
            x--;
        }
        y--;
    }
}

希望你能帮助我,谢谢。

【问题讨论】:

  • 您可能还应该在问题中添加示例图像参数的数值。
  • 好的,我刚刚做到了:)
  • OrsCrous,这不是 numeric 值!您是否希望读者手动计算图像上的像素?或者其他人应该如何获得ab 的值?
  • 我觉得我做得更好,谢谢
  • top-left (W,SW,S) 如果我用W 代表西方:S 代表什么?

标签: algorithm math geometry drawing ellipse


【解决方案1】:

使用误差项 e = a x^2 + b y^2 - r^2,很容易证明从 (x,y) 到 (x,y+1) 的步骤将误差更改为 2by + b ,到 (x+1,y+1) 的步长为 2ax + a + 2by + b,而到 (x+1,y) 的步长为 2ax + a。

从一点(-x0, 0)开始,从这三个中选择绝对误差最小的步长。前两种情况是您所说的“第一个区域”的规范。

第一次向右走一步,从 (x,y) 到 (x+1,y),产生的错误最小,你知道你在第二个区域。此时不再需要第一种情况。四分之一椭圆可以仅使用后两种情况完成。

请注意,此检查可避免您使用过的浮点运算。 Bresenham-ish 算法的重点是避免浮点数。

最后要注意的是,您不希望每次迭代都计算 2ax 或 2by。可以通过维护变量(例如 dx=2ax 和 dy=2by)并更新它们来避免乘法。从 x 到 x+1 的步长会使 dx 增加 2a(一个常数)。类似地,从 y 到 y+1 的步长会使 dy 增加 2b。

将所有这些放在一起,您会得到下面的(粗略)代码。

请注意,您可以通过根据原始误差项验证增量误差计算来检查它。如果 (x0,0) 是初始点,那么您知道 x0^2 = r^2。所以每次迭代的实际误差是 a * x^2 + b * y^2 - x0^2。这应该等于下面代码中的e,并且确实如此。

import static java.lang.Math.abs;
import java.util.Arrays;
import java.util.function.BiConsumer;

public class EllipseTracer {
  static char [] [] raster = new char[51][101]; 

  static void trace(int x, int y, int a, int b, BiConsumer<Integer, Integer> emitter) {
    emitter.accept(x, y);
    int e = 0;
    int dx = 2 * a * x;
    int dy = 2 * b * y;
    // First region: stepping north and northeast.
    while (x < 0) {
      int dxa = dx + a;
      int dyb = dy + b;
      int eUp = e + dyb;
      int eRt = e + dxa;
      int eDg = e + dxa + dyb;
      if (abs(eUp) < abs(eDg)) {
        emitter.accept(x, ++y);
        e = eUp;
        dy += 2 * b;
      } else {
        if (abs(eRt) < abs(eDg)) {
          // Step east is least error. Found second region.
          emitter.accept(++x, y);
          e = eRt;
          dx += 2 * a;
          break;
        }
        emitter.accept(++x, ++y);
        e = eDg;
        dy += 2 * b;
        dx += 2 * a;
      }
    }
    // Second region: step northeast and east.
    while (x < 0) {
      int dxa = dx + a;
      int dyb = dy + b;
      int eRt = e + dxa;
      int eDg = e + dxa + dyb;
      if (abs(eRt) < abs(eDg)) {
        emitter.accept(++x, y);
        e = eRt;
        dx += 2 * a;
      } else {
        emitter.accept(++x, ++y);
        e = eDg;
        dy += 2 * b;
        dx += 2 * a;
      }
    }
  }

  static void emit(int x, int y) {
    raster[y][x + 100] = '*';
  }

  public static void main(String [] args) {
    for (int i = 0; i < raster.length; ++i) {
      Arrays.fill(raster[i], ' ');
    }
    trace(-100, 0, 1, 4, EllipseTracer::emit);
    for (int i = 0; i < raster.length; ++i) {
      System.out.println(raster[i]);
    }
  }
}

你可以添加更多技巧来避免绝对值,但我会让你寻找那些。

【讨论】:

  • 所以如果我必须从 0,b 到 -a,0 执行此操作,我想我只需要添加一些 - 它应该可以工作。我会试试的
  • @OrsCrous 不,当然你可以在任何你想要的象限上做数学计算。我之所以选择这个,是因为它使所有斜率都为正数,因此不会出现带有负号的错误。
  • @OrsCrous 如果这个答案有帮助,如果你接受它会很好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多