CGContextDrawImage 的优化替代方案

Posted

技术标签:

【中文标题】CGContextDrawImage 的优化替代方案【英文标题】:Optimized alternative to CGContextDrawImage 【发布时间】:2013-07-22 12:39:50 【问题描述】:

我目前在 OSX 上使用 CoreGraphics 进行了大量工作。

我在我的代码上运行了 Time Profiler,发现最大的问题在于 CGContextDrawImage。它是每秒被调用多次的循环的一部分。

我没有任何方法可以优化此代码本身(因为它在 Apple 库中) - 但我想知道是否有更快的替代方案或提高速度的方法。

我在一些混合模式代码之后使用 CGContextDraw 图像,例如:CGContextSetBlendMode(context, kCGBlendModeDifference);,因此替代实现需要能够支持混合。

时间分析器结果:

3658.0ms   15.0%    0.0               CGContextDrawImage
3658.0ms   15.0%    0.0                ripc_DrawImage
3539.0ms   14.5%    0.0                 ripc_AcquireImage
3539.0ms   14.5%    0.0                  CGSImageDataLock
3539.0ms   14.5%    1.0                   img_data_lock
3465.0ms   14.2%    0.0                    img_interpolate_read
2308.0ms    9.4%    7.0                     resample_band
1932.0ms    7.9%    1932.0                   resample_byte_h_3cpp_vector
369.0ms     1.5%    369.0                    resample_byte_v_Ncpp_vector
1157.0ms    4.7%    2.0                     img_decode_read
1150.0ms    4.7%    8.0                      decode_data
863.0ms     3.5%    863.0                     decode_swap
267.0ms     1.0%    267.0                     decode_byte_8bpc_3

更新:

实际来源大致如下:

/////////////////////////////////////////////////////////////////////////////////////////
- (CGImageRef)createBlendedImage:(CGImageRef)image
                     secondImage:(CGImageRef)secondImage
                       blendMode:(CGBlendMode)blendMode

    // Get the image width and height
    size_t width = CGImageGetWidth(image);
    size_t height = CGImageGetHeight(image);

    // Set the frame
    CGRect frame = CGRectMake(0, 0, width, height);

    // Create context with alpha channel
    CGContextRef context = CGBitmapContextCreate(NULL,
                                                 width,
                                                 height,
                                                 CGImageGetBitsPerComponent(image),
                                                 CGImageGetBytesPerRow(image),
                                                 CGImageGetColorSpace(image),
                                                 kCGImageAlphaPremultipliedLast);

    if (!context) 
        return nil;
    

    // Draw the image inside the context
    CGContextSetBlendMode(context, kCGBlendModeCopy);
    CGContextDrawImage(context, frame, image);

    // Set the blend mode and draw the second image
    CGContextSetBlendMode(context, blendMode);
    CGContextDrawImage(context, frame, secondImage);

    // Get the masked image from the context
    CGImageRef blendedImage = CGBitmapContextCreateImage(context);
    CGContextRelease(context);

    return blendedImage;


/////////////////////////////////////////////////////////////////////////////////////////
- (CGImageRef)createImageTick

    // `self.image` and `self.previousImage` are two instance properties (CGImageRefs)

    // Create blended image (stage one)
    CGImageRef stageOne = [self createBlendedImage:self.image
                                       secondImage:self.previousImage
                                         blendMode:kCGBlendModeXOR];

    // Create blended image (stage two) if stage one image is 50% red
    CGImageRef stageTwo = nil;

    if ([self isImageRed:stageOne]) 
        stageTwo = [self createBlendedImage:self.image
                                secondImage:stageOne
                                  blendMode:kCGBlendModeSourceAtop];
    

    // Release intermediate image
    CGImageRelease(stageOne);

    return stageTwo;

【问题讨论】:

很高兴您在尝试过早优化之前实际分析了代码。但是,如果您的代码运行缓慢,我不会将其归咎于库 - 我相信您需要重新设计代码(通过更改算法来降低复杂性,或执行一些其他类型的“聪明”改进)让它运行得更快。 看起来在解码和字节交换图像数据上花费了一些时间。如果可能,您应该尝试使用 iphone 优化/压缩的 PNG。 花费大量时间重新采样图像。是否可以提前调整图像大小,以便在绘制时不必这样做?您是在同一尺寸下多次绘制相同的图像,还是在绘制不同的图像?你在制作动画吗? @JeremyRoman 等人:非常感谢您的 cmets。我在每个循环中绘制相同的图像几次,使用不同的过滤器在不同的上下文中,并与新图像组合。重采样是否包括从 RGB 切换到 RGBA?我可以尝试什么来加快或消除重新采样? 这将有助于更多地了解您的代码在高层次上的实际作用。例如你在画动画吗?如果是这样,您是否考虑过 Core Animation 是否对您的用例更有效? 【参考方案1】:

@JeremyRoman 等人:非常感谢您的 cmets。我在每个循环中绘制相同的图像几次,使用不同的过滤器在不同的上下文中,并与新图像组合。重采样是否包括从 RGB 切换到 RGBA?我可以尝试什么来加快或消除重采样? ——克里斯·诺莱特

这就是 Core Image 的用途。有关详细信息,请参阅Core Image Programming Guide。 CGContext 旨在将最终图像渲染到屏幕上,这听起来并不是您创建每张图像的目标。

【讨论】:

我实际上会将这些图像中的许多写入磁盘(快速连续写入虚拟 RAM 驱动器)。最好把图像处理移到Core Image,然后用createCGImage:fromRect:加上CGImageDestinationRef来写,还是要一直坚持用CG? 参见 WWDC 2013 会议 509(核心图像效果和技术)。当您可以将数据尽可能长时间地保存在 GPU 上时,Core Image 处于最佳状态。将数据移动到 GPU 并返回是昂贵的。因此,理想情况下,您希望将数据移动到 GPU,应用大量过滤器,然后将其直接渲染到屏幕(更快)或返回到 CPU 内存(更慢)。真正的性能关键是保持管道满载,因此您需要了解数据如何在 CPU 和 GPU 之间移动。另请参阅developer.apple.com/library/mac/#documentation/GraphicsImaging/… 也值得一试github.com/BradLarson/GPUImage:比 CIImage 更快,并且更容易(更快)与 CGImage 进行转换。

以上是关于CGContextDrawImage 的优化替代方案的主要内容,如果未能解决你的问题,请参考以下文章

CGContextDrawImage 画大图很模糊

Swift 3 和 CGContextDrawImage

-[UIImage drawInRect:] / CGContextDrawImage() 不释放内存?

CGContextDrawImage 崩溃

CGContextDrawImage 在传递 UIImage.CGImage 时将图像倒置

与 CGContextDrawImage 相比,为啥 UIImageView 如此占用内存