web-dev-qa-db-ja.com

iOSで画像の一部を描画する最も効率的な方法

UIImageCGRectが与えられた場合、CGRectに対応する画像の一部を(メモリと時間で)描画する最も効率的な方法は何ですか(スケーリングなし)?

参考までに、これは私が現在行っている方法です。

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGRect frameRect = CGRectMake(frameOrigin.x + rect.Origin.x, frameOrigin.y + rect.Origin.y, rect.size.width, rect.size.height);    
    CGImageRef imageRef = CGImageCreateWithImageInRect(image_.CGImage, frameRect);
    CGContextTranslateCTM(context, 0, rect.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);
    CGContextDrawImage(context, rect, imageRef);
    CGImageRelease(imageRef);
}

残念ながら、これは中程度のサイズの画像と高いsetNeedsDisplay周波数で非常に遅いようです。 UIImageViewのフレームとclipToBoundsを使用すると、より良い結果が得られます(柔軟性は低くなります)。

36
hpique

UIImageViewについて言及したので、画像の一部を画面に表示するためにこれを行っていると思います。そして、最適化問題は常に具体的に定義する必要があります。


信頼Apple

実際、UIImageViewclipsToBoundsは、目標が画像の長方形の領域(大きすぎない)をクリップするだけの場合に、アーカイブを作成するための最速/最も簡単な方法の1つです。また、setNeedsDisplayメッセージを送信する必要はありません。

または、空のUIImageView内にUIViewを配置して、コンテナービューでクリッピングを設定することもできます。この手法を使用すると、2Dでtransformプロパティを設定することにより、画像を自由に変換できます(スケーリング、回転、変換)。

3D変換が必要な場合でも、CALayerプロパティをmasksToBoundsプロパティと共に使用できますが、CALayerを使用しても、通常はそれほど大きなパフォーマンスの向上はほとんど得られません。

とにかく、最適化のためにそれらを適切に使用するには、すべての低レベルの詳細を知る必要があります。


なぜそれが最速の方法の1つなのですか?

UIViewは、OpenGLの上に実装されているCALayerの上にある薄いレイヤーであり、[〜#〜] gpuへの実質的に直接的なインターフェースです。 [〜#〜]。これは、UIKitがGPUによって高速化されていることを意味します。

したがって、それらを適切に(つまり、設計された制限内で)使用すると、単純なOpenGL実装と同様に機能します。数枚の画像を使用して表示する場合、基礎となるOpenGL(つまりGPUアクセラレーションを意味する)の完全なアクセラレーションを取得できるため、UIView実装で許容できるパフォーマンスが得られます。

とにかく、ゲームアプリのようにピクセルシェーダーを微調整した数百のアニメーションスプライトに極端な最適化が必要な場合は、CALayerに多くの機能がないため、直接OpenGLを使用する必要があります。下位レベルでの最適化のオプション。とにかく、少なくともUIの最適化に関しては、Appleよりも優れていることは信じられないほど難しいです。


メソッドがUIImageViewより遅いのはなぜですか?

知っておくべきことは、GPUアクセラレーションに関するすべてです。最近のすべてのコンピューターでは、高速グラフィックスパフォーマンスはGPUでのみ達成されます。次に、ポイントは、使用しているメソッドがGPUの上に実装されているかどうかです。

IMO、CGImage描画メソッドはGPUで実装されていません。私はこれについてAppleのドキュメントで言及しているのを読んだと思いますが、どこで覚えているかわかりません。だから私はこれについてはわかりません。とにかく、CGImageはCPUに実装されていると思います。

  1. そのAPIは、ビットマップ編集インターフェイスやテキスト描画など、CPU用に設計されたように見えます。それらはGPUインターフェースにうまく適合しません。
  2. ビットマップコンテキストインターフェイスは、直接メモリアクセスを可能にします。つまり、そのバックエンドストレージはCPUメモリに配置されます。ユニファイドメモリアーキテクチャ(およびMetal APIを使用した場合)では多少異なる場合がありますが、いずれにしても、CGImageの初期設計の意図はCPUを対象とする必要があります。
  3. 最近リリースされた他の多くのApple APIはGPUアクセラレーションに明示的に言及しています。つまり、それらの古いAPIはそうではありませんでした。特別な言及がない場合、通常はデフォルトでCPUで行われます。

だからそれはCPUで行われているようです。 CPUで実行されるグラフィックス操作は、GPUで実行するよりもかなり低速です。

画像を単にクリッピングして画像レイヤーを合成することは、GPUにとって(CPUと比較して)非常にシンプルで安価な操作であり、UIKit全体がOpenGLの上に実装されているため、UIKitライブラリがこれを利用することを期待できます。


制限について

最適化はミクロ管理に関する一種の作業であるため、特定の数と小さな事実が非常に重要です。 ミディアムサイズは何ですか? iOS上のOpenGLは通常、最大テクスチャサイズを1024x1024ピクセルに制限します(最近のリリースではそれより大きくなる可能性があります)。画像がこれよりも大きい場合は、機能しないか、パフォーマンスが大幅に低下します(UIImageViewは制限内の画像用に最適化されていると思います)。

クリッピングで巨大な画像を表示する必要がある場合は、CATiledLayerのような別の最適化を使用する必要がありますが、それはまったく別の話です。

また、OpenGLのすべての詳細を知りたい場合を除き、OpenGLを使用しないでください。低レベルのグラフィックスと少なくとも100倍以上のコードについて完全に理解する必要があります。


いくつかの未来について

それは起こりそうにありませんが、CGImageのもの(または他のもの)はCPUだけでスタックする必要はありません。使用しているAPIの基本技術を確認することを忘れないでください。それでも、GPUのものはCPUとはまったく異なるモンスターであり、APIの担当者は通常、明示的かつ明確にそれらについて言及します。

78
Eonil

UIImageViewの画像だけでなく、そのUIImage内に表示する左上オフセットも設定できれば、最終的には高速になり、Spriteアトラスからの画像作成が大幅に減ります。多分これは可能です。

一方、私は自分のアプリで使用するユーティリティクラスでこれらの便利な関数を作成しました。別のUIImageの一部からUIImageを作成します。指定する標準のUIImageOrientation値を使用して、回転、スケーリング、および反転するオプションがあります。

私のアプリは初期化中に多くのUIImageを作成しますが、これには必ず時間がかかります。ただし、一部の画像は、特定のタブが選択されるまで必要ありません。より速いロードの外観を与えるために、起動時に生成される別のスレッドでそれらを作成し、そのタブが選択されている場合は完了するまで待つだけです。

+ (UIImage*)imageByCropping:(UIImage *)imageToCrop toRect:(CGRect)aperture {
    return [ChordCalcController imageByCropping:imageToCrop toRect:aperture withOrientation:UIImageOrientationUp];
}

// Draw a full image into a crop-sized area and offset to produce a cropped, rotated image
+ (UIImage*)imageByCropping:(UIImage *)imageToCrop toRect:(CGRect)aperture withOrientation:(UIImageOrientation)orientation {

            // convert y coordinate to Origin bottom-left
    CGFloat orgY = aperture.Origin.y + aperture.size.height - imageToCrop.size.height,
            orgX = -aperture.Origin.x,
            scaleX = 1.0,
            scaleY = 1.0,
            rot = 0.0;
    CGSize size;

    switch (orientation) {
        case UIImageOrientationRight:
        case UIImageOrientationRightMirrored:
        case UIImageOrientationLeft:
        case UIImageOrientationLeftMirrored:
            size = CGSizeMake(aperture.size.height, aperture.size.width);
            break;
        case UIImageOrientationDown:
        case UIImageOrientationDownMirrored:
        case UIImageOrientationUp:
        case UIImageOrientationUpMirrored:
            size = aperture.size;
            break;
        default:
            assert(NO);
            return nil;
    }


    switch (orientation) {
        case UIImageOrientationRight:
            rot = 1.0 * M_PI / 2.0;
            orgY -= aperture.size.height;
            break;
        case UIImageOrientationRightMirrored:
            rot = 1.0 * M_PI / 2.0;
            scaleY = -1.0;
            break;
        case UIImageOrientationDown:
            scaleX = scaleY = -1.0;
            orgX -= aperture.size.width;
            orgY -= aperture.size.height;
            break;
        case UIImageOrientationDownMirrored:
            orgY -= aperture.size.height;
            scaleY = -1.0;
            break;
        case UIImageOrientationLeft:
            rot = 3.0 * M_PI / 2.0;
            orgX -= aperture.size.height;
            break;
        case UIImageOrientationLeftMirrored:
            rot = 3.0 * M_PI / 2.0;
            orgY -= aperture.size.height;
            orgX -= aperture.size.width;
            scaleY = -1.0;
            break;
        case UIImageOrientationUp:
            break;
        case UIImageOrientationUpMirrored:
            orgX -= aperture.size.width;
            scaleX = -1.0;
            break;
    }

    // set the draw rect to pan the image to the right spot
    CGRect drawRect = CGRectMake(orgX, orgY, imageToCrop.size.width, imageToCrop.size.height);

    // create a context for the new image
    UIGraphicsBeginImageContextWithOptions(size, NO, imageToCrop.scale);
    CGContextRef gc = UIGraphicsGetCurrentContext();

    // apply rotation and scaling
    CGContextRotateCTM(gc, rot);
    CGContextScaleCTM(gc, scaleX, scaleY);

    // draw the image to our clipped context using the offset rect
    CGContextDrawImage(gc, drawRect, imageToCrop.CGImage);

    // pull the image from our cropped context
    UIImage *cropped = UIGraphicsGetImageFromCurrentImageContext();

    // pop the context to get back to the default
    UIGraphicsEndImageContext();

    // Note: this is autoreleased
    return cropped;
}
5
Scott Lahteine

大きな画像をIImageView内に移動する非常に簡単な方法は次のとおりです。

ある画像の4つの状態を上下に表すサイズ(100、400)の画像があるとします。 offsetY = 100の正方形の2番目の画像IImageView(100、100)を表示したいとします。解決策は次のとおりです。

UIImageView *iView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
CGRect contentFrame = CGRectMake(0, 0.25, 1, 0.25);
iView.layer.contentsRect = contentFrame;
iView.image = [UIImage imageNamed:@"NAME"];

ここでcontentFrameは、実際のIImageサイズに対して相対的に正規化されたフレームです。したがって、「0」は左の境界線から画像の可視部分を開始することを意味し、「0.25」は垂直オフセット100があることを意味し、「1」は画像の全幅を表示することを意味し、最後に「0.25」は画像の高さの1/4の部分だけを表示したいこと。

したがって、ローカル画像座標では、次のフレームを表示します

CGRect visibleAbsoluteFrame = CGRectMake(0*100, 0.25*400, 1*100, 0.25*400)
or CGRectMake(0, 100, 100, 100);
3
malex

新しいイメージを作成するのではなく(メモリを割り当てるためにコストがかかります)、CGContextClipToRectを使用するのはどうですか?

1
David Dunham

最も速い方法は、イメージマスクを使用することです。つまり、マスクするイメージと同じサイズであるが、レンダリング時にイメージのどの部分をマスクするかを示す特定のピクセルパターンを持つイメージ->

// maskImage is used to block off the portion that you do not want rendered
// note that rect is not actually used because the image mask defines the rect that is rendered
-(void) drawRect:(CGRect)rect maskImage:(UIImage*)maskImage {

    UIGraphicsBeginImageContext(image_.size);
    [maskImage drawInRect:image_.bounds];
    maskImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    CGImageRef maskRef = maskImage.CGImage;
    CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(maskRef),
                                    CGImageGetHeight(maskRef),
                                    CGImageGetBitsPerComponent(maskRef),
                                    CGImageGetBitsPerPixel(maskRef),
                                    CGImageGetBytesPerRow(maskRef),
                                    CGImageGetDataProvider(maskRef), NULL, false);

    CGImageRef maskedImageRef = CGImageCreateWithMask([image_ CGImage], mask);
    image_ = [UIImage imageWithCGImage:maskedImageRef scale:1.0f orientation:image_.imageOrientation];

    CGImageRelease(mask);
    CGImageRelease(maskedImageRef); 
}
0
Adam Freeman