如何在iPhone上获取图像像素的RGB值

38

我正在编写一款iPhone应用程序,需要实现类似photoshop中的“吸管”工具,即您可以触摸图像上的一个点并捕获该像素的RGB值以确定和匹配其颜色。获取UIImage是易事,但是否有一种方法将UIImage数据转换为位图表示形式,在其中可以提取给定像素的此信息?最好提供一个可运行的代码示例,并注意我不关心alpha值。

8个回答

38

更详细的解释...

今晚我发布了一个与此页面上已经说过的内容有所整合和小补充的帖子,可以在本帖底部找到。但是,我正在编辑此帖以发布我提出的更好的方法(至少对于我的需求来说,这包括修改像素数据),因为它提供可写入的数据(而据我所知,先前帖子和本帖底部提供的方法只提供了只读数据的引用)。

方法1:可写像素信息

  1. 我定义了一些常量

  2. #define RGBA        4
    #define RGBA_8_BIT  8
    
    在我的UIImage子类中,我声明了实例变量:
    size_t bytesPerRow;
    size_t byteCount;
    size_t pixelCount;
    
    CGContextRef context;
    CGColorSpaceRef colorSpace;
    
    UInt8 *pixelByteData;
    // A pointer to an array of RGBA bytes in memory
    RPVW_RGBAPixel *pixelData;
    
    像素结构体(在此版本中带有 alpha 通道)
    typedef struct RGBAPixel {
        byte red;
        byte green;
        byte blue;
        byte alpha;
    } RGBAPixel;
    
  3. 位图函数(返回预先计算的RGBA值;将RGB除以A以获取未修改的RGB值):

  4. -(RGBAPixel*) bitmap {
        NSLog( @"Returning bitmap representation of UIImage." );
        // 8 bits each of red, green, blue, and alpha.
        [self setBytesPerRow:self.size.width * RGBA];
        [self setByteCount:bytesPerRow * self.size.height];
        [self setPixelCount:self.size.width * self.size.height];
    
        // Create RGB color space
        [self setColorSpace:CGColorSpaceCreateDeviceRGB()];
    
        if (!colorSpace)
        {
            NSLog(@"Error allocating color space.");
            return nil;
        }
    
        [self setPixelData:malloc(byteCount)];
    
        if (!pixelData)
        {
            NSLog(@"Error allocating bitmap memory. Releasing color space.");
            CGColorSpaceRelease(colorSpace);
    
            return nil;
        }
    
        // Create the bitmap context. 
        // Pre-multiplied RGBA, 8-bits per component. 
        // The source image format will be converted to the format specified here by CGBitmapContextCreate.
        [self setContext:CGBitmapContextCreate(
                                               (void*)pixelData,
                                               self.size.width,
                                               self.size.height,
                                               RGBA_8_BIT,
                                               bytesPerRow,
                                               colorSpace,
                                               kCGImageAlphaPremultipliedLast
                                               )];
    
        // Make sure we have our context
        if (!context)   {
            free(pixelData);
            NSLog(@"Context not created!");
        }
    
        // Draw the image to the bitmap context. 
        // The memory allocated for the context for rendering will then contain the raw image pixelData in the specified color space.
        CGRect rect = { { 0 , 0 }, { self.size.width, self.size.height } };
    
        CGContextDrawImage( context, rect, self.CGImage );
    
        // Now we can get a pointer to the image pixelData associated with the bitmap context.
        pixelData = (RGBAPixel*) CGBitmapContextGetData(context);
    
        return pixelData;
    }
    

只读数据(以前的信息)- 方法2:


步骤1. 我声明了一个字节类型:

 typedef unsigned char byte;

第二步,我声明了一个结构体来对应一个像素:

 typedef struct RGBPixel{
    byte red;
    byte green;
    byte blue;  
    }   
RGBPixel;

第三步。我子类化了UIImageView并声明了相应的合成属性:

//  Reference to Quartz CGImage for receiver (self)  
CFDataRef bitmapData;   

//  Buffer holding raw pixel data copied from Quartz CGImage held in receiver (self)    
UInt8* pixelByteData;

//  A pointer to the first pixel element in an array    
RGBPixel* pixelData;

第四步。我将子类代码放在一个名为“bitmap”的方法中(以返回位图像素数据):

//Get the bitmap data from the receiver's CGImage (see UIImage docs)  
[self setBitmapData: CGDataProviderCopyData(CGImageGetDataProvider([self CGImage]))];

//Create a buffer to store bitmap data (unitialized memory as long as the data)    
[self setPixelBitData:malloc(CFDataGetLength(bitmapData))];

//Copy image data into allocated buffer    
CFDataGetBytes(bitmapData,CFRangeMake(0,CFDataGetLength(bitmapData)),pixelByteData);

//Cast a pointer to the first element of pixelByteData    
//Essentially what we're doing is making a second pointer that divides the byteData's units differently - instead of dividing each unit as 1 byte we will divide each unit as 3 bytes (1 pixel).    
pixelData = (RGBPixel*) pixelByteData;

//Now you can access pixels by index: pixelData[ index ]    
NSLog(@"Pixel data one red (%i), green (%i), blue (%i).", pixelData[0].red, pixelData[0].green, pixelData[0].blue);

//You can determine the desired index by multiplying row * column.    
return pixelData;

第五步,我创建了一个访问器方法:

-(RGBPixel*)pixelDataForRow:(int)row column:(int)column{
    //Return a pointer to the pixel data
    return &pixelData[row * column];           
}

很详细,但这个答案需要整理一下。代码需要正确标记为代码,以便正确显示。我会自己做,但我还没有编辑他人答案的能力。 - CodeAndCats
1
这需要针对Retina显示器进行修改。UIImage的大小以点为单位,而不是像素。大小需要乘以缩放因子(self.scale)。 - Victor Engel

22

这是我对UIImage颜色采样的解决方案。

该方法将请求的像素渲染到一个1像素大小的RGBA缓冲区中,并将结果颜色值作为UIColor对象返回。这比我见过的大多数其他方法都要快得多,而且只使用非常少的内存。

对于像颜色选择器这样的东西,这应该非常有效,因为通常你只需要任何给定时间内一个特定像素的值。

Uiimage+Picker.h

#import <UIKit/UIKit.h>


@interface UIImage (Picker)

- (UIColor *)colorAtPosition:(CGPoint)position;

@end

Uiimage+Picker.m

#import "UIImage+Picker.h"


@implementation UIImage (Picker)

- (UIColor *)colorAtPosition:(CGPoint)position {

    CGRect sourceRect = CGRectMake(position.x, position.y, 1.f, 1.f);
    CGImageRef imageRef = CGImageCreateWithImageInRect(self.CGImage, sourceRect);

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    unsigned char *buffer = malloc(4);
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big;
    CGContextRef context = CGBitmapContextCreate(buffer, 1, 1, 8, 4, colorSpace, bitmapInfo);
    CGColorSpaceRelease(colorSpace);
    CGContextDrawImage(context, CGRectMake(0.f, 0.f, 1.f, 1.f), imageRef);
    CGImageRelease(imageRef);
    CGContextRelease(context);

    CGFloat r = buffer[0] / 255.f;
    CGFloat g = buffer[1] / 255.f;
    CGFloat b = buffer[2] / 255.f;
    CGFloat a = buffer[3] / 255.f;

    free(buffer);

    return [UIColor colorWithRed:r green:g blue:b alpha:a];
}

@end 

我在使用UIGraphicsBeginImageContextCGContextAddArc等方法创建图像,并将其放入UIImageView的image属性中时,使用另一种方法得到了意外的结果。 图像显示正常,但我无法从一个点获取颜色。 因此,我尝试了这种方法,但是我得到的r、g、b都是零,alpha是192,即使是在用圆圈绘制后也是如此。 我认为这个类别应该可以工作,因为你可以强制使用所需的颜色空间等,但它并没有起作用。 有什么想法吗? - Victor Engel
我收回之前的说法。r、g、b 始终为 0。Alpha 看起来每个会话都是随机的。 - Victor Engel
@Matej,我解决了我的问题。我使用的是视网膜设备,因此图像的比例因子为2。我建议在这个方法中添加一个参数scale,该参数将与position.x和position.y相乘,以获取检查颜色的图像中的正确位置。 - Victor Engel
编辑代码以包括self.scale的乘法。不需要额外的参数,因为比例是UIImage对象的属性。 - Victor Engel
我必须承认,我从未使用@2x图像进行过测试。我认为CGImageCreateWithImageInRect应该在这里做正确的事情。但是我确实看到了一些问题,这些问题涉及应用不同imageOrientation值的图像(主要来自设备相机)- https://gist.github.com/matej/8052724 。所以你的意思是说CGRectMake(position.x * self.scale,position.y * self.scale,1.f,1.f)可以解决你的问题? - Matej Bukovinski
显示剩余2条评论

11

UIImage的位图数据无法直接访问。

您需要获取UIImage的CGImage表示。然后获取CGImage的数据提供程序,从中获取位图的CFData表示形式。完成后,请确保释放CFData。

CGImageRef cgImage = [image CGImage];
CGDataProviderRef provider = CGImageGetDataProvider(cgImage);
CFDataRef bitmapData = CGDataProviderCopyData(provider);

您可能希望查看CGImage的位图信息以获取像素顺序、图像尺寸等。


5

Lajos的答案对我有用。为了将像素数据作为字节数组获取,我做了这个:

UInt8* data = CFDataGetBytePtr(bitmapData);

更多信息:CFDataRef 文档

另外,请记得包含CoreGraphics.framework


1
请注意,CFDataGetBytePtr(bitmapData) 返回一个 const UInt8*,这是一个 const,因此修改它可能会导致不可预测的结果。 - Marius

3

感谢大家!综合几个答案,我得到以下结论:

- (UIColor*)colorFromImage:(UIImage*)image sampledAtPoint:(CGPoint)p {
    CGImageRef cgImage = [image CGImage];
    CGDataProviderRef provider = CGImageGetDataProvider(cgImage);
    CFDataRef bitmapData = CGDataProviderCopyData(provider);
    const UInt8* data = CFDataGetBytePtr(bitmapData);
    size_t bytesPerRow = CGImageGetBytesPerRow(cgImage);
    size_t width = CGImageGetWidth(cgImage);
    size_t height = CGImageGetHeight(cgImage);
    int col = p.x*(width-1);
    int row = p.y*(height-1);
    const UInt8* pixel = data + row*bytesPerRow+col*4;
    UIColor* returnColor = [UIColor colorWithRed:pixel[0]/255. green:pixel[1]/255. blue:pixel[2]/255. alpha:1.0];
    CFRelease(bitmapData);
    return returnColor;
}

这只需要一个0.0-1.0的点范围,分别用于x和y。例如:

UIColor* sampledColor = [self colorFromImage:image
         sampledAtPoint:CGPointMake(p.x/imageView.frame.size.width,
                                    p.y/imageView.frame.size.height)];

这对我来说非常有效。我做了一些假设,如每像素位数和RGBA颜色空间,但对于大多数情况应该是适用的。

另外需要注意的是,它在模拟器和设备上都能正常工作 - 我以前遇到过因为PNG优化而在设备上出现问题的情况。


嗯,这似乎可以工作,除了透明度。我添加了转换为HSBA和RGBA组件的方法,当我查询图像的透明部分(alpha=0.0)时,实际上我得到的是0,0,0,1.0,而不是我预期的?,?,?,0.0。你有解释吗?我可以检查前三个组件是否为0.0,但那将与黑色无法区分。编辑:我看到你已经硬编码了alpha。有没有办法从数据中获取它? - Victor Engel
我已经解决了这个问题,并且修改了你的解决方案以正确处理alpha。 - Victor Engel
我使用了这个解决方案的代码来开发我的项目,但是似乎存在内存泄漏问题。我并不真正理解这里发生了什么。也许有人可以给我指点一下。我省略了CFRelease语句,因为我认为在ARC下不需要它。加回去也没有解决泄漏问题,但我还没有能够确定到底是什么占用了所有的内存。如果我对这里发生的内存情况有更好的理解,我认为这可能会帮助我找到问题所在。 - Victor Engel
在分配工具中显示为UIDeviceRGBColor - 32字节增长实例(有很多)。 - Victor Engel
我一直在使用我的设备,即Retina iPad上的仪器。切换到iPad模拟器(选择Retina),我没有看到之前看到的泄漏情况。我是使用仪器的新手。我应该期望从我的设备和模拟器中看到不同的结果吗?我应该相信哪一个? - Victor Engel

1
为了在我的应用程序中实现类似的功能,我创建了一个小的离屏CGImageContext,并将UIImage呈现在其中。这使我能够快速提取一次多个像素。这意味着您可以设置目标位图以易于解析的格式,并让CoreGraphics处理颜色模型或位图格式之间的转换工作。

1
我不知道如何根据给定的X,Y坐标正确地索引图像数据。有人知道吗?
像素位置=(x+(y*(imagewidth * BytesPerPixel)));
//就我所知,这个设备的间距不是问题,可以让它为零... //(或从数学中删除)。

1

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