绘制大尺寸UIImage到CGContext后,CGContextDrawImage非常缓慢。

33

似乎CGContextDrawImage(CGContextRef, CGRect, CGImageRef)在绘制使用CoreGraphics创建的CGImage(例如,使用CGBitmapContextCreateImage)时的性能要比绘制UIImage支持的CGImage差得多。参见以下测试方法:

-(void)showStrangePerformanceOfCGContextDrawImage
{
    ///Setup : Load an image and start a context:
    UIImage *theImage = [UIImage imageNamed:@"reallyBigImage.png"];
    UIGraphicsBeginImageContext(theImage.size);
    CGContextRef ctxt = UIGraphicsGetCurrentContext();    
    CGRect imgRec = CGRectMake(0, 0, theImage.size.width, theImage.size.height);


    ///Why is this SO MUCH faster...
    NSDate * startingTimeForUIImageDrawing = [NSDate date];
    CGContextDrawImage(ctxt, imgRec, theImage.CGImage);  //Draw existing image into context Using the UIImage backing    
    NSLog(@"Time was %f", [[NSDate date] timeIntervalSinceDate:startingTimeForUIImageDrawing]);

    /// Create a new image from the context to use this time in CGContextDrawImage:
    CGImageRef theImageConverted = CGBitmapContextCreateImage(ctxt);

    ///This is WAY slower but why??  Using a pure CGImageRef (ass opposed to one behind a UIImage) seems like it should be faster but AT LEAST it should be the same speed!?
    NSDate * startingTimeForNakedGImageDrawing = [NSDate date];
    CGContextDrawImage(ctxt, imgRec, theImageConverted);
    NSLog(@"Time was %f", [[NSDate date] timeIntervalSinceDate:startingTimeForNakedGImageDrawing]);


}
所以我想问的是,#1这可能是什么原因导致的?#2是否有其他方法可以创建CGImageRef并且更快? 我知道我可以先将一切转换为UIImages,但那太丑陋了。我已经有了CGContextRef sitting there。
更新:当绘制小图像时,这似乎并不一定正确?这可能是一个线索——当使用大图像(即全尺寸相机照片)时,这个问题会被放大。640x480看起来在执行时间方面与任何方法都很相似。
更新2:好吧,我发现了一个新的东西...实际上并不是CGImage的支撑变化了性能。我可以颠倒两个步骤的顺序,使UIImage方法表现缓慢,而“裸”的CGImage将会超级快。似乎无论你第二次执行哪个操作,它都会遭受可怕的性能损失。除非我通过在由CGBitmapContextCreateImage创建的图像上调用CGImageRelease来释放内存。然后UIImage支持的方法随后将快速执行。反之则不成立。拥挤的内存不应该像这样影响性能,对吗?
更新3:说得太早了。以前的更新对于2048x2048大小的图像是正确的,但是升级到1936x2592(相机大小)时,裸的CGImage方法仍然要慢得多,无论操作顺序或内存情况如何。也许有一些CG内部限制,使16MB图像有效,而21MB图像则不能有效处理。 以相机大小绘制的速度比2048x2048慢20倍。不知何故,UIImage提供其CGImage数据比纯CGImage对象快得多。
更新4:我认为这可能与某些内存缓存事物有关,但是结果与使用非缓存[UIImage imageWithContentsOfFile]加载UIImage的结果相同,就像使用[UIImage imageNamed]一样。
更新5(第二天):在昨天创建了更多未回答的问题之后,今天我有了一些确凿的东西。 我可以确定以下内容:
- 在UIImage背后的CGImages不使用alpha。 (kCGImageAlphaNoneSkipLast)。我认为它们可能更快地被绘制,因为我的上下文正在使用alpha。所以我将上下文更改为使用kCGImageAlphaNoneSkipLast。这使得绘图速度大为提高,除非: - 首先使用UIImage在CGContextRef中进行绘制会使所有随后的图像绘制变慢。
我通过以下方式证明了这一点:
1)首先创建一个无alpha上下文(1936x2592);
2)填充它的颜色为随机着色的2x2方块;
3)将CGImage完全绘制到该上下文中,速度很快(0.17秒);
4)重复实验,但使用绘制CGImage支持UIImage的上下文来填充上下文。随后全部帧图像绘制都需要6秒以上时间。
以某种方式,在具有(大型)UIImage的上下文中进行绘图会显着减慢对该上下文的所有随后绘图。

你在哪个设备上测试它?2k x 2k像素的图像需要约16Mb的内存,而iPhone 3G只有30Mb的可用内存供应用程序使用。 - Nickolay Olshevsky
这一切都是在一个沙盒测试应用程序中的 iPhone 4 上完成的... 我绝对没有收到任何内存警告。 - Typewriter
2个回答

46

经过大量的实验,我认为我找到了处理这种情况最快的方法。之前需要6秒以上的绘图操作现在只需要0.1秒。是的,我发现了以下几点:

  • 统一使用像素格式使上下文和图像保持一致!我所提出的问题的核心就在于UIImage中的CGImages使用了与我的上下文相同的像素格式。因此会很快。而CGImages则是不同的格式,因此会变慢。通过CGImageGetAlphaInfo检查您的图像以查看它们使用的像素格式。我现在在所有地方都使用kCGImageAlphaNoneSkipLast,因为我不需要处理alpha。如果您没有在所有地方使用相同的像素格式,则在将图像绘制到上下文中时,Quartz将被迫为每个像素执行昂贵的像素转换。= 慢

  • 使用CGLayers!这可以使屏幕外绘图性能更好。其基本原理如下:1)使用CGLayerCreateWithContext从上下文创建一个CGLayer。2)在此图层的上下文(使用CGLayerGetContext获取)上进行任何绘画/设置绘画属性。从原始上下文读取任何像素或信息。3)完成后,使用CGContextDrawLayerAtPoint "印"回原始上下文中。只要牢记以下几点,就可以快速完成:

  • 1)在使用CGContextDrawLayerAtPoint将图层"印"回CGContextRef之前,释放从上下文创建的任何CGImages(即使用CGBitmapContextCreateImage创建的CGImages)。这将使绘制该图层时速度提高3-4倍。2)在所有地方保持像素格式相同!!! 3)尽快清理CG对象。内存中挂起的东西似乎会导致性能下降的奇怪情况,可能是因为与这些强引用相关的回调或检查。只是猜测,但我可以说,尽快清理内存对性能有很大帮助。


15

我曾经遇到过类似的问题。我的应用需要重新绘制一个几乎与屏幕大小相同的图片。问题在于每次都要尽可能快地绘制两个分辨率相同、既不旋转也不翻转但放大和定位在屏幕不同位置的图像。最终,我在 iPad 1 上达到了约 15-20 帧每秒,在 iPad4 上则达到了约 20-25 帧每秒。因此...希望这对某人有所帮助:

  1. 正如typewriter所说,您必须使用相同的像素格式。使用AlphaNone格式可以提高速度。但更重要的是,在我的情况下,argb32_image调用执行了许多次从ARGB到BGRA的像素转换操作。因此,对我来说最好的bitmapInfo值是(目前来看;未来苹果公司可能会在这里做出一些更改):const CGBitmabInfo g_bitmapInfo = kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast;
  2. 如果将矩形参数设置为整数(通过CGRectIntegral),可能会使CGContextDrawImage的效率更高。当缩放图像的因子接近1时,效果更为明显。
  3. 对我来说,使用层实际上会减慢事情的进展。可能是自2011年以来在某些内部调用中做了一些更改。
  4. 将上下文的插值质量设置为低于默认值(通过CGContextSetInterpolationQuality)很重要。我建议使用(IS_RETINA_DISPLAY ? kCGInterpolationNone : kCGInterpolationLow)。宏IS_RETINA_DISPLAY取自这里
  5. 在创建上下文时,请确保从CGColorSpaceCreateDeviceRGB()或类似函数中获取CGColorSpaceRef。因为有人报告说获取固定颜色空间而不是请求设备的颜色空间会出现性能问题。
  6. 从UIImageView继承视图类,然后简单地将self.image设置为从上下文创建的图像对我很有用。但是,如果您想这样做,请先阅读有关使用UIImageView的资料,因为这需要对代码逻辑进行一些更改(因为不再调用drawRect:方法)。
  7. 如果可能的话,请避免在实际绘制时缩放图像。绘制非缩放图像速度显著更快 - 不幸的是,对我来说这不是一个选择。

将插值质量设置为此答案中所示,可以使我的性能提高40倍。我只是在图像上绘制了几个填充矩形。 - f0xik
很棒的答案。我通过在创建CGContext时设置以下参数来提高绘图性能:space: CGColorSpaceCreateDeviceRGB()bitmapInfo: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValuecontext.interpolationQuality = .low - smartwolf
我认为插值只会在调用路径、曲线或矩形绘制API时发生,使其更加平滑。当在上下文中绘制CGImageRef而不进行调整大小时,这是否有帮助? - Steven
不进行调整大小?可能不行,但在我的情况下,肉眼可见的差异,所以尝试一下可能会更容易看出来。 - Abstraction

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接