中点粗椭圆绘制算法

Posted

技术标签:

【中文标题】中点粗椭圆绘制算法【英文标题】:Midpoint thick ellipse drawing algorithm 【发布时间】:2019-05-04 07:10:58 【问题描述】:

我真的很接近让粗椭圆算法工作,但我遇到了一些麻烦。我采用了here 的中点粗圆算法和here 的中点椭圆算法,我正在尝试将它们组合在一起以获得中点粗椭圆算法。我这样做是因为谷歌搜索“中点粗椭圆算法”没有显示我在寻找什么。我尝试的输出类似于一个粗圆圈(图片位于帖子底部)。

这是图片代码(只是一个占位符):

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++;
  

这是中点粗圆算法:

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;
      
    
  

这是中点椭圆算法:

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;
    
  

我尝试将这两种算法结合起来,这就是我目前所拥有的。我留下了一些 ? 我不确定代码的地方。我很清楚这里的混乱和重复。我只想在担心代码长什么样之前让它工作。

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;
      
    // 
  

这是一个带有innerRadius = 22; outerRadius = 24的粗圆圈:

这是一个带有radius = 32, 24的椭圆:

这是(应该是)一个带有 innerRadius = 30, 22; outerRadius = 32, 24 的粗椭圆:

我很接近但不完全在那里。比我更了解这些东西的人能否让我越过终点线?

【问题讨论】:

@Scheff 实际上我也在使用 Qt 进行测试!您所要做的就是为ImageColor 创建类型别名,然后实现setPixel 来调用QImage::setPixel 如何画一个椭圆,然后用类似但稍小的椭圆来创建厚度? @גלעדברקן 那可能会留下漏洞。而且它不如绘制水平线和垂直线那么有效。 【参考方案1】:

我必须承认,我坚信圆形比椭圆更对称。如果一个圆可以通过中心在任何轴上镜像,对于一个椭圆,这通常只适用于 x 和 y 轴。因此,我认为midPointCircleThick() 不能适用于椭圆。

所以,我从 OP 提供的 midpointEllipse() 开始实施。

这些是我的基本想法:

恕我直言,Bresenham Line algorithm 是 Midpoint Circle algorithm 以及中点椭圆算法的起源。这可能有助于理解所使用的错误/增量魔法。一条线要简单得多,但遵循适用于 x²/a² + y²/b² = 1 (the ellipse equation) 的相同想法。

原点位于椭圆中心,midpointEllipse() 同时渲染所有 4 个象限(利用对称性)。因此,只需有效计算一个象限中的曲线。该区域的曲线是单调的。

midpointEllipse() 有两个区域:

    从 x 轴上的点开始,Δy > Δx 直到交叉偶数。 之后,Δx > Δy。

我的概念是采用这种方式调整midpointEllipse(),即“复制”代码以管理具有相同 y 坐标的两个点(一个用于内边框,一个用于外边框)以绘制水平线(跨线)。

我的第一个观察结果是新算法必须管理最后一个阶段(对于 innerRadius.y outerRadius.y,其中只需要考虑外边界上的点。

记住原始算法有两个区域,现在有两个区域用于外边界,两个区域用于内边界,以及上面提到的两个阶段。这允许多种组合。 (管理这个是我实现的主要工作。)

示例实现(基于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();

在 VS2017 (Qt 5.11.2) 中编译了一个测试:

我使用颜色来可视化区域和阶段的不同组合。这是为了简单说明哪部分代码负责渲染椭圆的哪一部分。


我对@9​​87654348@ 中的else 案例有点不确定。我测试了

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

得到了这个:

现在,// 1st phasedeltaOuter.y &lt; deltaOuter.x 失败而停止(并出现青色区域)。


OP 抱怨对边缘情况的处理不当,例如innerRadius = outerRadius;。我使用以下测试集对其进行了检查:

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

Qt::yellow 更改为Qt::darkgray(以获得更好的对比度)并得到这个:

当 ∆xy →y+1 > xOuter - xInner 时,差距就很明显了。

要解决此问题,还必须考虑 ∆xy →y+1 来生成跨度线。为了实现这一点,我修改了 ∆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();
  

结果看起来还不错:

间隙被消除。

我意识到还有另一个关于非一错误的抱怨问题:

椭圆顶部和底部的厚度似乎太小了一个像素。

嗯……这是一个定义问题。无论何时必须给出一个范围,都必须说明 start 和 end 是(每个)包含还是不包含。 (例如,与标准容器中的迭代器范围进行比较——开始 → 包含,结束 → 排除。)

Qt 文档。为这个主题专门写了一整章Coordinate System。

我必须承认:我当前的算法处理水平和垂直方向的不同,我认为这是“丑陋”。恕我直言,最简单的解决方法是使其水平和垂直一致。之后是医生。可能会分别调整。

员工:“老板!我们最近生产的水桶有一个洞,会失水。” 老板:“很高兴知道。我们应该在手册中提到这一点。”

因此,我通过调整 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);
    ;

现在,我认为结果至少是一致的(如果不令人满意的话):

innerRadius 现在显示为独占。如果这不是故意的,请分别。可以应用midpointEllipseThick()开头的参数预调整。

【讨论】:

这看起来很有希望,但是在我的时区已经很晚了,所以我明天必须尝试一下。 @Kerndog73 哦,澳大利亚。我明白了... ;-) 这看起来很不错,但我认为这里的某个地方可能存在一个错误。椭圆顶部和底部的厚度似乎太小了一个像素。尝试用outerRadius == innerRadius 绘制一些东西,你就会明白我的意思了。蓝色和绿色部分的厚度似乎还可以,但洋红色和黄色部分有点薄。老实说,我不知道这段代码是如何工作的,所以我没有希望自己修复它! 另外,那些被评论的错误计算让我担心(//errOuter = errOuterXY();)。请您仔细检查它们是否不是必需的并删除它们,好吗?顺便说一句,我真的很喜欢这只画水平线。这使得它对于真正厚的椭圆来说相当快。除了这两件事,我对你所做的一切感到非常满意!如果你解决了这些问题,我什至可能会开放赏金并奖励你一些额外的互联网积分! @Kerndog73 注释的错误计算表示恕我直言,当相应曲线在相应曲线之前没有翻转时,错误的无用重新初始化。环形。取消注释它们不应该改变任何东西(除了一些不必要的额外计算)。【参考方案2】:

您面临的问题是等厚的厚椭圆的轮廓不是椭圆,它们是更高阶的曲线!两个椭圆之间的填充只能给出近似值。

在图片上,红色曲线对应的是恒定的厚度。

正确的解决方案是用粗笔绘制,即使用标准算法,通过让圆心跟随椭圆来扫过所需半径的圆盘。

因此,这是一个低效的过程,因为连续的磁盘重叠并且像素将被绘制多次。一个解决方案是考虑八个位移方向的圆盘覆盖的新像素。对于给定的半径,这些像素集必须提前计算并制成表格。

要建立表格,请绘制一个圆盘并用一个在八个基本方向之一上移动一个像素的圆盘擦除它;重复所有方向。

【讨论】:

这实际上可能是一个可行的解决方案。在直线上绘制 1px 的描边圆仅比 QPainter 在绘制粗线时所做的任何操作慢一点。 1px 的圆圈在对角线移动时确实会留下间隙。位图表听起来是个有趣的想法。我对性能持怀疑态度,因为您必须迭代位图中的每个像素,并且大多数像素都没有设置。不过我还没试过。由于 Scheff 付出的努力,我仍然会将赏金奖励给 Scheff。不过,我可能会考虑更改已接受的答案。 ? 我可以做一些小操作来使位图循环体无分支。我仍然持怀疑态度,因为您仍然会看到很多不需要更改的像素。 当然要存储一个要设置的像素列表! 啊,这很聪明! @Kerndog73:如果我是对的,您可以通过改进的圆形绘制算法来构建这些“增量”列表:每次绘制一个像素时,您都会检查它在主要方向上有哪些邻居.

以上是关于中点粗椭圆绘制算法的主要内容,如果未能解决你的问题,请参考以下文章

中点Bresenham算法光栅化画椭圆(四分法)

opencv学习笔记基本图像的绘制——直线椭圆矩形圆和多边形

MATLAB实现高斯混合分布的EM算法及二维时概率密度曲面置信椭圆绘制

MATLAB实现高斯混合分布的EM算法及二维时概率密度曲面置信椭圆绘制

如何用MATHCAD绘制椭圆

CAD参数绘制椭圆(网页版)