简体   繁体   English

中点粗椭圆绘制算法

[英]Midpoint thick ellipse drawing algorithm

I'm really close to getting a thick ellipse algorithm working but I'm having a bit of trouble. 我真的很接近于使用厚椭圆算法,但我遇到了一些麻烦。 I've taken the midpoint thick circle algorithm from here , and the midpoint ellipse algorithm from here , and I'm trying to combine them together to get the midpoint thick ellipse algorithm. 我从这里开始采用中点粗圆算法,从这里采用中点椭圆算法,我试图将它们组合在一起得到中点粗椭圆算法。 I'm doing this because Googling "midpoint thick ellipse algorithm" didn't show what I'm looking for. 我这样做是因为谷歌搜索“中点厚椭圆算法”没有显示我正在寻找的东西。 The output from my attempt resembles a thick circle (images are at the bottom of the post). 我尝试的输出类似于一个粗圆(图像位于帖子的底部)。

This is the image code (just a placeholder): 这是图像代码(只是一个占位符):

struct Point {
  int x, y;
};

struct Image {};
using Color = int;

void setPixel(Image &, Color, Point) {
  // ...
}

void horiLine(Image &image, Color color, Point first, int last) {
  while (first.x <= last) {
    setPixel(image, color, first);
    first.x++;
  }
}

void vertLine(Image &image, Color color, Point first, int last) {
  while (first.y <= last) {
    setPixel(image, color, first);
    first.y++;
  }
}

Here's the midpoint thick circle algorithm: 这是中点粗圆算法:

void midpointCircleThick(
  Image &image,
  Color color,
  Point center,
  int innerRadius,
  int outerRadius
) {
  int innerX = innerRadius;
  int outerX = outerRadius;
  int posY = 0;
  int innerErr = 1 - innerRadius;
  int outerErr = 1 - outerRadius;

  while (outerX >= posY) {
    horiLine(image, color, {center.x + innerX, center.y + posY},   center.x + outerX);
    vertLine(image, color, {center.x + posY,   center.y + innerX}, center.y + outerX);
    horiLine(image, color, {center.x - outerX, center.y + posY},   center.x - innerX);
    vertLine(image, color, {center.x - posY,   center.y + innerX}, center.y + outerX);

    horiLine(image, color, {center.x - outerX, center.y - posY},   center.x - innerX);
    vertLine(image, color, {center.x - posY,   center.y - outerX}, center.y - innerX);
    horiLine(image, color, {center.x + innerX, center.y - posY},   center.x + outerX);
    vertLine(image, color, {center.x + posY,   center.y - outerX}, center.y - innerX);

    posY++;

    if (outerErr < 0) {
      outerErr += 2 * posY + 1;
    } else {
      outerX--;
      outerErr += 2 * (posY - outerX) + 1;
    }

    if (posY > innerRadius) {
      innerX = posY;
    } else {
      if (innerErr < 0) {
        innerErr += 2 * posY + 1;
      } else {
        innerX--;
        innerErr += 2 * (posY - innerX) + 1;
      }
    }
  }
}

Here's the midpoint ellipse algorithm: 这是中点椭圆算法:

void midpointEllipse(
  Image &image,
  Color color,
  Point center,
  Point radius
) {
  Point pos = {radius.x, 0};
  Point delta = {
    2 * radius.y * radius.y * pos.x,
    2 * radius.x * radius.x * pos.y
  };
  int err = radius.x * radius.x
          - radius.y * radius.y * radius.x
          + (radius.y * radius.y) / 4;

  while (delta.y < delta.x) {
    setPixel(image, color, {center.x + pos.x, center.y + pos.y});
    setPixel(image, color, {center.x + pos.x, center.y - pos.y});
    setPixel(image, color, {center.x - pos.x, center.y + pos.y});
    setPixel(image, color, {center.x - pos.x, center.y - pos.y});

    pos.y++;

    if (err < 0) {
      delta.y += 2 * radius.x * radius.x;
      err += delta.y + radius.x * radius.x;
    } else {
      pos.x--;
      delta.y += 2 * radius.x * radius.x;
      delta.x -= 2 * radius.y * radius.y;
      err += delta.y - delta.x + radius.x * radius.x;
    }
  }

  err = radius.x * radius.x * (pos.y * pos.y + pos.y)
      + radius.y * radius.y * (pos.x - 1) * (pos.x - 1)
      - radius.y * radius.y * radius.x * radius.x;

  while (pos.x >= 0) {
    setPixel(image, color, {center.x + pos.x, center.y + pos.y});
    setPixel(image, color, {center.x + pos.x, center.y - pos.y});
    setPixel(image, color, {center.x - pos.x, center.y + pos.y});
    setPixel(image, color, {center.x - pos.x, center.y - pos.y});

    pos.x--;

    if (err > 0) {
      delta.x -= 2 * radius.y * radius.y;
      err += radius.y * radius.y - delta.x;
    } else {
      pos.y++;
      delta.y += 2 * radius.x * radius.x;
      delta.x -= 2 * radius.y * radius.y;
      err += delta.y - delta.x + radius.y * radius.y;
    }
  }
}

I attempted to combine the two algorithms and this is what I have so far. 我试图将这两种算法结合起来,这就是我到目前为止所做的。 I left some ? 我留下了一些? where I'm not sure about the code. 我不确定代码的地方。 I am well aware of the messiness and duplication here. 我很清楚这里的混乱和重复。 I just want to get it working before I worry about what the code looks like. 我只是想在我担心代码的样子之前让它工作。

void midpointEllipseThick(
  Image &image,
  Color color,
  Point center,
  Point innerRadius,
  Point outerRadius
) {
  int innerX = innerRadius.x;
  int outerX = outerRadius.x;
  int posY = 0;
  Point innerDelta = {
    2 * innerRadius.y * innerRadius.y * innerX,
    2 * innerRadius.x * innerRadius.x * posY
  };
  Point outerDelta = {
    2 * outerRadius.y * outerRadius.y * outerX,
    2 * outerRadius.x * outerRadius.x * posY
  };
  int innerErr = innerRadius.x * innerRadius.x
               - innerRadius.y * innerRadius.y * innerRadius.x
               + (innerRadius.y * innerRadius.y) / 4;
  int outerErr = outerRadius.x * outerRadius.x
               - outerRadius.y * outerRadius.y * outerRadius.x
               + (outerRadius.y * outerRadius.y) / 4;

  while (outerDelta.y < outerDelta.x) { // ?
    horiLine(image, color, {center.x + innerX, center.y + posY},   center.x + outerX);
    vertLine(image, color, {center.x + posY,   center.y + innerX}, center.y + outerX);
    horiLine(image, color, {center.x - outerX, center.y + posY},   center.x - innerX);
    vertLine(image, color, {center.x - posY,   center.y + innerX}, center.y + outerX);

    horiLine(image, color, {center.x - outerX, center.y - posY},   center.x - innerX);
    vertLine(image, color, {center.x - posY,   center.y - outerX}, center.y - innerX);
    horiLine(image, color, {center.x + innerX, center.y - posY},   center.x + outerX);
    vertLine(image, color, {center.x + posY,   center.y - outerX}, center.y - innerX);

    posY++;

    if (outerErr < 0) {
      outerDelta.y += 2 * outerRadius.x * outerRadius.x;
      outerErr += outerDelta.y + outerRadius.x * outerRadius.x;
    } else {
      outerX--;
      outerDelta.y += 2 * outerRadius.x * outerRadius.x;
      outerDelta.x -= 2 * outerRadius.y * outerRadius.y;
      outerErr += outerDelta.y - outerDelta.x + outerRadius.x * outerRadius.x;
    }

    // ?
    // if (posY > innerRadius.y) {
    //   innerX = posY;
    // } else {
      if (innerErr < 0) {
        innerDelta.y += 2 * innerRadius.x * innerRadius.x;
        innerErr += innerDelta.y + innerRadius.x * innerRadius.x;
      } else {
        innerX--;
        innerDelta.y += 2 * innerRadius.x * innerRadius.x;
        innerDelta.x -= 2 * innerRadius.y * innerRadius.y;
        innerErr += innerDelta.y - innerDelta.x + innerRadius.x * innerRadius.x;
      }
    // }
  }

  innerErr = innerRadius.x * innerRadius.x * (posY * posY + posY)
           + innerRadius.y * innerRadius.y * (innerX - 1) * (innerX - 1)
           - innerRadius.y * innerRadius.y * innerRadius.x * innerRadius.x;
  outerErr = outerRadius.x * outerRadius.x * (posY * posY + posY)
           + outerRadius.y * outerRadius.y * (outerX - 1) * (outerX - 1)
           - outerRadius.y * outerRadius.y * outerRadius.x * outerRadius.x;

  while (outerX >= 0) { // ?
    horiLine(image, color, {center.x + innerX, center.y + posY},   center.x + outerX);
    vertLine(image, color, {center.x + posY,   center.y + innerX}, center.y + outerX);
    horiLine(image, color, {center.x - outerX, center.y + posY},   center.x - innerX);
    vertLine(image, color, {center.x - posY,   center.y + innerX}, center.y + outerX);

    horiLine(image, color, {center.x - outerX, center.y - posY},   center.x - innerX);
    vertLine(image, color, {center.x - posY,   center.y - outerX}, center.y - innerX);
    horiLine(image, color, {center.x + innerX, center.y - posY},   center.x + outerX);
    vertLine(image, color, {center.x + posY,   center.y - outerX}, center.y - innerX);

    outerX--; // ?
    innerX--;

    if (outerErr > 0) {
      outerDelta.x -= 2 * outerRadius.y * outerRadius.y;
      outerErr += outerRadius.y * outerRadius.y - outerDelta.x;
    } else {
      posY++;
      outerDelta.y += 2 * outerRadius.x * outerRadius.x;
      outerDelta.x -= 2 * outerRadius.y * outerRadius.y;
      outerErr += outerDelta.y - outerDelta.x + outerRadius.y * outerRadius.y;
    }

    // ?
    // if (innerX < -innerRadius.x) {

    // } else {
      if (outerErr > 0) {
        innerDelta.x -= 2 * innerRadius.y * innerRadius.y;
        innerErr += innerRadius.y * innerRadius.y - innerDelta.x;
      } else {
        posY++;
        innerDelta.y += 2 * innerRadius.x * innerRadius.x;
        innerDelta.x -= 2 * innerRadius.y * innerRadius.y;
        outerErr += innerDelta.y - innerDelta.x + innerRadius.y * innerRadius.y;
      }
    // }
  }
}

Here's a thick circle with innerRadius = 22; outerRadius = 24 这是一个innerRadius = 22; outerRadius = 24innerRadius = 22; outerRadius = 24 innerRadius = 22; outerRadius = 24 : innerRadius = 22; outerRadius = 24

厚圆圈

Here's an ellipse with radius = {32, 24} : 这是一个radius = {32, 24}的椭圆:

椭圆

Here's (what's supposed to be) a thick ellipse with innerRadius = {30, 22}; outerRadius = {32, 24} 这是(应该是什么)一个厚椭圆, innerRadius = {30, 22}; outerRadius = {32, 24} innerRadius = {30, 22}; outerRadius = {32, 24} : innerRadius = {30, 22}; outerRadius = {32, 24}

粗椭圆

I'm close but not quite there. 我很近但不太相似。 Could someone who knows more about this stuff than I do get me over the finish line? 有谁比这更了解这些东西会让我超过终点线吗?

I must admit that I strongly believe there is more symmetry in a circle than in an ellipse. 我必须承认,我坚信圆中的对称性比椭圆中的对称性更强。 Where a circle might be mirrored on any axis through center, for an ellipse, this is possible only with x and y axis in general. 如果圆可以在任何轴上穿过中心镜像,对于椭圆,这通常仅适用于x和y轴。 Hence, I believe that the midPointCircleThick() cannot be adapted for an ellipse. 因此,我认为midPointCircleThick()不能适应椭圆。

So, I started my implementation with the midpointEllipse() provided by the OP. 所以,我开始使用OP提供的midpointEllipse()实现我的实现。

These were my basic thoughts: 这些是我的基本想法:

  • IMHO, the Bresenham Line algorithm is the origin of the Midpoint Circle algorithm as well as the Midpoint Ellipse algorithm. 恕我直言, Bresenham线算法中点圆算法的起源以及中点椭圆算法。 This might be helpful to understand the error/delta magic which is used. 这可能有助于理解使用的错误/ delta魔法。 It's much simpler for a line but follows the same idea adapted to x²/a² + y²/b² = 1 ( the ellipse equation ). 它对于一条线路要简单得多,但遵循相同的思路,适用于x²/a²+y²/b²= 1( 椭圆方程 )。

  • With origin in center of ellipse, the midpointEllipse() renders all 4 quadrants simultaneously (exploiting the symmetry). 由于原点位于椭圆中心, midpointEllipse()同时渲染所有4个象限(利用对称性)。 Hence, only the curve in one quadrant has to be computed effectively. 因此,只需要有效地计算一个象限中的曲线。 The curve is in this area monotonic. 曲线在这个区域是单调的。

  • The midpointEllipse() has two regions: midpointEllipse()有两个区域:

    1. Starting at points on x axis, ∆y > ∆x until cross-even. 从x轴上的点开始,Δy>Δx直到交叉偶数。
    2. Afterwards, ∆x > ∆y. 然后,Δx>Δy。

My concept was to adapt the midpointEllipse() that way, that the code is "duplicated" to manage two points (one for inner border, one for outer) with identical y coordinates to draw horizontal lines (span lines). 我的概念是以这种方式调整midpointEllipse() ,代码被“复制”以管理两个点(一个用于内边界,一个用于外部),具有相同的y坐标以绘制水平线(跨度线)。

My first observation was that the new algorithm has to manage a final phase (for innerRadius.y < y ≤ outerRadius.y where only the points on outer border have to be considered. 我的第一个观察是新算法必须管理最终阶段(对于innerRadius.y < outerRadius.y ,其中只需要考虑外边界上的点。

Remembering that the original algorithm has two regions, there are now two regions for the outer border, two regions for the inner border, and the two phases mentioned above. 记住原始算法有两个区域,现在有两个区域用于外边界,两个区域用于内边界,以及上面提到的两个区域。 This allows a variety of combinations. 这允许各种组合。 (To get this managed was the main effort in my implementation.) (要实现这种管理是我实施的主要工作。)

The sample implementation (based on Qt to have a simple visualization): 示例实现(基于Qt进行简单的可视化):

#include <functional>

#include <QtWidgets>

class View: public QLabel {

  public:
    View(QWidget *pQParent = nullptr):
      QLabel(pQParent)
    { }
    virtual ~View() = default;

    View(const View&) = delete;
    View& operator=(const View&) = delete;

  protected:

    virtual void paintEvent(QPaintEvent *pQEvent) override;
};

struct Point { int x, y; };

using Color = QColor;

void midpointEllipse(
  Point center,
  Point radius,
  std::function<void(const Color&, const Point&)> setPixel)
{
  Point pos = { radius.x, 0 };
  Point delta = {
    2 * radius.y * radius.y * pos.x,
    2 * radius.x * radius.x * pos.y
  };
  int err = radius.x * radius.x
    - radius.y * radius.y * radius.x
    + (radius.y * radius.y) / 4;

  while (delta.y < delta.x) {
    setPixel(Qt::blue, { center.x + pos.x, center.y + pos.y });
    setPixel(Qt::blue, { center.x + pos.x, center.y - pos.y });
    setPixel(Qt::blue, { center.x - pos.x, center.y + pos.y });
    setPixel(Qt::blue, { center.x - pos.x, center.y - pos.y });

    pos.y++;

    if (err < 0) {
      delta.y += 2 * radius.x * radius.x;
      err += delta.y + radius.x * radius.x;
    } else {
      pos.x--;
      delta.y += 2 * radius.x * radius.x;
      delta.x -= 2 * radius.y * radius.y;
      err += delta.y - delta.x + radius.x * radius.x;
    }
  }

  err = radius.x * radius.x * (pos.y * pos.y + pos.y)
    + radius.y * radius.y * (pos.x - 1) * (pos.x - 1)
    - radius.y * radius.y * radius.x * radius.x;

  while (pos.x >= 0) {
    setPixel(Qt::yellow, { center.x + pos.x, center.y + pos.y });
    setPixel(Qt::yellow, { center.x + pos.x, center.y - pos.y });
    setPixel(Qt::yellow, { center.x - pos.x, center.y + pos.y });
    setPixel(Qt::yellow, { center.x - pos.x, center.y - pos.y });

    pos.x--;

    if (err > 0) {
      delta.x -= 2 * radius.y * radius.y;
      err += radius.y * radius.y - delta.x;
    } else {
      pos.y++;
      delta.y += 2 * radius.x * radius.x;
      delta.x -= 2 * radius.y * radius.y;
      err += delta.y - delta.x + radius.y * radius.y;
    }
  }
}

void midpointEllipseThick(
  Point center,
  Point innerRadius,
  Point outerRadius,
  std::function<void(const Color&, const Point&, int)> horiLine)
{
  /// @todo validate/correct innerRadius and outerRadius
  Point pos = { outerRadius.x, 0 };
  Point deltaOuter = {
    2 * outerRadius.y * outerRadius.y * pos.x,
    2 * outerRadius.x * outerRadius.x * pos.y
  };
  auto errOuterYX
    = [&]() {
      return outerRadius.x * outerRadius.x
        - outerRadius.y * outerRadius.y * outerRadius.x
        + (outerRadius.y * outerRadius.y) / 4;
    };
  auto errOuterXY
    = [&]() {
      return outerRadius.x * outerRadius.x * (pos.y * pos.y + pos.y)
        + outerRadius.y * outerRadius.y * (pos.x - 1) * (pos.x - 1)
        - outerRadius.y * outerRadius.y * outerRadius.x * outerRadius.x;
    };
  int errOuter = errOuterYX();
  int xInner = innerRadius.x;
  Point deltaInner = {
    2 * innerRadius.y * innerRadius.y * xInner,
    2 * innerRadius.x * innerRadius.x * pos.y
  };
  auto errInnerYX
    = [&]() {
      return innerRadius.x * innerRadius.x
        - innerRadius.y * innerRadius.y * innerRadius.x
        + (innerRadius.y * innerRadius.y) / 4;
    };
  auto errInnerXY
    = [&]() {
      return innerRadius.x * innerRadius.x * (pos.y * pos.y + pos.y)
        + innerRadius.y * innerRadius.y * (xInner - 1) * (xInner - 1)
        - innerRadius.y * innerRadius.y * innerRadius.x * innerRadius.x;
    };
  int errInner = errInnerYX();
  // helpers (to reduce code duplication)
  auto stepOuterYX
    = [&]() {
      ++pos.y;
      if (errOuter < 0) {
        deltaOuter.y += 2 * outerRadius.x * outerRadius.x;
        errOuter += deltaOuter.y + outerRadius.x * outerRadius.x;
      } else {
        --pos.x;
        deltaOuter.y += 2 * outerRadius.x * outerRadius.x;
        deltaOuter.x -= 2 * outerRadius.y * outerRadius.y;
        errOuter += deltaOuter.y - deltaOuter.x + outerRadius.x * outerRadius.x;
      }
    };
  auto stepOuterXY
    = [&]() {
      while (--pos.x > 0) {
        if (errOuter > 0) {
          deltaOuter.x -= 2 * outerRadius.y * outerRadius.y;
          errOuter += outerRadius.y * outerRadius.y - deltaOuter.x;
        } else {
          ++pos.y;
          deltaOuter.y += 2 * outerRadius.x * outerRadius.x;
          deltaOuter.x -= 2 * outerRadius.y * outerRadius.y;
          errOuter += deltaOuter.y - deltaOuter.x + outerRadius.y * outerRadius.y;
          break;
        }
      }
    };
  auto stepInnerYX
    = [&]() {
      if (errInner < 0) {
        deltaInner.y += 2 * innerRadius.x * innerRadius.x;
        errInner += deltaInner.y + innerRadius.x * innerRadius.x;
      } else {
        --xInner;
        deltaInner.y += 2 * innerRadius.x * innerRadius.x;
        deltaInner.x -= 2 * innerRadius.y * innerRadius.y;
        errInner += deltaInner.y - deltaInner.x + innerRadius.x * innerRadius.x;
      }
    };
  auto stepInnerXY
    = [&]() {
      while (--xInner >= 0) {
        if (errInner > 0) {
          deltaInner.x -= 2 * innerRadius.y * innerRadius.y;
          errInner += innerRadius.y * innerRadius.y - deltaInner.x;
        } else {
          deltaInner.y += 2 * innerRadius.x * innerRadius.x;
          deltaInner.x -= 2 * innerRadius.y * innerRadius.y;
          errInner += deltaInner.y - deltaInner.x + innerRadius.y * innerRadius.y;
          break;
        }
      }
    };
  // 1st phase
  while (deltaOuter.y < deltaOuter.x && deltaInner.y < deltaInner.x) {
    horiLine(Qt::blue, { center.x - pos.x, center.y + pos.y }, center.x - xInner);
    horiLine(Qt::blue, { center.x + pos.x, center.y + pos.y }, center.x + xInner);
    horiLine(Qt::blue, { center.x - pos.x, center.y - pos.y }, center.x - xInner);
    horiLine(Qt::blue, { center.x + pos.x, center.y - pos.y }, center.x + xInner);
    stepOuterYX();
    stepInnerYX();
  }

  // 2nd phase
  if (deltaOuter.y < deltaOuter.x) { // inner flipped
    //errOuter = errOuterYX();
    errInner = errInnerXY();
    while (deltaOuter.y < deltaOuter.x && xInner >= 0) {
      horiLine(Qt::green, { center.x - pos.x, center.y + pos.y }, center.x - xInner);
      horiLine(Qt::green, { center.x + pos.x, center.y + pos.y }, center.x + xInner);
      horiLine(Qt::green, { center.x - pos.x, center.y - pos.y }, center.x - xInner);
      horiLine(Qt::green, { center.x + pos.x, center.y - pos.y }, center.x + xInner);
      stepOuterYX();
      stepInnerXY();
    }
    //errOuter = errOuterYX();
    while (deltaOuter.y < deltaOuter.x) {
      horiLine(Qt::red, { center.x - pos.x, center.y + pos.y }, center.x + pos.x);
      horiLine(Qt::red, { center.x - pos.x, center.y - pos.y }, center.x + pos.x);
      stepOuterYX();
    }
  } else { // outer flipped
    errOuter = errOuterXY();
    //errInner = errInnerYX();
    while (deltaInner.y < deltaInner.x) {
      horiLine(Qt::cyan, { center.x - pos.x, center.y + pos.y }, center.x - xInner);
      horiLine(Qt::cyan, { center.x + pos.x, center.y + pos.y }, center.x + xInner);
      horiLine(Qt::cyan, { center.x - pos.x, center.y - pos.y }, center.x - xInner);
      horiLine(Qt::cyan, { center.x + pos.x, center.y - pos.y }, center.x + xInner);
      stepOuterXY();
      stepInnerYX();
    }
    //errOuter = errOuterXY();
  }
  // 3rd phase
  errOuter = errOuterXY();
  errInner = errInnerXY();
  while (xInner >= 0) {
    horiLine(Qt::yellow, { center.x - pos.x, center.y + pos.y }, center.x - xInner);
    horiLine(Qt::yellow, { center.x + pos.x, center.y + pos.y }, center.x + xInner);
    horiLine(Qt::yellow, { center.x - pos.x, center.y - pos.y }, center.x - xInner);
    horiLine(Qt::yellow, { center.x + pos.x, center.y - pos.y }, center.x + xInner);
    stepOuterXY();
    stepInnerXY();
  }
  // 4th phase
  //errOuter = errOuterXY();
  while (pos.x >= 0) {
    horiLine(Qt::magenta, { center.x - pos.x, center.y + pos.y }, center.x + pos.x);
    horiLine(Qt::magenta, { center.x - pos.x, center.y - pos.y }, center.x + pos.x);
    stepOuterXY();
  }
}

void View::paintEvent(QPaintEvent*)
{
  QPainter qPainter(this);
#if 0 // warm up
  auto setPixel
    = [&](const Color &color, const Point &point)
    {
      qPainter.setPen(color);
      qPainter.drawPoint(point.x, point.y);
    };
  Point center = { 0.5 * width(), 0.5 * height() };
  midpointEllipse(center, center, setPixel);
#else // my attempt to adapt it to thick ellipses
  auto horiLine
    = [&](const Color &color, const Point &pos0, int x1)
    {
      qPainter.setPen(color);
      qPainter.drawLine(pos0.x, pos0.y, x1, pos0.y);
    };
  Point center = { 0.5 * width(), 0.5 * height() };
  Point innerRadius = { 0.5 * center.x, 0.5 * center.y };
  Point outerRadius = { 0.9 * center.x, 0.9 * center.y };
  midpointEllipseThick(center, innerRadius, outerRadius, horiLine);
#endif // 0
}

int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  // setup UI
  View qWin;
  qWin.setWindowTitle(QString::fromUtf8("Draw Thick Ellipse"));
  qWin.resize(320, 240);
  qWin.show();
  // runtime loop
  return app.exec();
}

Compiled an tested in VS2017 (Qt 5.11.2): 在VS2017(Qt 5.11.2)中编译了一个测试:

testQThickEllipse的快照 testQThickEllipse的快照(宽度/高度不同) testQThickEllipse的快照(还有另一个宽度/高度)

I used colors to visualize the different combinations of regions and phases. 我使用颜色来显示区域和阶段的不同组合。 This is intended to simply illustrate which part of code was responsible to render which part of ellipse. 这是为了简单地说明代码的哪一部分负责渲染椭圆的哪一部分。


I was a bit uncertain about the else case in // 2nd phase . 我对// 2nd phaseelse情况有点不确定。 I tested with 我测试过

  Point center = { 0.5 * width(), 0.5 * height() };
  Point innerRadius = { 0.3 * center.x, 0.8 * center.y };
  Point outerRadius = { 0.9 * center.x, 0.9 * center.y };
  midpointEllipseThick(center, innerRadius, outerRadius, horiLine);

and got this: 得到了这个:

testQThickEllipse的快照(带有不同的参数)

Now, the // 1st phase stops due to failing deltaOuter.y < deltaOuter.x (and cyan areas appear). 现在,由于deltaOuter.y < deltaOuter.x失败(并出现青色区域), // 1st phase停止。


OP complained about poor handling of edge cases like eg innerRadius = outerRadius; OP抱怨边缘情况的处理不当,例如innerRadius = outerRadius; . I checked it with the following test set: 我用以下测试集检查了它:

  Point center = { 0.5 * width(), 0.5 * height() };
  // test edge cases
  { Point outerRadius = { 0.9 * center.x, 0.9 * center.y };
    Point innerRadius = { outerRadius.x, outerRadius.y };
    Old::midpointEllipseThick(center, innerRadius, outerRadius, horiLine);
  }
  { Point outerRadius = { 0.8 * center.x, 0.8 * center.y };
    Point innerRadius = { outerRadius.x - 1, outerRadius.y };
    Old::midpointEllipseThick(center, innerRadius, outerRadius, horiLine);
  }
  { Point outerRadius = { 0.7 * center.x, 0.7 * center.y };
    Point innerRadius = { outerRadius.x, outerRadius.y - 1 };
    Old::midpointEllipseThick(center, innerRadius, outerRadius, horiLine);
  }
  { Point outerRadius = { 0.6 * center.x, 0.6 * center.y };
    Point innerRadius = { outerRadius.x - 1, outerRadius.y - 1 };
    Old::midpointEllipseThick(center, innerRadius, outerRadius, horiLine);
  }
  { Point outerRadius = { 0.5 * center.x, 0.5 * center.y };
    Point innerRadius = { outerRadius.x - 2, outerRadius.y - 2 };
    Old::midpointEllipseThick(center, innerRadius, outerRadius, horiLine);
  }

changed Qt::yellow to Qt::darkgray (for a better contrast) and got this: Qt::yellow更改为Qt::darkgray (为了更好的对比度)并得到了这个:

testQThickEllipse的快照(边缘测试)

It becomes obvious that gaps appear when ∆x y →y+1 > x Outer - x Inner . 很明显,当Δxy →y + 1 > x Outer - x Inner时,出现间隙。

To fix this issue, the ∆x y →y+1 has to be considered as well for the generation of span lines. 为了解决这个问题,必须考虑Δxy →y + 1以产生跨度线。 To achieve this, I modified the iterations for ∆x ≥ ∆y (in the bottom part of function): 为了实现这一点,我修改了Δx≥Δy的迭代(在函数的底部):

void midpointEllipseThick(
  Point center,
  Point innerRadius,
  Point outerRadius,
  std::function<void(const Color&, const Point&, int)> horiLine)
{
  /// @todo validate/correct innerRadius and outerRadius
  Point pos = { outerRadius.x, 0 };
  Point deltaOuter = {
    2 * outerRadius.y * outerRadius.y * pos.x,
    2 * outerRadius.x * outerRadius.x * pos.y
  };
  auto errOuterYX
    = [&]() {
      return outerRadius.x * outerRadius.x
        - outerRadius.y * outerRadius.y * outerRadius.x
        + (outerRadius.y * outerRadius.y) / 4;
    };
  auto errOuterXY
    = [&]() {
      return outerRadius.x * outerRadius.x * (pos.y * pos.y + pos.y)
        + outerRadius.y * outerRadius.y * (pos.x - 1) * (pos.x - 1)
        - outerRadius.y * outerRadius.y * outerRadius.x * outerRadius.x;
    };
  int errOuter;
  int xInner = innerRadius.x;
  Point deltaInner = {
    2 * innerRadius.y * innerRadius.y * xInner,
    2 * innerRadius.x * innerRadius.x * pos.y
  };
  auto errInnerYX
    = [&]() {
      return innerRadius.x * innerRadius.x
        - innerRadius.y * innerRadius.y * innerRadius.x
        + (innerRadius.y * innerRadius.y) / 4;
    };
  auto errInnerXY
    = [&]() {
      return innerRadius.x * innerRadius.x * (pos.y * pos.y + pos.y)
        + innerRadius.y * innerRadius.y * (xInner - 1) * (xInner - 1)
        - innerRadius.y * innerRadius.y * innerRadius.x * innerRadius.x;
    };
  int errInner;
  // helpers (to reduce code duplication)
  auto stepOuterYX
    = [&]() {
      ++pos.y;
      if (errOuter < 0) {
        deltaOuter.y += 2 * outerRadius.x * outerRadius.x;
        errOuter += deltaOuter.y + outerRadius.x * outerRadius.x;
      } else {
        --pos.x;
        deltaOuter.y += 2 * outerRadius.x * outerRadius.x;
        deltaOuter.x -= 2 * outerRadius.y * outerRadius.y;
        errOuter += deltaOuter.y - deltaOuter.x + outerRadius.x * outerRadius.x;
      }
    };
  auto stepInnerYX
    = [&]() {
      if (errInner < 0) {
        deltaInner.y += 2 * innerRadius.x * innerRadius.x;
        errInner += deltaInner.y + innerRadius.x * innerRadius.x;
      } else {
        --xInner;
        deltaInner.y += 2 * innerRadius.x * innerRadius.x;
        deltaInner.x -= 2 * innerRadius.y * innerRadius.y;
        errInner += deltaInner.y - deltaInner.x + innerRadius.x * innerRadius.x;
      }
    };
  auto stepOuterXY
    = [&]() {
      while (--pos.x >= 0) {
        if (errOuter > 0) {
          deltaOuter.x -= 2 * outerRadius.y * outerRadius.y;
          errOuter += outerRadius.y * outerRadius.y - deltaOuter.x;
        } else {
          ++pos.y;
          deltaOuter.y += 2 * outerRadius.x * outerRadius.x;
          deltaOuter.x -= 2 * outerRadius.y * outerRadius.y;
          errOuter += deltaOuter.y - deltaOuter.x + outerRadius.y * outerRadius.y;
          break;
        }
      }
    };
  auto stepInnerXY
    = [&]() {
      while (--xInner >= 0) {
        if (errInner > 0) {
          deltaInner.x -= 2 * innerRadius.y * innerRadius.y;
          errInner += innerRadius.y * innerRadius.y - deltaInner.x;
        } else {
          deltaInner.y += 2 * innerRadius.x * innerRadius.x;
          deltaInner.x -= 2 * innerRadius.y * innerRadius.y;
          errInner += deltaInner.y - deltaInner.x + innerRadius.y * innerRadius.y;
          break;
        }
      }
    };
  auto min
    = [](int x1, int x2, int x3) {
      return std::min(std::min(x1, x2), x3);
    };
  // 1st phase
  errOuter = errOuterYX(); // init error for delta y < delta x
  errInner = errInnerYX(); // init error for delta y < delta x
  while (deltaOuter.y < deltaOuter.x && deltaInner.y < deltaInner.x) {
    horiLine(Qt::blue, { center.x - pos.x, center.y + pos.y }, center.x - xInner);
    horiLine(Qt::blue, { center.x + pos.x, center.y + pos.y }, center.x + xInner);
    horiLine(Qt::blue, { center.x - pos.x, center.y - pos.y }, center.x - xInner);
    horiLine(Qt::blue, { center.x + pos.x, center.y - pos.y }, center.x + xInner);
    stepOuterYX();
    stepInnerYX();
  }

  // 2nd phase
  if (deltaOuter.y < deltaOuter.x) { // inner flipped
    //errOuter = errOuterYX(); // still delta y < delta x
    errInner = errInnerXY(); // init error for delta x < delta y
    while (deltaOuter.y < deltaOuter.x && xInner >= 0) {
      horiLine(Qt::green, { center.x - pos.x, center.y + pos.y }, center.x - xInner);
      horiLine(Qt::green, { center.x + pos.x, center.y + pos.y }, center.x + xInner);
      horiLine(Qt::green, { center.x - pos.x, center.y - pos.y }, center.x - xInner);
      horiLine(Qt::green, { center.x + pos.x, center.y - pos.y }, center.x + xInner);
      stepOuterYX();
      stepInnerXY();
    }
    //errOuter = errOuterYX(); // still delta y < delta x
    while (deltaOuter.y < deltaOuter.x) {
      horiLine(Qt::red, { center.x - pos.x, center.y + pos.y }, center.x + pos.x);
      horiLine(Qt::red, { center.x - pos.x, center.y - pos.y }, center.x + pos.x);
      stepOuterYX();
    }
  } else { // outer flipped
    errOuter = errOuterXY(); // init error for delta x < delta y
    //errInner = errInnerYX(); // still delta y < delta x
    while (deltaInner.y < deltaInner.x) {
      Point pos_ = pos;
      stepOuterXY();
      stepInnerYX();
      int xInner_ = std::min(pos.x, xInner);
      horiLine(Qt::cyan, { center.x - pos_.x, center.y + pos_.y }, center.x - xInner_);
      horiLine(Qt::cyan, { center.x + pos_.x, center.y + pos_.y }, center.x + xInner_);
      horiLine(Qt::cyan, { center.x - pos_.x, center.y - pos_.y }, center.x - xInner_);
      horiLine(Qt::cyan, { center.x + pos_.x, center.y - pos_.y }, center.x + xInner_);
    }
  }
  // 3rd phase
  errOuter = errOuterXY(); // init error for delta x < delta y
  errInner = errInnerXY(); // init error for delta x < delta y
  while (xInner >= 0) {
    Point pos_ = pos;
    stepOuterXY();
    int xInner_ = std::min(pos.x, xInner);
    horiLine(Qt::darkGray, { center.x - pos_.x, center.y + pos_.y }, center.x - xInner_);
    horiLine(Qt::darkGray, { center.x + pos_.x, center.y + pos_.y }, center.x + xInner_);
    horiLine(Qt::darkGray, { center.x - pos_.x, center.y - pos_.y }, center.x - xInner_);
    horiLine(Qt::darkGray, { center.x + pos_.x, center.y - pos_.y }, center.x + xInner_);
    stepInnerXY();
  }
  // 4th phase
  //errOuter = errOuterXY(); // still delta x < delta y
  while (pos.x >= 0) {
    horiLine(Qt::magenta, { center.x - pos.x, center.y + pos.y }, center.x + pos.x + 1);
    horiLine(Qt::magenta, { center.x - pos.x, center.y - pos.y }, center.x + pos.x + 1);
    stepOuterXY();
  }
}

The result looks not that bad: 结果看起来并不坏:

testQThickEllipse的快照(固定用于边缘测试)

The gaps are removed. 差距被删除。

I realized that there is still the other complained issue about off-by-one error: 我意识到还有另一个抱怨的问题是关于一个错误:

The thickness at the top and bottom part of the ellipse seems to be one pixel too small. 椭圆顶部和底部的厚度似乎是一个像素太小。

Hmmm… That's a question of definition. 嗯......这是一个定义问题。 Whenever a range has to be given, there has to be said whether start and end are (each) inclusive or exclusive. 每当必须给出一个范围时,必须说明开始和结束是(包括)还是排除。 (Compare eg with iterator ranges in standard containers – start → inclusive, end → exclusive.) (例如,与标准容器中的迭代器范围进行比较 - 开始→包含,结束→排除。)

The Qt doc. Qt doc。 dedicates a whole extra chapter to this topic Coordinate System . 在这个主题坐标系中专门用了一章。

What I have to admit: My current algorithm handles this different for horizontal and vertical direction which I would consider as “ugliness”. 我必须承认:我目前的算法处理水平和垂直方向的不同,我认为这是“丑陋”。 IMHO, the easiest fix is to make it consistent horizontally and vertically. 恕我直言,最简单的解决方法是使其在水平和垂直方向上保持一致。 Afterwards the doc. 之后的文件。 might be adjusted respectively. 可能会分别调整。

Employee: “Boss! 员工:“老板! Our recently produced buckets have a hole and lose water.” 我们最近生产的水桶有一个洞,失水。“
Boss: “Good to know. 老板:“很高兴知道。 We should mention that in the manual.” 我们应该在手册中提到。“

Thus, I fixed the horizontal border size by tweaking the horiLine helper lambda: 因此,我通过调整horiLine辅助lambda来修复水平边框大小:

  auto horiLine
    = [&](const Color &color, const Point &pos0, int x1)
    {
      qPainter.setPen(color);
      if (x1 != pos0.x) x1 += x1 < pos0.x ? +1 : -1;
      qPainter.drawLine(pos0.x, pos0.y, x1, pos0.y);
    };

Now, I consider the result, at least, as consistent (if not satisfying): 现在,我认为结果至少是一致的(如果不满意):

testQThickEllipse的快照(固定为边框宽度)

The innerRadius appears now as exclusive. innerRadius现在显示为独占。 If this is not intended, a resp. 如果不是这样的话,请分别说明。 pre-adjustment of the parameters at the begin of the midpointEllipseThick() could be applied. 可以应用在midpointEllipseThick()开始时的参数的预调整。

The problem you are facing is that the outlines of a thick ellipse of constant thickness are not ellipses, they are higher degree curves ! 您面临的问题是厚度恒定的粗椭圆的轮廓不是椭圆,它们是更高度的曲线! Filling between two ellipses can only give approximations. 在两个椭圆之间填充只能给出近似值。

On the picture, the red curve corresponds to a constant thickness. 在图片上,红色曲线对应于恒定的厚度。

在此输入图像描述

A correct solution is to draw with a thick pen, ie sweep a disk of the desired radius by letting its center follow an ellipse, using the standard algorithm. 正确的解决方案是使用粗笔绘制,即使用标准算法通过使其中心遵循椭圆来扫描所需半径的圆盘。

As such, this is an inefficient procedure, as the successive disks overlap and the pixels will be drawn several times. 因此,这是一个低效的过程,因为连续的磁盘重叠并且像素将被绘制几次。 A solution is to consider the new pixels that are covered by the disk for the eight directions of displacement. 一种解决方案是考虑磁盘覆盖的新像素,用于八个位移方向。 These sets of pixels must be computed and tabulated in advance, for the given radius. 对于给定的半径,必须预先计算这些像素组并将其制成表格。

To establish the tables, draw a disk and erase it with a disk shifted by one pixel in one of the eight cardinal directions; 要建立表格,请绘制一个磁盘并用一个在八个基本方向之一上移动一个像素的磁盘擦除它; repeat for all directions. 所有方向重复。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM