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

Posted

技术标签:

【中文标题】iOS如何计算曲线包围的像素数/面积?【英文标题】:iOS how to calculate number of pixels/area enclosed by a curve? 【发布时间】:2012-12-22 15:27:55 【问题描述】:

我得到了一个任意形状的曲线,包围了一些区域。 我想估计曲线在 iPhone/iPad 屏幕上包围的像素数。我该怎么做?

曲线定义为点的连续 x/y 坐标。 一条曲线是闭合的。 通过用户的触摸(touchesMoved 方法)绘制一条曲线,并且我 不知道它的样子

我正在考虑以某种方式用颜色填充闭合曲线,然后在屏幕截图中计算这种颜色的像素数。这意味着我需要知道如何以编程方式用颜色填充闭合曲线。

还有其他我没有想到的方法吗?

谢谢!

【问题讨论】:

谷歌“洪水填充算法”。 另外,你可以做的是数值积分。 构成曲线的 X、Y 坐标是否连续?还是曲线是一组小向量? 在我最后一次作为 ios 开发人员的职位面试中看起来像一个问题:“在 Java 中实现泛洪算法”。 “在 Java 中?我想,这是对 iOS 职位的面试。为什么选择 Java?” “我们只希望开发人员能够做所有事情——我需要能够检查你的答案” 【参考方案1】:

让我们通过创建一个包含曲线的 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;

您可以在this gist 中找到捆绑的所有代码。

【讨论】:

【参考方案2】:

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

(CGBitmapContextCreateImage(UIGraphicsGetCurrentContext());

然后,按照上面的建议,使用“Flood Fill”方法来计算像素数。 (谷歌洪水填充)

【讨论】:

以上是关于iOS如何计算曲线包围的像素数/面积?的主要内容,如果未能解决你的问题,请参考以下文章

sklearn 如何计算二元分类器的 roc 曲线下面积?

如何计算投票集成分类器的 AUC(曲线下面积)?

SSOJ 2316 面积DFS/Flood Fill

用origin计算曲线的积分

用origin计算曲线的积分

怎么在matlab里面求已有两条曲线的交点?