UIImage
とCGRect
が与えられた場合、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
を使用すると、より良い結果が得られます(柔軟性は低くなります)。
UIImageView
について言及したので、画像の一部を画面に表示するためにこれを行っていると思います。そして、最適化問題は常に具体的に定義する必要があります。
実際、UIImageView
とclipsToBounds
は、目標が画像の長方形の領域(大きすぎない)をクリップするだけの場合に、アーカイブを作成するための最速/最も簡単な方法の1つです。また、setNeedsDisplay
メッセージを送信する必要はありません。
または、空のUIImageView
内にUIView
を配置して、コンテナービューでクリッピングを設定することもできます。この手法を使用すると、2Dでtransform
プロパティを設定することにより、画像を自由に変換できます(スケーリング、回転、変換)。
3D変換が必要な場合でも、CALayer
プロパティをmasksToBounds
プロパティと共に使用できますが、CALayer
を使用しても、通常はそれほど大きなパフォーマンスの向上はほとんど得られません。
とにかく、最適化のためにそれらを適切に使用するには、すべての低レベルの詳細を知る必要があります。
UIView
は、OpenGLの上に実装されているCALayer
の上にある薄いレイヤーであり、[〜#〜] gpuへの実質的に直接的なインターフェースです。 [〜#〜]。これは、UIKitがGPUによって高速化されていることを意味します。
したがって、それらを適切に(つまり、設計された制限内で)使用すると、単純なOpenGL
実装と同様に機能します。数枚の画像を使用して表示する場合、基礎となるOpenGL(つまりGPUアクセラレーションを意味する)の完全なアクセラレーションを取得できるため、UIView
実装で許容できるパフォーマンスが得られます。
とにかく、ゲームアプリのようにピクセルシェーダーを微調整した数百のアニメーションスプライトに極端な最適化が必要な場合は、CALayer
に多くの機能がないため、直接OpenGLを使用する必要があります。下位レベルでの最適化のオプション。とにかく、少なくともUIの最適化に関しては、Appleよりも優れていることは信じられないほど難しいです。
UIImageView
より遅いのはなぜですか?知っておくべきことは、GPUアクセラレーションに関するすべてです。最近のすべてのコンピューターでは、高速グラフィックスパフォーマンスはGPUでのみ達成されます。次に、ポイントは、使用しているメソッドがGPUの上に実装されているかどうかです。
IMO、CGImage
描画メソッドはGPUで実装されていません。私はこれについてAppleのドキュメントで言及しているのを読んだと思いますが、どこで覚えているかわかりません。だから私はこれについてはわかりません。とにかく、CGImage
はCPUに実装されていると思います。
CGImage
の初期設計の意図はCPUを対象とする必要があります。だからそれはCPUで行われているようです。 CPUで実行されるグラフィックス操作は、GPUで実行するよりもかなり低速です。
画像を単にクリッピングして画像レイヤーを合成することは、GPUにとって(CPUと比較して)非常にシンプルで安価な操作であり、UIKit全体がOpenGLの上に実装されているため、UIKitライブラリがこれを利用することを期待できます。
最適化はミクロ管理に関する一種の作業であるため、特定の数と小さな事実が非常に重要です。 ミディアムサイズは何ですか? iOS上のOpenGLは通常、最大テクスチャサイズを1024x1024ピクセルに制限します(最近のリリースではそれより大きくなる可能性があります)。画像がこれよりも大きい場合は、機能しないか、パフォーマンスが大幅に低下します(UIImageViewは制限内の画像用に最適化されていると思います)。
クリッピングで巨大な画像を表示する必要がある場合は、CATiledLayer
のような別の最適化を使用する必要がありますが、それはまったく別の話です。
また、OpenGLのすべての詳細を知りたい場合を除き、OpenGLを使用しないでください。低レベルのグラフィックスと少なくとも100倍以上のコードについて完全に理解する必要があります。
それは起こりそうにありませんが、CGImage
のもの(または他のもの)はCPUだけでスタックする必要はありません。使用しているAPIの基本技術を確認することを忘れないでください。それでも、GPUのものはCPUとはまったく異なるモンスターであり、APIの担当者は通常、明示的かつ明確にそれらについて言及します。
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;
}
大きな画像を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);
新しいイメージを作成するのではなく(メモリを割り当てるためにコストがかかります)、CGContextClipToRect
を使用するのはどうですか?
最も速い方法は、イメージマスクを使用することです。つまり、マスクするイメージと同じサイズであるが、レンダリング時にイメージのどの部分をマスクするかを示す特定のピクセルパターンを持つイメージ->
// 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);
}