web-dev-qa-db-ja.com

CGContextDrawImageは、大きなUIImageが描画された後、非常に遅くなります

CGContextDrawImage(CGContextRef、CGRect、CGImageRef)は、UIImageをバックアップするCGImageを描画する場合よりも、CoreGraphicsによって(つまり、CGBitmapContextCreateImageを使用して)作成されたCGImageを描画する場合にはるかに悪いパフォーマンスを示すようです。このテスト方法を参照してください。

-(void)showStrangePerformanceOfCGContextDrawImage
{
    ///Setup : Load an image and start a context:
    UIImage *theImage = [UIImage imageNamed:@"reallyBigImage.png"];
    UIGraphicsBeginImageContext(theImage.size);
    CGContextRef ctxt = UIGraphicsGetCurrentContext();    
    CGRect imgRec = CGRectMake(0, 0, theImage.size.width, theImage.size.height);


    ///Why is this SO MUCH faster...
    NSDate * startingTimeForUIImageDrawing = [NSDate date];
    CGContextDrawImage(ctxt, imgRec, theImage.CGImage);  //Draw existing image into context Using the UIImage backing    
    NSLog(@"Time was %f", [[NSDate date] timeIntervalSinceDate:startingTimeForUIImageDrawing]);

    /// Create a new image from the context to use this time in CGContextDrawImage:
    CGImageRef theImageConverted = CGBitmapContextCreateImage(ctxt);

    ///This is WAY slower but why??  Using a pure CGImageRef (ass opposed to one behind a UIImage) seems like it should be faster but AT LEAST it should be the same speed!?
    NSDate * startingTimeForNakedGImageDrawing = [NSDate date];
    CGContextDrawImage(ctxt, imgRec, theImageConverted);
    NSLog(@"Time was %f", [[NSDate date] timeIntervalSinceDate:startingTimeForNakedGImageDrawing]);


}

だから私は質問が、#1これを引き起こしている可能性があるものと#2それを回避する方法、つまりより速いかもしれないCGImageRefを作成する他の方法があると思いますか?最初にすべてをUIImagesに変換できることに気付きましたが、それはとても醜い解決策です。私はすでにCGContextRefをそこに置いています。

更新:これは小さな画像を描くときに必ずしも当てはまらないようですか?それは手がかりかもしれません-この問題は大きな画像(つまりフルサイズのカメラ写真)が使用されるときに増幅されます。 640x480は、どちらの方法でも実行時間の点でかなり似ているようです

更新2:わかりました、それで私は何か新しいものを発見しました..それは実際にはパフォーマンスを変えているCGImageの裏付けではありません。 2つのステップの順序をフリップフロップして、UIImageメソッドの動作を遅くすることができますが、「裸の」CGImageは超高速になります。どちらを2番目に実行しても、ひどいパフォーマンスに悩まされるようです。これは、CGBitmapContextCreateImageで作成した画像でCGImageReleaseを呼び出してメモリを解放しない限り、当てはまるようです。その後、UIImageでバックアップされたメソッドは高速になります。逆は真実ではありません。何が得られますか? 「混雑した」メモリは、このようなパフォーマンスに影響を与えるべきではありませんか?

更新3:話が早すぎた。以前の更新は、サイズ2048x2048の画像に当てはまりますが、1936x2592(カメラサイズ)にステップアップすると、操作の順序やメモリの状況に関係なく、ネイキッドCGImageメソッドの速度が大幅に低下します。 21MBの画像を効率的に処理できないのに対し、16MBの画像を効率的にするCGの内部制限があるかもしれません。カメラサイズの描画は、2048x2048よりも文字通り20倍遅くなります。どういうわけか、UIImageは純粋なCGImageオブジェクトよりもはるかに高速にCGImageデータを提供します。 o.O

更新4:これは何らかのメモリキャッシュの問題に関係しているのではないかと思いましたが、UIImageが非キャッシュの[UIImage imageWithContentsOfFile]でロードされても、[UIImageimageNamed]が使用されている場合と同じ結果になります。

更新5(2日目):昨日答えられたよりもmroeの質問を作成した後、私は何か今日はしっかりしています。私が確かに言えることは次のとおりです。

  • UIImageの背後にあるCGImageはアルファを使用しません。 (kCGImageAlphaNoneSkipLast)。私のコンテキストはアルファを使用していたので、おそらくそれらはより速く描画されると思いました。そこで、kCGImageAlphaNoneSkipLastを使用するようにコンテキストを変更しました。これにより、以下の場合を除いて、描画がはるかに高速になります。
  • 最初にUIImageを使用してCGContextRefに描画すると、後続のすべての画像の描画が遅くなります

私はこれを1)最初に非アルファコンテキスト(1936x2592)を作成することによって証明しました。 2)ランダムに色付けされた2x2の正方形で塗りつぶしました。 3)そのコンテキストにCGImageを描画するフルフレームは高速(.17秒)でした。4)実験を繰り返しましたが、UIImageを裏付ける描画されたCGImageでコンテキストを埋めました。その後のフルフレーム画像の描画は6秒以上でした。 SLOWWWWW。

どういうわけか、(大)UIImageを使用してコンテキストに描画すると、そのコンテキストへの後続のすべての描画が大幅に遅くなります。

28
typewriter

たくさんの実験の後、私はこのような状況を処理するための最速の方法を見つけたと思います。 6秒以上かかっていた描画操作が0.1秒になりました。はい。これが私が発見したものです:

  • ピクセル形式でコンテキストと画像を均質化します!私が尋ねた質問の根本は、UIImage内のCGImageが私のコンテキストとして同じピクセルフォーマットを使用していたという事実に要約されます。したがって、高速です。 CGImagesは異なる形式であったため、低速でした。 CGImageGetAlphaInfoを使用して画像を検査し、使用しているピクセル形式を確認します。アルファを使用する必要がないため、現在はどこでもkCGImageAlphaNoneSkipLastを使用しています。どこでも同じピクセル形式を使用しない場合、コンテキストに画像を描画すると、Quartzは各ピクセルに対して高価なピクセル変換を実行することを余儀なくされます。 =遅い

  • CGLayersを使用してください!これらにより、オフスクリーン描画のパフォーマンスが大幅に向上します。これがどのように機能するかは基本的に次のとおりです。 1)CGLayerCreateWithContextを使用して、コンテキストからCGLayerを作成します。 2)CGLayerGetContextで取得したこのレイヤーのコンテキストで描画/描画プロパティの設定を行います。元のコンテキストからピクセルまたは情報を読み取ります。 3)完了したら、CGContextDrawLayerAtPointを使用して、このCGLayerを元のコンテキストに「スタンプ」します。これは、次の点に注意する限り高速です。

  • 1)CGContextDrawLayerAtPointを使用してレイヤーをCGContextRefに「スタンプ」する前に、コンテキストから作成されたCGImage(つまり、CGBitmapContextCreateImageで作成されたもの)を解放します。これにより、そのレイヤーを描画するときに3〜4倍の速度が向上します。 2)どこでも同じピクセルフォーマットを維持してください!! 3)CGオブジェクトをできるだけ早くクリーンアップします。おそらくこれらの強力な参照に関連付けられたコールバックまたはチェックがあるため、メモリ内でぶらぶらしているものは、速度低下の奇妙な状況を作成しているようです。推測ですが、メモリをできるだけ早くクリーンアップすると、パフォーマンスが大幅に向上すると言えます。

41
typewriter

私も同様の問題を抱えていました。私のアプリケーションは、画面サイズとほぼ同じ大きさの画像を再描画する必要があります。問題は、同じ解像度の2つの画像をできるだけ速く描画することでした。回転も反転もせず、毎回画面の異なる場所に拡大縮小して配置しました。結局のところ、iPad 1では最大15〜20 FPS、iPad4では最大20〜25FPSを得ることができました。だから...これが誰かを助けることを願っています:

  1. typewriterが言ったように、同じピクセル形式を使用する必要があります。 AlphaNoneと一緒に使用すると、速度が向上します。しかし、さらに重要なのは、私の場合のargb32_image呼び出しは、ピクセルをARGBからBGRAに変換する多数の呼び出しを行ったことです。したがって、私にとって最良のbitmapInfo値は(当時; Appleは将来ここで何かを変更できる可能性があります):const CGBitmabInfo g_bitmapInfo = kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast;
  2. 長方形の引数が(CGRectIntegralによって)整数にされた場合、CGContextDrawImageはより速く動作する可能性があります。画像を1に近い係数で拡大縮小すると、より効果があるようです。
  3. レイヤーを使用すると、実際に速度が低下しました。おそらく、2011年以降、一部の社内通話で何かが変更されました。
  4. (CGContextSetInterpolationQualityによって)デフォルトよりも低いコンテキストの補間品質を設定することが重要です。 (IS_RETINA_DISPLAY ? kCGInterpolationNone : kCGInterpolationLow)の使用をお勧めします。マクロIS_RETINA_DISPLAYは ここ から取得されます。
  5. コンテキストを作成するときは、必ずCGColorSpaceCreateDeviceRGB()などからCGColorSpaceRefを取得してください。デバイスの色空間を要求する代わりに、固定の色空間を取得するためのパフォーマンスの問題がいくつか報告されました。
  6. UIImageViewからビュークラスを継承し、コンテキストから作成された画像にself.imageを設定するだけで便利であることがわかりました。ただし、これを実行する場合は、最初にUIImageViewの使用についてお読みください。これは、コードロジックを変更する必要があるためです(drawRect:が呼び出されなくなったため)。
  7. また、実際の描画時に画像の拡大縮小を回避できる場合は、拡大縮小を試みてください。拡大縮小されていない画像を描画する方がはるかに高速です。残念ながら、それは選択肢ではありませんでした。
13
Abstraction