iOS如何计算由曲线包围的像素数/面积?

8
我有一个任意形状的曲线,围绕着一些区域。我想估计这条曲线在iPhone/iPad屏幕上所包含的像素数量。如何做到呢?
以下是需要注意的:
  • 曲线由一系列的x/y坐标点定义。
  • 曲线是闭合的。
  • 曲线是由用户触摸(touchesMoved方法)绘制的,我不知道它的样子。

enter image description here

我考虑用某种方式填充这个闭合的曲线颜色,然后计算屏幕截图中该颜色的像素数量。这意味着我需要知道如何以编程方式填充闭合曲线的颜色。
还有其他我没有想到的方法吗?
谢谢!

1
谷歌“泛洪填充算法”。 - Fogmeister
另外,您可以进行数值积分。 - user529758
你的曲线的X,Y坐标是连续的吗?还是由一组小向量组成的曲线? - Greycon
在我上一次iOS开发职位的面试中,有一个问题让我感到困惑:“用Java实现洪水算法”。“用Java?我以为这是iOS职位的面试。为什么要用Java?”“我们只想要能够全方位开发的开发人员,我需要检查你的答案。” - vikingosegundo
2个回答

8

我们可以通过创建一个包含曲线的Quartz路径来完成这个操作。然后,我们将创建一个位图上下文,并在该上下文中填充路径。然后,我们可以检查位图并计算被填充的像素数。最后,我们将把所有这些封装在一个便捷的函数中:

static double areaOfCurveWithPoints(const CGPoint *points, size_t count) {

首先我们需要创建路径:

    CGPathRef path = createClosedPathWithPoints(points, count);

然后我们需要获取路径的边界框。 CGPoint 坐标不一定是整数,但是位图必须具有整数维度,因此我们将获取至少与路径边界框相同大小的整数边界框:

    CGRect frame = integralFrameForPath(path);

我们还需要决定位图的宽度(以字节为单位):

    size_t bytesPerRow = bytesPerRowForWidth(frame.size.width);

现在我们可以创建位图:
    CGContextRef gc = createBitmapContextWithFrame(frame, bytesPerRow);

创建位图时,其填充颜色为黑色。我们将使用白色来填充路径:
    CGContextSetFillColorWithColor(gc, [UIColor whiteColor].CGColor);
    CGContextAddPath(gc, path);
    CGContextFillPath(gc);

现在我们已经完成了路径,所以我们可以发布它:
    CGPathRelease(path);

下一步,我们将计算填充的区域面积:
    double area = areaFilledInBitmapContext(gc);

现在,我们完成了位图上下文的操作,因此可以释放它:
    CGContextRelease(gc);

最后,我们可以返回我们计算出的区域:
    return area;
}

好的,那很容易!但我们需要编写所有这些辅助函数。让我们从头开始。创建路径是微不足道的:

static CGPathRef createClosedPathWithPoints(const CGPoint *points, size_t count) {
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddLines(path, NULL, points, count);
    CGPathCloseSubpath(path);
    return path;
}

获取路径的整体边界框也很简单:
static CGRect integralFrameForPath(CGPathRef path) {
    CGRect frame = CGPathGetBoundingBox(path);
    return CGRectIntegral(frame);
}

为了选择位图的每行字节数,我们可以使用路径边界框的宽度。但我认为Quartz喜欢具有2的幂次方倍数的位图。我还没有对此进行任何测试,因此您可能需要进行实验。现在,我们将把宽度向上舍入到最接近64的下一个倍数:
static size_t bytesPerRowForWidth(CGFloat width) {
    static const size_t kFactor = 64;
    // Round up to a multiple of kFactor, which must be a power of 2.
    return ((size_t)width + (kFactor - 1)) & ~(kFactor - 1);
}

我们使用计算出的大小创建位图上下文。 我们还需要翻译坐标系的原点。 为什么呢? 因为路径边界框的原点可能不在 (0, 0)。

static CGContextRef createBitmapContextWithFrame(CGRect frame, size_t bytesPerRow) {
    CGColorSpaceRef grayscale = CGColorSpaceCreateDeviceGray();
    CGContextRef gc = CGBitmapContextCreate(NULL, frame.size.width, frame.size.height, 8, bytesPerRow, grayscale, kCGImageAlphaNone);
    CGColorSpaceRelease(grayscale);
    CGContextTranslateCTM(gc, -frame.origin.x, -frame.origin.x);
    return gc;
}

最后,我们需要编写实际计算填充像素的辅助程序。我们必须决定如何计算像素。每个像素由一个无符号8位整数表示。黑色像素为0,白色像素为255。中间的数字是灰度。当Quartz使用灰色像素填充路径时,会对路径的边缘进行抗锯齿处理。因此,我们必须决定如何计算这些灰色像素。
一种方法是定义一个阈值,比如128。任何达到或超过阈值的像素都算作已填充;其余的则算作未填充。
另一种方法是将灰色像素视为部分填充,并将该部分填充相加。因此,两个完全半填充的像素被合并,并计为一个完全填充的像素。让我们采用这种方法:
static double areaFilledInBitmapContext(gc) {
    size_t width = CGBitmapContextGetWidth(gc);
    size_t height = CGBitmapContextGetHeight(gc);
    size_t stride = CGBitmapContextGetBytesPerRow(gc);
    uint8_t *pixels = CGBitmapContextGetData(gc);
    uint64_t coverage = 0;
    for (size_t y = 0; y < height; ++y) {
        for (size_t x = 0; x < width; ++x) {
            coverage += pixels[y * stride + x];
        }
    }
    return (double)coverage / UINT8_MAX;
}

你可以在这个代码片段中找到所有的代码。

0

我会将绘图作为CGImage抓取...

(CGBitmapContextCreateImage(UIGraphicsGetCurrentContext());)

然后,如上所推荐,使用“泛洪填充”方法来计算像素。 (谷歌泛洪填充)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接