且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2023-02-13 19:07:57

让我们通过创建一条包围曲线的Quartz路径来实现。然后我们将创建一个位图上下文并填充该上下文中的路径。然后我们可以检查位图并计算填充的像素。我们将这一切包装在一个方便的函数中:

Let's do this by creating a Quartz path enclosing your curve. Then we'll create a bitmap context and fill the path in that context. Then we can examine the bitmap and count the pixels that were filled. We'll wrap this all in a convenient function:

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

首先我们需要创建路径:

First we need to create the path:

    CGPathRef path = createClosedPathWithPoints(points, count);

然后我们需要获取路径的边界框。 CGPoint 坐标不必是整数,但是位图必须具有整数尺寸,因此我们将得到一个至少与路径边界框一样大的整数边界框:

Then we need to get the bounding box of the path. CGPoint coordinates don't have to be integers, but a bitmap has to have integer dimensions, so we'll get an integral bounding box at least as big as the path's bounding box:

    CGRect frame = integralFrameForPath(path);

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

We also need to decide how wide (in bytes) to make the bitmap:

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

现在我们可以创建位图:

Now we can create the bitmap:

    CGContextRef gc = createBitmapContextWithFrame(frame, bytesPerRow);

位图在创建时用黑色填充。我们将用白色填充路径:

The bitmap is filled with black when it's created. We'll fill the path with white:

    CGContextSetFillColorWithColor(gc, [UIColor whiteColor].CGColor);
    CGContextAddPath(gc, path);
    CGContextFillPath(gc);

现在我们已经完成了路径,所以我们可以发布它:

Now we're done with the path so we can release it:

    CGPathRelease(path);

接下来我们将计算已填充的区域:

Next we'll compute the area that was filled:

    double area = areaFilledInBitmapContext(gc);

现在我们已经完成了位图上下文,所以我们可以发布它:

Now we're done with the bitmap context, so we can release it:

    CGContextRelease(gc);

最后,我们可以返回我们计算的区域:

Finally, we can return the area we computed:

    return area;
}

嗯,这很简单!但是我们必须编写所有这些辅助函数。让我们从顶部开始吧。创建路径是微不足道的:

Well, that was easy! But we have to write all those helper functions. Let's start at the top. Creating the path is trivial:

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

获取路径的整数边界框也很简单:

Getting the integral bounding box of the path is also trivial:

static CGRect integralFrameForPath(CGPathRef path) {
    CGRect frame = CGPathGetBoundingBox(path);
    return CGRectIntegral(frame);
}

要选择位图的每行字节数,我们可以只使用宽度路径的边界框。但我认为Quartz喜欢将位图设置为两倍的优势的倍数。我没有对此进行任何测试,因此您可能想要进行实验。现在,我们将宽度四舍五入为64的下一个最小倍数:

To choose the bytes per row of the bitmap, we could just use width of the path's bounding box. But I think Quartz likes to have bitmaps that are multiples of a nice power of two. I haven't done any testing on this, so you might want to experiment. For now, we'll round up the width to the next smallest multiple of 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)。

We create the bitmap context with the computed sizes. We also need to translate the origin of the coordinate system. Why? Because the origin of the path's bounding box might not be at (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会对路径的边缘进行反锯齿处理。所以我们必须决定如何计算这些灰色像素。

Finally, we need to write the helper that actually counts the filled pixels. We have to decide how we want to count pixels. Each pixel is represented by one unsigned 8-bit integer. A black pixel is 0. A white pixel is 255. The numbers in between are shades of gray. Quartz anti-aliases the edge of the path when it fills it using gray pixels. So we have to decide how to count those gray pixels.

一种方法是定义一个阈值,如128.任何等于或高于阈值的像素都算作填充;其余的计数为未填充。

One way is to define a threshold, like 128. Any pixel at or above the threshold counts as filled; the rest count as unfilled.

另一种方法是将灰色像素计算为部分填充,并将该部分填充加起来。因此,两个完全半填充的像素被组合并计为单个完全填充的像素。让我们这样做:

Another way is to count the gray pixels as partially filled, and add up that partial filling. So two exactly half-filled pixels get combined and count as a single, entirely-filled pixel. Let's do it that way:

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

你可以找到这个要点