11

Core Graphics を使用して消しゴム ツールを作成しようとしていますが、パフォーマンスの高い消しゴムを作成するのは非常に難しいと感じています。

CGContextSetBlendMode(context, kCGBlendModeClear)

Core Graphics で「消去」する方法をグーグルで検索すると、ほとんどすべての回答がそのスニペットで返されます。問題は、(明らかに) ビットマップ コンテキストでのみ機能することです。インタラクティブな消去を実装しようとしている場合、どのようkCGBlendModeClearに役立つかわかりません.パフォーマンス。UIImageCGImage[UIView drawRect]

これが私ができる最高のことです:

-(void)drawRect:(CGRect)rect
{
    if (drawingStroke) {
        if (eraseModeOn) {
            UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0);
            CGContextRef context = UIGraphicsGetCurrentContext();
            [eraseImage drawAtPoint:CGPointZero];
            CGContextAddPath(context, currentPath);
            CGContextSetLineCap(context, kCGLineCapRound);
            CGContextSetLineWidth(context, lineWidth);
            CGContextSetBlendMode(context, kCGBlendModeClear);
            CGContextSetLineWidth(context, ERASE_WIDTH);
            CGContextStrokePath(context);
            curImage = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();
            [curImage drawAtPoint:CGPointZero];
        } else {
            [curImage drawAtPoint:CGPointZero];
            CGContextRef context = UIGraphicsGetCurrentContext();
            CGContextAddPath(context, currentPath);
            CGContextSetLineCap(context, kCGLineCapRound);
            CGContextSetLineWidth(context, lineWidth);
            CGContextSetBlendMode(context, kCGBlendModeNormal);
            CGContextSetStrokeColorWithColor(context, lineColor.CGColor);
            CGContextStrokePath(context);
        }
    } else {
        [curImage drawAtPoint:CGPointZero];
    }
}

法線の描画 ( !eraseModeOn) は許容できるパフォーマンスです。オフスクリーン描画バッファー (curImage以前に描画されたすべてのストロークを含む) を現在の にブリットし、現在CGContext描画されている線 (パス) をレンダリングしています。完璧ではありませんが、動作し、適度なパフォーマンスを発揮します。

ただし、kCGBlendModeNormal明らかにビットマップコンテキストの外では機能しないため、次のことを余儀なくされています。

  1. ビットマップ コンテキストを作成します ( UIGraphicsBeginImageContextWithOptions)。
  2. オフスクリーン バッファを描画します (eraseImageこれは、消しゴム ツールがオンになっているときに実際に派生するため、引数の目的curImageとほとんど同じです)。curImage
  3. 現在描画されている「消去線」(パス) をビットマップ コンテキストにレンダリングします (kCGBlendModeClearピクセルをクリアするために使用します)。
  4. 画像全体をオフスクリーン バッファに抽出します ( curImage = UIGraphicsGetImageFromCurrentImageContext();)
  5. そして最後に、オフスクリーン バッファをビューのCGContext

パフォーマンス的には恐ろしいことです。Instrument の Time ツールを使用すると、この方法のどこに問題があるかが痛々しいほど明白になります。

  • UIGraphicsBeginImageContextWithOptions高いです
  • オフスクリーン バッファを 2 回描画するとコストがかかる
  • 画像全体をオフスクリーン バッファに抽出するのはコストがかかる

当然のことながら、このコードは実際の iPad ではひどいパフォーマンスを示します。

ここで何をすべきかよくわかりません。ビットマップ以外のコンテキストでピクセルをクリアする方法を見つけようとしていますが、私が知る限り、に依存することkCGBlendModeClearは行き止まりです。

何か考えや提案はありますか?他の iOS 描画アプリはどのように消去を処理しますか?


追加情報

私が行った少しのグーグル検索に基づいて 動作CGLayerするように見えるので、私はアプローチで遊んでいます。CGContextSetBlendMode(context, kCGBlendModeClear)CGLayer

ただし、このアプローチがうまくいくとはあまり期待していません。drawRectレイヤーを( を使用しても)描画するsetNeedsDisplayInRectと、非常にパフォーマンスが低下します。CGContextDrawLayerAtPointCore Graphics は、 (Instruments によると)レイヤー内の各パスをレンダリングします。私が知る限り、ビットマップ コンテキストを使用することは、パフォーマンスの点でここでは間違いなく望ましいです。もちろん、唯一の問題は上記の質問です (ビットマップ コンテキストをメインにkCGBlendModeClearブリットした後は動作しません)。CGContextdrawRect

4

3 に答える 3

8

次のコードを使用して、良い結果を得ることができました。

- (void)drawRect:(CGRect)rect
{
    if (drawingStroke) {
        if (eraseModeOn) {
            CGContextRef context = UIGraphicsGetCurrentContext();
            CGContextBeginTransparencyLayer(context, NULL);
            [eraseImage drawAtPoint:CGPointZero];

            CGContextAddPath(context, currentPath);
            CGContextSetLineCap(context, kCGLineCapRound);
            CGContextSetLineWidth(context, ERASE_WIDTH);
            CGContextSetBlendMode(context, kCGBlendModeClear);
            CGContextSetStrokeColorWithColor(context, [[UIColor clearColor] CGColor]);
            CGContextStrokePath(context);
            CGContextEndTransparencyLayer(context);
        } else {
            [curImage drawAtPoint:CGPointZero];
            CGContextRef context = UIGraphicsGetCurrentContext();
            CGContextAddPath(context, currentPath);
            CGContextSetLineCap(context, kCGLineCapRound);
            CGContextSetLineWidth(context, self.lineWidth);
            CGContextSetBlendMode(context, kCGBlendModeNormal);
            CGContextSetStrokeColorWithColor(context, self.lineColor.CGColor);
            CGContextStrokePath(context);
        }
    } else {
        [curImage drawAtPoint:CGPointZero];
    }

    self.empty = NO;
}

トリックは、以下をCGContextBeginTransparencyLayer/CGContextEndTransparencyLayer呼び出しにラップすることでした:

  • 消去背景画像をコンテキストにブリットする
  • を使用して、消去の背景画像の上に「消去」パスを描画しますkCGBlendModeClear

消去背景イメージのピクセル データと消去パスの両方が同じレイヤーにあるため、ピクセルをクリアする効果があります。

于 2013-11-11T22:55:12.903 に答える