在iOS中最有效的方法来绘制图像的一部分

给定一个UIImage和一个CGRect ,什么是最有效的方式(在内存和时间)来绘制图像的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 。 而优化问题总是需要专门定义。


信任苹果正式的UI的东西

其实, UIImageViewclipsToBounds是最快/最简单的方法来存档您的目标,如果你的目标是剪切图像的矩形区域(不是太大)。 另外,您不需要发送setNeedsDisplay消息。

或者你可以尝试把UIImageView放在一个空的UIView并在容器视图中设置剪切。 使用这种技术,您可以通过在2D中设置transform属性(缩放,旋转,平移)来自由地变换图像。

如果你需要3D转换,你仍然可以使用CALayermasksToBounds属性,但使用CALayer会给你很less额外的性能通常不是很大。

无论如何,你需要知道所有的低级细节,以便正确使用它们进行优化。


为什么这是最快的方法之一?

UIView只是在CALayer之上的一个薄层,它是在OpenGL的基础上实现的,这个OpenGL实际上是GPU的直接接口。 这意味着UIKit正在被GPU加速。

所以,如果你正确地使用它们(我的意思是,在devise的限制内),它将会像普通的OpenGL执行。 如果只使用几个图像来显示,那么UIView实现将会获得可接受的性能,因为它可以获得底层OpenGL (即GPU加速)的全面加速。

无论如何,如果你需要在游戏应用程序中对数百个animation精灵进行极度优化 ,那么应该直接使用OpenGL,因为CALayer在较低级别上缺less很多优化选项。 无论如何,至less对于用户界面的优化来说,要比苹果好得难以置信。


为什么你的方法比UIImageView慢?

你应该知道的是GPU加速。 在所有最近的计算机中,只有使用GPU才能实现快速的graphics性能。 那么问题就在于你使用的方法是否在GPU之上实现。

IMO, CGImage绘图方法不是用GPU实现的。 我想我在苹果的文档中提到过这个,但我不记得在哪里。 所以我不确定这个。 无论如何,我相信CGImage在CPU中实现,因为,

  1. 它的API看起来像是为CPUdevise的,比如位图编辑界面和文本绘图。 他们不太适合GPU界面。
  2. 位图上下文接口允许直接存储器访问。 这意味着后端存储位于CPU内存中。 在统一的内存架构上(也可以用Metal API)也许有所不同,但无论如何, CGImage初始devise意图应该是CPU。
  3. 许多最近发布的其他苹果API明确提到了GPU加速。 这意味着他们的旧API不是。 如果没有特别提及,通常默认在CPU中完成。

所以这似乎是在CPU中完成的。 在CPU中完成的graphics操作要比在GPU中慢很多。

简单地裁剪图像和合成图像层是非常简单和便宜的GPU操作(与CPU相比),所以您可以期待UIKit库将利用这个,因为整个UIKit是在OpenGL之上实现的。

  • 这里是关于iOS上的CoreGraphics是否使用OpenGL的另一个线索: iOS:是在OpenGL之上实现的核心graphics?

关于限制

因为优化是一种关于微观pipe理的工作,具体的数字和小的事实是非常重要的。 什么是中等大小 ? iOS上的OpenGL通常将最大纹理尺寸限制为1024×1024像素(最近发布的版本可能更大)。 如果你的图像大于这个,它将不起作用,或者性能会大大降低(我认为UIImageView是针对图像范围进行了优化的)。

如果您需要使用裁剪来显示大图像,则必须使用另一个像CATiledLayer这样的优化,这是完全不同的故事。

除非你想知道OpenGL的每个细节,否则不要去OpenGL。 它至less需要对低级graphics和100倍以上的代码有充分的了解。


关于一些未来

尽pipe不太可能发生,但CGImage东西(或其他)不需要卡在CPU中。 不要忘记检查您正在使用的API的基础技术。 尽pipe如此,GPU的玩意与CPU的怪物是截然不同的,然而API玩家通常会明确地提及它们。

如果不仅可以设置UIImageView的图像,而且可以设置在该UIImage中显示的左上angular的偏移量,它最终会更快,从精灵地图集创build的图像要less得多。 也许这是可能的。

同时,我在我的应用程序中使用的实用程序类中创build了这些有用的function。 它从另一个UIImage的一部分创buildUIImage,并使用标准的UIImageOrientation值来指定旋转,缩放和翻转选项。

我的应用程序在初始化过程中创build了很多UIImage,这一定需要时间。 但是select某个标签之前,一些图像是不需要的。 为了给出更快的加载外观,我可以在启动时产生的单独的线程中创build它们,然后等待直到完成,如果select该选项卡。

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

UIImageView中移动大图像的非常简单的方法如下。

让我们把尺寸(100,400)的图像代表一个图像的四个状态。 我们要在大小为(100,100)的方形UIImageView中显示具有偏移量Y = 100的第二张图片。 解决scheme是:

 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是相对于真实UIImage大小的规范化的框架。 所以,“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); 

而不是创build一个新的图像(这是昂贵的,因为它分配内存),如何使用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); }