38

我正在编写一个 iPhone 应用程序,并且需要基本上实现与 Photoshop 中的“吸管”工具等效的东西,您可以在其中触摸图像上的一个点并捕获相关像素的 RGB 值以确定和匹配其颜色。获取 UIImage 是很容易的部分,但是有没有办法将 UIImage 数据转换为位图表示,我可以在其中提取给定像素的这些信息?一个工作代码示例将不胜感激,并注意我不关心 alpha 值。

4

8 回答 8

38

再详细一点...

我今天晚上早些时候发布了一个合并和一个小补充,对这个页面上所说的内容 - 可以在这篇文章的底部找到。但是,此时我正在编辑帖子,以发布我建议的内容(至少对于我的要求,包括修改像素数据)是一种更好的方法,因为它提供了可写数据(而据我了解,提供的方法由以前的帖子和这篇文章的底部提供了对数据的只读引用)。

方法一:可写像素信息

  1. 我定义了常量

    #define RGBA        4
    #define RGBA_8_BIT  8
    
  2. 在我的 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;
    
  3. 像素结构(此版本中带有 alpha)

    typedef struct RGBAPixel {
        byte red;
        byte green;
        byte blue;
        byte alpha;
    } RGBAPixel;
    
  4. 位图函数(返回预先计算的 RGBA;将 RGB 除以 A 得到未修改的 RGB):

    -(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;

步骤 2. 我声明了一个对应于像素的结构:

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

第 3 步。我将 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;

步骤 4. 子类代码我放入一个名为 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;

第 5 步。我做了一个访问器方法:

-(RGBPixel*)pixelDataForRow:(int)row column:(int)column{
    //Return a pointer to the pixel data
    return &pixelData[row * column];           
}
于 2009-03-29T05:20:23.770 回答
22

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

这种方法将请求的像素渲染到 1px 大的 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 
于 2012-08-21T16:43:09.223 回答
11

您不能直接访问 UIImage 的位图数据。

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

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

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

于 2008-09-28T01:12:55.763 回答
5

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

UInt8* data = CFDataGetBytePtr(bitmapData);

更多信息:CFDataRef文档

另外,记得包括CoreGraphics.framework

于 2008-12-29T01:09:01.050 回答
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;
}

这只需要 x 和 y 的点范围 0.0-1.0。例子:

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

这对我很有用。我做了几个假设,比如每像素位数和 RGBA 颜色空间,但这应该适用于大多数情况。

另一个注意事项 - 它对我来说既适用于模拟器又适用于设备 - 我过去曾遇到过问题,因为它在设备上运行时发生了 PNG 优化。

于 2012-08-20T04:00:58.427 回答
1

为了在我的应用程序中做类似的事情,我创建了一个小的屏幕外 CGImageContext,然后将 UIImage 渲染到其中。这使我能够快速地一次提取多个像素。这意味着您可以将目标位图设置为易于解析的格式,并让 CoreGraphics 完成在颜色模型或位图格式之间转换的繁重工作。

于 2008-10-01T21:37:36.997 回答
1

我不知道如何根据给定的 X、Y 坐标正确索引图像数据。有人知道吗?

pixelPosition = (x+(y*((imagewidth)*BytesPerPixel)));

//据我所知,音高不是这个设备的问题,可以为零……//(或从数学中提取)。

于 2009-03-14T04:04:57.730 回答
1

使用提供像素级访问(读/写)的ANImageBitmapRep 。

于 2012-04-13T17:15:42.400 回答