在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的东西
其实, UIImageView
与clipsToBounds
是最快/最简单的方法来存档您的目标,如果你的目标是剪切图像的矩形区域(不是太大)。 另外,您不需要发送setNeedsDisplay
消息。
或者你可以尝试把UIImageView
放在一个空的UIView
并在容器视图中设置剪切。 使用这种技术,您可以通过在2D中设置transform
属性(缩放,旋转,平移)来自由地变换图像。
如果你需要3D转换,你仍然可以使用CALayer
与masksToBounds
属性,但使用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中实现,因为,
- 它的API看起来像是为CPUdevise的,比如位图编辑界面和文本绘图。 他们不太适合GPU界面。
- 位图上下文接口允许直接存储器访问。 这意味着后端存储位于CPU内存中。 在统一的内存架构上(也可以用Metal API)也许有所不同,但无论如何,
CGImage
初始devise意图应该是CPU。 - 许多最近发布的其他苹果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); }
- iOS:使用UIView的“drawRect:”与其图层的delagate“drawLayer:inContext:”
- 在使用UITableViewAutomaticDimension更新UITableViewCell之后进行粗糙的滚动