我得到了一条任意形状的曲线,包围了一些区域。 我想估计曲线在 iPhone/iPad 屏幕上包围的像素数。我该怎么做?
- 曲线被定义为点的连续 x/y 坐标。
- 闭合曲线。
- 通过用户的触摸(touchesMoved 方法)绘制一条曲线,我 不知道它是什么样子
我正在考虑以某种方式用颜色填充闭合曲线,然后计算屏幕截图中这种颜色的像素数。这意味着我需要知道如何以编程方式用颜色填充闭合曲线。
还有其他我没有想到的方法吗?
谢谢!
最佳答案
让我们通过创建一个封闭曲线的 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 中找到捆绑的所有代码.
关于iphone - iOS如何计算曲线包围的像素数/面积?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14220719/