用Bresenham算法绘制椭圆

时间:2018-03-26 18:52:22

标签: algorithm math geometry drawing ellipse

您好

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

what i want to draw

为此,我使用增量算法和二阶逻辑。我是从另一种算法中做到的,它首先绘制了右上角,但我所做的并不起作用。

当第二个区域正在绘制时出现问题,我不知道它来自何处。

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

该算法是从具有二阶逻辑的增量算法中推导出来的。

这是我的代码,(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--;
    }
}

我希望你能帮助我,谢谢。

1 个答案:

答案 0 :(得分:1)

使用误差项e = ax ^ 2 + by ^ 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。所以每次迭代的实际误差是* 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]);
    }
  }
}

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