iOS性能优化:获取大尺寸图片像素颜色的最快方法

11

有一些关于如何获取给定点的图像像素颜色的问题/答案。然而,所有这些答案都对于大型图像(即使是1000 x 1300这么小)都非常缓慢(100-500ms)。

大多数代码示例都会绘制到图像上下文中。当实际绘制发生时,所有这些方法都需要时间:

CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, (CGFloat)width, (CGFloat)height), cgImage)

通过在Instruments上检查,发现绘制是通过从源图像复制数据来完成的:

enter image description here

我甚至尝试了另一种获取数据的方法,希望直接访问字节本身能够更加高效。

NSInteger pointX = trunc(point.x);
NSInteger pointY = trunc(point.y);
CGImageRef cgImage = CGImageCreateWithImageInRect(self.CGImage, 
                           CGRectMake(pointX * self.scale, 
                                      pointY * self.scale, 
                                      1.0f, 
                                      1.0f));

CGDataProviderRef provider = CGImageGetDataProvider(cgImage);
CFDataRef data = CGDataProviderCopyData(provider);

CGImageRelease(cgImage);

UInt8* buffer = (UInt8*)CFDataGetBytePtr(data);

CGFloat red   = (float)buffer[0] / 255.0f;
CGFloat green = (float)buffer[1] / 255.0f;
CGFloat blue  = (float)buffer[2] / 255.0f;
CGFloat alpha = (float)buffer[3] / 255.0f;

CFRelease(data);

UIColor *pixelColor = [UIColor colorWithRed:red green:green blue:blue alpha:alpha];

return pixelColor;

这个方法在数据复制方面需要一些时间:

CFDataRef data = CGDataProviderCopyData(provider);

看起来它也是从磁盘读取数据,而不是我创建的CGImage实例:

enter image description here

现在,在一些非正式测试中,此方法的性能表现要比我想象的要好,但仍然不够快。 有人知道更快获取底层像素数据的方法吗?

4个回答

6
如果您可以通过OpenGL ES将此图像绘制到屏幕上,那么在iOS 5.0中引入的纹理缓存中,您可以获得对底层像素的极快速随机访问。它们允许直接访问存储在OpenGL ES纹理中(其中您的图像将驻留)的底层BGRA像素数据的内存,您可以几乎瞬间提取该纹理的任何像素。

我使用这个方法甚至读回大型(2048x2048)图像的原始像素数据,读取时间最慢也只需10-20毫秒即可拉下所有这些像素。同样,对单个像素的随机访问几乎不需要时间,因为您只是从字节数组中的位置进行读取。

当然,这意味着您将不得不解析并上传您特定的图像到OpenGL ES,这将涉及与Core Graphics的交互(如果通过UIImage),就像您尝试从随机PNG磁盘上读取像素数据一样,但是看起来您只需要渲染一次并多次采样该图像。如果是这样,那么在屏幕上显示某些东西时,OpenGL ES和iOS 5.0上的纹理缓存将是读回此像素数据的绝对最快的方法。

我在我的开源GPUImage框架中,使用GPUImagePicture(图像上传)和GPUImageRawData(快速原始数据访问)类来封装这些过程。如果您想了解类似的工作原理,可以查看这个框架。

4
我还没有找到一种获取绘制(在帧缓冲区中)像素的方法。我测量过的最快方法是:
  1. 在创建图像时指定kCGImageSourceShouldCache,以指示您希望缓存该图像。
  2. (可选)通过强制渲染来预缓存图像。
  3. 将图像绘制到1x1位图上下文中。
这种方法的代价是缓存的位图,其寿命可能与关联的CGImage一样长。代码看起来类似于:
  1. Create image w/ ShouldCache flag

    NSDictionary *options = @{ (id)kCGImageSourceShouldCache: @(YES) };
    CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
    CGImageRef cgimage = CGImageSourceCreateImageAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options);
    UIImage *image = [UIImage imageWithCGImage:cgimage];
    CGImageRelease(cgimage);
    
  2. Precache image

    UIGraphicsBeginImageContext(CGSizeMake(1, 1));
    [image drawAtPoint:CGPointZero];
    UIGraphicsEndImageContext();
    
  3. Draw image to a 1x1 bitmap context

    unsigned char pixelData[] = { 0, 0, 0, 0 };
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(pixelData, 1, 1, 8, 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    CGImageRef cgimage = image.CGImage;
    int imageWidth = CGImageGetWidth(cgimage);
    int imageHeight = CGImageGetHeight(cgimage);
    CGContextDrawImage(context, CGRectMake(-testPoint.x, testPoint.y - imageHeight, imageWidth, imageHeight), cgimage);
    CGColorSpaceRelease(colorSpace);
    CGContextRelease(context);
    

pixelData包含测试点处像素的R、G、B和A值。


1

一个CGImage上下文可能几乎为空,直到您尝试读取第一个像素或绘制它时才包含实际的像素数据,因此尝试加速从图像获取像素可能不会有任何效果。目前还没有可获取的内容。

您是否正在尝试从PNG文件中读取像素?您可以尝试直接访问文件并mmap它,并自己解码PNG格式。从存储中提取数据仍将需要一些时间。


图像已经绘制在屏幕上,我想它的像素数据已经存储在内存中的某个地方。 - Wayne Hartman
图形/ GPU/ 显示内存与 CPU 内存不同。您无法读取显示器的内存。它是不透明的并且在应用程序的沙盒之外。 - hotpaw2

-2
- (BOOL)isWallPixel: (UIImage *)image: (int) x :(int) y {

CFDataRef pixelData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
const UInt8* data = CFDataGetBytePtr(pixelData);

int pixelInfo = ((image.size.width  * y) + x ) * 4; // The image is png

//UInt8 red = data[pixelInfo];         // If you need this info, enable it
//UInt8 green = data[(pixelInfo + 1)]; // If you need this info, enable it
//UInt8 blue = data[pixelInfo + 2];    // If you need this info, enable it
UInt8 alpha = data[pixelInfo + 3];     // I need only this info for my maze game
CFRelease(pixelData);

//UIColor* color = [UIColor colorWithRed:red/255.0f green:green/255.0f blue:blue/255.0f alpha:alpha/255.0f]; // The pixel color info

if (alpha) return YES;
else return NO;
}

请提供更多关于你的答案的解释。 - zachjs
谢谢,我必须更改声明以读取(- (BOOL)isWallPixel: (UIImage *)image theX:(int) x theY:(int) y { - Jim True
-1:这只是一个没有署名的这个答案的剽窃。 - DarkDust

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