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

hpi*_*que 36 iphone uiimage drawrect quartz-2d ios

给定a UIImage和a 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);
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,对于中等大小的图像和高setNeedsDisplay频率,这似乎非常慢.使用UIImageView框架并clipToBounds产生更好的结果(灵活性较低).

Eon*_*nil 78

我猜你这样做是为了在屏幕上显示部分图像,因为你提到过UIImageView.优化问题总是需要专门定义.


信任Apple的常规UI东西

实际上,UIImageViewclipsToBounds是存档你的目标,如果你的目标只是裁剪图像(不是太大)的矩形区域中最快/最简单的方法之一.此外,您不需要发送setNeedsDisplay消息.

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

如果您需要3D变换,你仍然可以使用CALayermasksToBounds财产,但使用CALayer会给你非常少的额外性能通常不可观.

无论如何,您需要知道所有低级细节才能正确使用它们进行优化.


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

UIView只是一个薄层,CALayer其顶部是在OpenGL之上实现的,OpenGL几乎是GPU的直接接口.这意味着UIKit正在被GPU加速.

因此,如果您正确使用它们(我的意思是,在设计限制内),它将执行与简单OpenGL实现一样好.如果您只使用几个图像来显示,那么您将获得可接受的性能,UIView因为它可以完全加速底层OpenGL(这意味着GPU加速).

无论如何,如果您需要使用像游戏应用程序中那样精细调整的像素着色器对数百个动画精灵进行极端优化,您应该直接使用OpenGL,因为CALayer在较低级别缺少许多优化选项.无论如何,至少为了优化UI的东西,要比Apple好一点是难以置信的.


为什么你的方法比慢UIImageView

你应该知道的是GPU加速.在所有最近的计算机中,只有GPU才能实现快速的图形性能.然后,重点是您使用的方法是否在GPU之上实现.

IMO,CGImage绘图方法没有用GPU实现.我想我在Apple的文档中提到了这一点,但我不记得在哪里.所以我不确定这一点.无论如何,我相信CGImage是在CPU中实现的,因为,

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

所以它似乎是在CPU中完成的.在CPU中完成的图形操作比在GPU中慢很多.

简单地剪切图像和合成图像层对于GPU来说是非常简单和便宜的操作(与CPU相比),因此您可以期望UIKit库将利用它,因为整个UIKit是在OpenGL之上实现的.


关于限制

因为优化是一种关于微观管理的工作,具体数字和小事实非常重要.什么是中等大小?iOS上的OpenGL通常将最大纹理大小限制为1024x1024像素(在最近的版本中可能更大).如果你的图像比这大,它将无法工作,或性能会大大降低(我认为UIImageView针对极限范围内的图像进行了优化).

如果你需要使用剪辑显示巨大的图像,你必须使用另一种优化CATiledLayer,这是一个完全不同的故事.

除非你想知道OpenGL的每个细节,否则不要去OpenGL.它需要充分了解低级图形,至少需要100倍的代码.


关于一些未来

虽然不太可能发生,但是CGImage东西(或其他任何东西)不需要仅仅停留在CPU中.不要忘记检查您正在使用的API的基本技术.尽管如此,GPU的东西与CPU有很大不同,然后API人员通常会明确地提及它们.


Sco*_*ine 5

如果您不仅可以设置UIImageView的图像,而且还可以设置在UIImage中显示的左上角偏移,那么它最终会更快,从精灵地图集创建的图像少得多.也许这是可能的.

同时,我在我的应用程序中使用的实用程序类中创建了这些有用的函数.它从另一个UIImage的一部分创建一个UIImage,其中包含使用标准UIImageOrientation值指定的旋转,缩放和翻转选项.

我的应用程序在初始化期间创建了大量的UIImages,这必然需要时间.但是在选择某个选项卡之前不需要某些图像.为了给出更快加载的外观,我可以在启动时生成的单独线程中创建它们,然后等到完成后如果选中该选项卡就完成了.

+ (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;
}
Run Code Online (Sandbox Code Playgroud)