8

いくつかの領域を囲む任意の形状の曲線が得られました。iPhone/iPadの画面で曲線が囲んでいるピクセル数を概算したいと思います。どうすればいいですか?

  • 曲線は、点の連続するx/y座標として定義されます。
  • カーブが閉じています。
  • 曲線はユーザーのタッチ(touchesMovedメソッド)によって描画されますが、どのように見えるかわかりません。

ここに画像の説明を入力してください

どういうわけか閉曲線を色で塗りつぶしてから、画面のスクリーンショットでこの色のピクセル数を計算することを考えていました。これは、閉曲線をプログラムで色で塗りつぶす方法を知る必要があることを意味します。

私が考えていない他の方法はありますか?

ありがとうございました!

4

2 に答える 2

8

カーブを囲むクォーツパスを作成して、これを実行しましょう。次に、ビットマップコンテキストを作成し、そのコンテキストでパスを入力します。次に、ビットマップを調べて、塗りつぶされたピクセルを数えます。これをすべて便利な関数でラップします。

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

ビットマップの1行あたりのバイト数を選択するには、パスの境界ボックスの幅を使用できます。しかし、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;
}

最後に、実際に塗りつぶされたピクセルをカウントするヘルパーを作成する必要があります。ピクセルのカウント方法を決定する必要があります。各ピクセルは、1つの符号なし8ビット整数で表されます。黒のピクセルは0です。白のピクセルは255です。その間の数字は灰色の陰影です。Quartzは、灰色のピクセルを使用してパスを塗りつぶすときに、パスのエッジをアンチエイリアスします。したがって、これらの灰色のピクセルをカウントする方法を決定する必要があります。

1つの方法は、128などのしきい値を定義することです。しきい値以上のピクセルは、塗りつぶされたものとしてカウントされます。残りは未記入としてカウントされます。

もう1つの方法は、灰色のピクセルを部分的に塗りつぶされたものとしてカウントし、その部分的な塗りつぶしを合計することです。したがって、2つの正確に半分塗りつぶされたピクセルが結合され、1つの完全に塗りつぶされたピクセルとしてカウントされます。そのようにしましょう:

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

この要点にバンドルされているすべてのコードを見つけることができます。

于 2013-01-11T22:30:40.173 に答える
0

図面をCGIMageとして取得します...

(CGBitmapContextCreateImage(UIGraphicsGetCurrentContext());

次に、上記で推奨されているように、「塗りつぶし」アプローチを使用してピクセルをカウントします。(Google Flood Fill)

于 2013-01-08T18:17:59.220 に答える