如何在iOS中发布CGImageRef

时间:2012-08-27 18:50:37

标签: ios memory-management memory-leaks cgimageref

我正在编写此方法来计算图像的平均R,G,B值。以下方法将UIImage作为输入,并返回包含输入图像的R,G,B值的数组。我有一个问题:如何/我在哪里正确发布CGImageRef?

-(NSArray *)getAverageRGBValuesFromImage:(UIImage *)image
{
    CGImageRef rawImageRef = [image CGImage];

    //This function returns the raw pixel values
    const UInt8 *rawPixelData = CFDataGetBytePtr(CGDataProviderCopyData(CGImageGetDataProvider(rawImageRef)));

    NSUInteger imageHeight = CGImageGetHeight(rawImageRef);
    NSUInteger imageWidth = CGImageGetWidth(rawImageRef);

    //Here I sort the R,G,B, values and get the average over the whole image
    int i = 0;
    unsigned int red = 0;
    unsigned int green = 0;
    unsigned int blue = 0;

    for (int column = 0; column< imageWidth; column++)
    {
        int r_temp = 0;
        int g_temp = 0;
        int b_temp = 0;

        for (int row = 0; row < imageHeight; row++) {
            i = (row * imageWidth + column)*4;
            r_temp += (unsigned int)rawPixelData[i];
            g_temp += (unsigned int)rawPixelData[i+1];
            b_temp += (unsigned int)rawPixelData[i+2];

        }

        red += r_temp;
        green += g_temp;
        blue += b_temp;

    }

    NSNumber *averageRed = [NSNumber numberWithFloat:(1.0*red)/(imageHeight*imageWidth)];
    NSNumber *averageGreen = [NSNumber numberWithFloat:(1.0*green)/(imageHeight*imageWidth)];
    NSNumber *averageBlue = [NSNumber numberWithFloat:(1.0*blue)/(imageHeight*imageWidth)];


    //Then I store the result in an array
    NSArray *result = [NSArray arrayWithObjects:averageRed,averageGreen,averageBlue, nil];


    return result;
}

我尝试了两件事: 选项1: 我保持原样,但经过几个周期(5+)后程序崩溃,我得到“低内存警告错误”

选项2: 我添加一行 CGImageRelease(rawImageRef) 在方法返回之前。现在它在第二个周期后崩溃,我得到了传递给方法的UIImage的EXC_BAD_ACCESS错误。当我尝试在Xcode中分析(而不是RUN)时,我会在此行收到以下警告 “调用者不在此时拥有的对象的引用计数的错误减少”

我应该在何处以及如何发布CGImageRef?

谢谢!

6 个答案:

答案 0 :(得分:68)

正如其他人所说,您的内存问题是由复制的数据引起的。但这是另一个想法:使用Core Graphics的优化像素插值来计算平均值。

  1. 创建1x1位图上下文。
  2. 将插值质量设置为中等(见下文)。
  3. 将缩小的图像缩小到这一个像素。
  4. 从上下文的缓冲区中读取RGB值。
  5. (当然,请释放上下文。)
  6. 这可能会带来更好的性能,因为Core Graphics经过高度优化,甚至可以使用GPU进行缩减。

    测试表明,中等质量似乎通过取颜色值的平均值来插值像素。这就是我们想要的。

    值得一试,至少。

    编辑:好的,这个想法似乎太有趣了,不要尝试。所以here's an example project显示出差异。下面的测量是使用包含的512x512测试图像进​​行的,但您可以根据需要更改图像。

    通过迭代图像数据中的所有像素来计算平均值大约需要12.2 ms。绘制到一个像素的方法需要3毫秒,所以它大约快4倍。使用kCGInterpolationQualityMedium时,它似乎产生相同的结果。

    我认为巨大的性能提升是由于Quartz注意到它不必完全解压缩JPEG,而是只能使用DCT的低频部分。当编写尺寸小于0.5的JPEG压缩像素时,这是一个有趣的优化策略。但我只是在这里猜测。

    有趣的是,在使用您的方法时,70%的时间花在CGDataProviderCopyData上,而在像素数据遍历中只花费30%。这暗示了花在JPEG解压缩上的大量时间。

    Pixel Iterating Screenshot Draw-To-One-Pixel Screenshot

答案 1 :(得分:8)

您不拥有CGImageRef rawImageRef,因为您是使用[image CGImage]获取的。所以你不需要发布它。

但是,您拥有rawPixelData,因为您是使用CGDataProviderCopyData获得的,并且必须将其发布。

  

<强> CGDataProviderCopyData

     

返回值:   包含提供程序数据副本的新数据对象。您有责任释放此对象。

答案 2 :(得分:4)

我相信您的问题出在本声明中:

const UInt8 *rawPixelData = CFDataGetBytePtr(CGDataProviderCopyData(CGImageGetDataProvider(rawImageRef)));

您应该释放CGDataProviderCopyData的返回值。

答案 3 :(得分:4)

您的mergedColor适用于从文件加载的图像,但不适用于相机捕获的图像。因为从捕获的样本缓冲区创建的上下文中的CGBitmapContextGetData()不会返回位图。我将您的代码更改为以下内容。它适用于任何图像,它与您的代码一样快。

- (UIColor *)mergedColor
{
     CGImageRef rawImageRef = [self CGImage];

    // scale image to an one pixel image

    uint8_t  bitmapData[4];
    int bitmapByteCount;
    int bitmapBytesPerRow;
    int width = 1;
    int height = 1;

    bitmapBytesPerRow = (width * 4);
    bitmapByteCount = (bitmapBytesPerRow * height);
    memset(bitmapData, 0, bitmapByteCount);
    CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate (bitmapData,width,height,8,bitmapBytesPerRow,
             colorspace,kCGBitmapByteOrder32Little|kCGImageAlphaPremultipliedFirst);
    CGColorSpaceRelease(colorspace);
    CGContextSetBlendMode(context, kCGBlendModeCopy);
    CGContextSetInterpolationQuality(context, kCGInterpolationMedium);
    CGContextDrawImage(context, CGRectMake(0, 0, width, height), rawImageRef);
    CGContextRelease(context);
    return [UIColor colorWithRed:bitmapData[2] / 255.0f
                    green:bitmapData[1] / 255.0f
                             blue:bitmapData[0] / 255.0f
                            alpha:1];
}

答案 4 :(得分:3)

CFDataRef abgrData = CGDataProviderCopyData(CGImageGetDataProvider(rawImageRef));

const UInt8 *rawPixelData = CFDataGetBytePtr(abgrData);

...

CFRelease(abgrData);

答案 5 :(得分:1)

之前曾问过另一个类似的问题,并且有一些非常有用的答案可以帮助您更好地理解:

CGImageRef Memory leak