我正在编写一款iPhone应用程序,需要实现类似photoshop中的“吸管”工具,即您可以触摸图像上的一个点并捕获该像素的RGB值以确定和匹配其颜色。获取UIImage是易事,但是否有一种方法将UIImage数据转换为位图表示形式,在其中可以提取给定像素的此信息?最好提供一个可运行的代码示例,并注意我不关心alpha值。
我正在编写一款iPhone应用程序,需要实现类似photoshop中的“吸管”工具,即您可以触摸图像上的一个点并捕获该像素的RGB值以确定和匹配其颜色。获取UIImage是易事,但是否有一种方法将UIImage数据转换为位图表示形式,在其中可以提取给定像素的此信息?最好提供一个可运行的代码示例,并注意我不关心alpha值。
今晚我发布了一个与此页面上已经说过的内容有所整合和小补充的帖子,可以在本帖底部找到。但是,我正在编辑此帖以发布我提出的更好的方法(至少对于我的需求来说,这包括修改像素数据),因为它提供可写入的数据(而据我所知,先前帖子和本帖底部提供的方法只提供了只读数据的引用)。
方法1:可写像素信息
我定义了一些常量
#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;
位图函数(返回预先计算的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;
}
步骤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];
}
这是我对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
UIGraphicsBeginImageContext
,CGContextAddArc
等方法创建图像,并将其放入UIImageView的image属性中时,使用另一种方法得到了意外的结果。 图像显示正常,但我无法从一个点获取颜色。 因此,我尝试了这种方法,但是我得到的r、g、b都是零,alpha是192,即使是在用圆圈绘制后也是如此。 我认为这个类别应该可以工作,因为你可以强制使用所需的颜色空间等,但它并没有起作用。 有什么想法吗? - Victor EngelUIImage的位图数据无法直接访问。
您需要获取UIImage的CGImage表示。然后获取CGImage的数据提供程序,从中获取位图的CFData表示形式。完成后,请确保释放CFData。
CGImageRef cgImage = [image CGImage];
CGDataProviderRef provider = CGImageGetDataProvider(cgImage);
CFDataRef bitmapData = CGDataProviderCopyData(provider);
您可能希望查看CGImage的位图信息以获取像素顺序、图像尺寸等。
Lajos的答案对我有用。为了将像素数据作为字节数组获取,我做了这个:
UInt8* data = CFDataGetBytePtr(bitmapData);
更多信息:CFDataRef 文档。
另外,请记得包含CoreGraphics.framework
。
CFDataGetBytePtr(bitmapData)
返回一个 const UInt8*
,这是一个 const,因此修改它可能会导致不可预测的结果。 - Marius感谢大家!综合几个答案,我得到以下结论:
- (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优化而在设备上出现问题的情况。