【问题标题】:Xiaolin Wu circle algorithm renders circle with holes inside吴晓林圆算法渲染内有孔的圆
【发布时间】:2019-02-08 14:46:16
【问题描述】:

我已经从这里实现了小林吴圆算法:https://create.stephan-brumme.com/antialiased-circle/ in c++:

float radiusX = endRadius;
float radiusY = endRadius;
float radiusX2 = radiusX * radiusX;
float radiusY2 = radiusY * radiusY;

float maxTransparency = 127;

float quarter = roundf(radiusX2 / sqrtf(radiusX2 + radiusY2));
for(float _x = 0; _x <= quarter; _x++) {
    float _y = radiusY * sqrtf(1 - _x * _x / radiusX2);
    float error = _y - floorf(_y);

    float transparency = roundf(error * maxTransparency);
    int alpha = transparency;
    int alpha2 = maxTransparency - transparency;

    setPixel4(x, y, _x, floorf(_y), r, g, b, alpha, data, areasData, false);
    setPixel4(x, y, _x, floorf(_y) - 1, r, g, b, alpha2, data, areasData, false);
}

quarter = roundf(radiusY2 / sqrtf(radiusX2 + radiusY2));
for(float _y = 0; _y <= quarter; _y++) {
    float _x = radiusX * sqrtf(1 - _y * _y / radiusY2);
    float error = _x - floorf(_x);

    float transparency = roundf(error * maxTransparency);
    int alpha = transparency;
    int alpha2 = maxTransparency - transparency;

    setPixel4(x, y, floorf(_x), _y, r, g, b, alpha, data, areasData, false);
    setPixel4(x, y, floorf(_x) - 1, _y, r, g, b, alpha2, data, areasData, false);
}

x,y 为圆心坐标。

在我看来它看起来不错:

但是,我需要填充圆圈。也许我错了,但我开发了一个简单的算法:从 1 迭代到半径,然后画一个圆。它看起来像这样:

奇怪。所以,为了解决这个问题,我还将透明度设置为最大,直到我到达最后一个半径(所以它是一个外圆):

正如你所见,外层和其他层之间有奇怪的洞。我试过制作两个外层和类似的东西,但没有得到正确的结果。

这是最终版本的代码:

for(int cradius = startRadius; cradius <= endRadius; cradius++) {
    bool last = cradius == endRadius;

    float radiusX = cradius;
    float radiusY = cradius;
    float radiusX2 = radiusX * radiusX;
    float radiusY2 = radiusY * radiusY;

    float maxTransparency = 127;

    float quarter = roundf(radiusX2 / sqrtf(radiusX2 + radiusY2));
    for(float _x = 0; _x <= quarter; _x++) {
        float _y = radiusY * sqrtf(1 - _x * _x / radiusX2);
        float error = _y - floorf(_y);

        float transparency = roundf(error * maxTransparency);
        int alpha = transparency;
        int alpha2 = maxTransparency - transparency;

        if(!last) {
            alpha = maxTransparency;
            alpha2 = maxTransparency;
        }

        setPixel4(x, y, _x, floorf(_y), r, g, b, alpha, data, areasData, false);
        setPixel4(x, y, _x, floorf(_y) - 1, r, g, b, alpha2, data, areasData, false);
    }

    quarter = roundf(radiusY2 / sqrtf(radiusX2 + radiusY2));
    for(float _y = 0; _y <= quarter; _y++) {
        float _x = radiusX * sqrtf(1 - _y * _y / radiusY2);
        float error = _x - floorf(_x);

        float transparency = roundf(error * maxTransparency);
        int alpha = transparency;
        int alpha2 = maxTransparency - transparency;

        if(!last) {
            alpha = maxTransparency;
            alpha2 = maxTransparency;
        }

        setPixel4(x, y, floorf(_x), _y, r, g, b, alpha, data, areasData, false);
        setPixel4(x, y, floorf(_x) - 1, _y, r, g, b, alpha2, data, areasData, false);
    }
}

我该如何解决这个问题?

编辑:

因为我不能使用洪水填充来填充圆圈(我绘制的区域可能不是单色背景,我需要混合这些颜色)我已经实现了将点与线连接的简单方法:

我在 setPixel4 方法中添加了 2 个 drawLine 调用:

void setPixel4(int x, int y, int deltaX, int deltaY, int r, int g, int b, int a, unsigned char* data, unsigned char* areasData, bool blendColor) {
    drawLine(x - deltaX, y - deltaY, x + deltaX, y + deltaY, r, g, b, 127, data, areasData); //maxTransparency
    drawLine(x + deltaX, y - deltaY, x - deltaX, y + deltaY, r, g, b, 127, data, areasData); //maxTransparency

    setPixelWithCheckingArea(x + deltaX, y + deltaY, r, g, b, a, data, areasData, blendColor);
    setPixelWithCheckingArea(x - deltaX, y + deltaY, r, g, b, a, data, areasData, blendColor);
    setPixelWithCheckingArea(x + deltaX, y - deltaY, r, g, b, a, data, areasData, blendColor);
    setPixelWithCheckingArea(x - deltaX, y - deltaY, r, g, b, a, data, areasData, blendColor);
}

它看起来与第三张图片完全相同。我认为里面的这些白色像素是由外圈(来自小林吴算法)本身造成的。

编辑 2:

感谢@JaMiT,我改进了我的代码,它适用于一个圆圈,但是当我有更多的相互重叠时会失败。一、新代码:

void drawFilledCircle(int x, int y, int startRadius, int endRadius, int r, int g, int b, int a, unsigned char* data, unsigned char* areasData, int startAngle, int endAngle, bool blendColor) {
    assert(startAngle <= endAngle);
    assert(startRadius <= endRadius);

    dfBufferCounter = 0;

    for(int i = 0; i < DRAW_FILLED_CIRCLE_BUFFER_SIZE; i++) {
        drawFilledCircleBuffer[i] = -1;
    }

    for(int cradius = endRadius; cradius >= startRadius; cradius--) {
        bool last = cradius == endRadius;
        bool first = cradius == startRadius && cradius != 0;

        float radiusX = cradius;
        float radiusY = cradius;
        float radiusX2 = radiusX * radiusX;
        float radiusY2 = radiusY * radiusY;

        float maxTransparency = 127;

        float quarter = roundf(radiusX2 / sqrtf(radiusX2 + radiusY2));
        for(float _x = 0; _x <= quarter; _x++) {
            float _y = radiusY * sqrtf(1 - _x * _x / radiusX2);
            float error = _y - floorf(_y);

            float transparency = roundf(error * maxTransparency);
            int alpha = last ? transparency : maxTransparency;
            int alpha2 = first ? maxTransparency - transparency : maxTransparency;

            setPixel4(x, y, _x, floorf(_y), r, g, b, alpha, cradius, endRadius, data, areasData, blendColor);
            setPixel4(x, y, _x, floorf(_y) - 1, r, g, b, alpha2, cradius, endRadius, data, areasData, blendColor);
        }

        quarter = roundf(radiusY2 / sqrtf(radiusX2 + radiusY2));
        for(float _y = 0; _y <= quarter; _y++) {
            float _x = radiusX * sqrtf(1 - _y * _y / radiusY2);
            float error = _x - floorf(_x);

            float transparency = roundf(error * maxTransparency);
            int alpha = last ? transparency : maxTransparency;
            int alpha2 = first ? maxTransparency - transparency : maxTransparency;

            setPixel4(x, y, floorf(_x), _y, r, g, b, alpha, cradius, endRadius, data, areasData, blendColor);
            setPixel4(x, y, floorf(_x) - 1, _y, r, g, b, alpha2, cradius, endRadius, data, areasData, blendColor);
        }
    }
}

如果在 setPixel4 中没有 drawLine 调用,它看起来像这样:

我改进了 setPixel4 方法以避免再次重绘相同的像素:

void setPixel4(int x, int y, int deltaX, int deltaY, int r, int g, int b, int a, int radius, int maxRadius, unsigned char* data, unsigned char* areasData, bool blendColor) {

    for(int j = 0; j < 4; j++) {

        int px, py;
        if(j == 0) {
            px = x + deltaX;
            py = y + deltaY;
        } else if(j == 1) {
            px = x - deltaX;
            py = y + deltaY;
        } else if(j == 2) {
            px = x + deltaX;
            py = y - deltaY;
        } else if(j == 3) {
            px = x - deltaX;
            py = y - deltaY;
        }

        int index = (px + (img->getHeight() - py - 1) * img->getWidth()) * 4;

        bool alreadyInBuffer = false;
        for(int i = 0; i < dfBufferCounter; i++) {
            if(i >= DRAW_FILLED_CIRCLE_BUFFER_SIZE) break;
            if(drawFilledCircleBuffer[i] == index) {
                alreadyInBuffer = true;
                break;
            }
        }

        if(!alreadyInBuffer) {
            if(dfBufferCounter < DRAW_FILLED_CIRCLE_BUFFER_SIZE) {
                drawFilledCircleBuffer[dfBufferCounter++] = index;
            }

            setPixelWithCheckingArea(px, py, r, g, b, a, data, areasData, blendColor);
        }
    }

}

然后,最后:

这几乎是完美的。但是,为了摆脱这个白色的轮廓,我挣扎了很多时间,但我做不到。

【问题讨论】:

  • 为什么不从中心进行简单的洪水填充?
  • 如果你从外到内画,我想它看起来应该是对的。
  • 但我不敢相信没有绘制抗锯齿(实心)圆的功能。
  • Flood-fill 是有道理的,但是.. 目标是绘制半透明的圆圈并混合它们的颜色。因此,背景可能并不总是相同的颜色,然后填充将无济于事。
  • 请注意:这个算法很好,我只是遇到了混合问题。这是解决方案:stackoverflow.com/a/54695312/1264375

标签: c++ rendering cocos2d-x antialiasing


【解决方案1】:

因为您将圆离散化,所以必然会丢失一些像素。您获得的图片显示了众所周知的莫尔效应。

最好的解决方案是使用任何洪水填充算法,或者合成一个简单的算法,在同一水平线(或垂直线,如果您愿意)上的圆点之间画线。

【讨论】:

  • Flood-fill 是有道理的,但是.. 目标是绘制半透明的圆圈并混合它们的颜色。因此,背景可能并不总是相同的颜色,然后填充将无济于事。我想我只剩下水平/垂直线了。
  • @Makalele您可以尝试通过每一步小于1像素来绘制圆圈...由于我不明白您尝试达到什么样的效果,很难帮助您更多。或者使用 Wu 以外的其他算法,因为它是一种非常特殊的抗锯齿算法。
  • 我已经尝试过了。我想用光滑的边缘填充圆(所以第一张图像,刚刚填充)。
  • @Makalele 然后再次画圆并在同一水平线上从一个点到另一个点画一条线!分析算法,了解点是如何通过对称生成的,边界点是什么……如果应该是圆盘,那就简单了。
  • 查看我的编辑。我通过画线得到了相同的图像。
【解决方案2】:

想想你正在做什么来获得第三张图像(在圆周内有“奇怪的洞”的那张)。你画了内盘,你想在它周围画一个圆圈,让它稍微大一点。好主意。 (你的微积分老师应该批准。)

但是,您不能简单地围绕它画一个圆圈;你在它周围画了一个 antialiased 圆圈。那是什么意思?这意味着你不是简单地画一个点,而是画两个,用不同的透明度来欺骗眼睛,让他们认为它只是一个。其中一个点(内部点)将覆盖您已经绘制的磁盘上的一个点。

当外点更透明时,除了可能有点模糊之外没有问题。但是,当 inner 点更透明时,您会出现这种奇怪的行为,即磁盘开始时大部分是不透明的,然后变得更加透明,然后又恢复为完全不透明。你从磁盘上取了一个完全不透明的点,使它大部分是透明的。您的眼睛会将其解读为一个洞。

那么如何解决这个问题

1) 只要你的磁盘应该是均匀着色的(考虑到透明度),如果你反转外环,你的最后一次尝试应该会起作用——从最大半径变为零。因为只有最外层的圆圈被赋予抗锯齿,所以只有这个反向循环的第一次迭代会用更透明的像素覆盖一个像素。在那个阶段没有什么可以覆盖的。

2) 在您设置alpha2 的两个地方,将其设置为maxTransparency。这是内部像素的透明度,您不希望对内部边缘进行抗锯齿处理。继续并在任一方向上循环半径,将您的磁盘构建成圆形。不绘制最外圈时,继续将两个透明度设置为最大。这种方法的优点是可以在磁盘中间打一个洞; startRadius 不必为零。当你在startRadius(且startRadius不为零)时,根据反锯齿算法设置alpha2,但将alpha设置为maxTransparency

所以你的 alpha 设置逻辑看起来像

    bool first = cradius == startRadius  &&  cRadius != 0; // Done earlier
    int alpha = last ? transparency : maxTransparency;
    int alpha2 = first ? maxTransparency - transparency : maxTransparency;

编辑: 想想看,如果cRadius 为零,就会被零除。由于您显然已经考虑到了这一点,您应该能够将“第一”的概念调整为“最内圈,我们实际上正在留下一个洞”。

3) 您可以按照建议画线,但有一些事情需要调整以尽量减少伪影。首先,删除每对中对setPixel4 的第二次调用;我们将用线条覆盖这种情况。这消除了拥有alpha2 的需要(无论如何这是造成漏洞的原因)。其次,尝试画一个盒子(四条线)而不是两条平行线。使用这种算法,一半的绘图基于水平线,一半基于垂直线。通过一直绘制两者,您可以覆盖您的基础。第三,如果您仍然看到伪影,请尝试在第一个框内绘制第二个框。

【讨论】:

  • 非常感谢您的详细回答。方法 #1 效果很好,我不敢相信它这么容易修复。圆圈看起来很棒。但是,我还需要颜色混合。这就是问题开始的地方。当我有 2 个(或更多)圆圈彼此部分相交时,我在相交的轮廓上会出现奇怪的伪影。我也在同时使用方法#2。不过我没有帮忙。我已经尝试过实现方法 #3,但我做错了。有关详细信息,请参阅我编辑的答案。
  • @Makalele “我也需要”——这表明你还有第二个问题要问。在这篇文章中加入第二个问题可能会在短期内使您受益,但它往往会给试图解决相同问题的其他人留下难以理解的混乱。我建议撤消您最近的编辑,然后针对新问题开始一个新问题。 (确保人们即使没有读过这个问题也能理解这个新问题。)
  • 但这几乎是相同的算法,只是需要稍微调整一下。此外,如果它可以工作,那将是一个更好的答案。
  • 我发现现在问题出在混合上,而不是 xiaolin wu 算法本身。所以,现在提出一个新问题是有意义的:stackoverflow.com/questions/54692947/…
  • 我认为,只要您添加了第二个圈子,就可以提出一个新问题。最初的问题是如何使 one 圆圈看起来正确。其他人可能会提出同样的问题。有什么理由认为其他人会想要两个圈子?并不真地。所以第二个问题是有道理的。 (如果第二个答案恰好是对第一个的改进,那么,可以更新答案。合并答案比分离问题更容易。)
【解决方案3】:

你应该只画一个外圆,从左到右像素边用实心水平简单线连接。

]1

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-13
    • 2021-12-20
    • 2014-03-13
    相关资源
    最近更新 更多