如何从UIImage (Cocoa Touch)或CGImage (Core Graphics)获取像素数据?

我有一个UIImage (Cocoa Touch)。从那里,我很高兴得到一个CGImage或任何你想要的可用的东西。我想写这个函数:

- (int)getRGBAFromImage:(UIImage *)image atX:(int)xx andY:(int)yy {
// [...]
// What do I want to read about to help
// me fill in this bit, here?
// [...]


int result = (red << 24) | (green << 16) | (blue << 8) | alpha;
return result;
}
148778 次浏览

一种方法是将图像绘制到一个位图上下文中,这个位图上下文由给定的缓冲区支持,用于给定的颜色空间(在这种情况下是RGB):(注意,这将把图像数据复制到该缓冲区中,所以你想要缓存它,而不是每次你需要获取像素值时都做这个操作)

请看下面的例子:

// First get the image into your data buffer
CGImageRef image = [myUIImage CGImage];
NSUInteger width = CGImageGetWidth(image);
NSUInteger height = CGImageGetHeight(image);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char *rawData = malloc(height * width * 4);
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(rawData, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);


CGContextDrawImage(context, CGRectMake(0, 0, width, height));
CGContextRelease(context);


// Now your rawData contains the image data in the RGBA8888 pixel format.
int byteIndex = (bytesPerRow * yy) + xx * bytesPerPixel;
red = rawData[byteIndex];
green = rawData[byteIndex + 1];
blue = rawData[byteIndex + 2];
alpha = rawData[byteIndex + 3];

Apple的技术qq1509显示了以下简单的方法:

CFDataRef CopyImagePixels(CGImageRef inImage)
{
return CGDataProviderCopyData(CGImageGetDataProvider(inImage));
}

使用CFDataGetBytePtr来获得实际的字节(以及各种CGImageGet*方法来理解如何解释它们)。

仅供参考,我将凯雷姆克的答案与我的原始大纲结合起来,清除了错字,将其概括为返回一个颜色数组,并得到了整个编译。结果如下:

+ (NSArray*)getRGBAsFromImage:(UIImage*)image atX:(int)x andY:(int)y count:(int)count
{
NSMutableArray *result = [NSMutableArray arrayWithCapacity:count];


// First get the image into your data buffer
CGImageRef imageRef = [image CGImage];
NSUInteger width = CGImageGetWidth(imageRef);
NSUInteger height = CGImageGetHeight(imageRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char *rawData = (unsigned char*) calloc(height * width * 4, sizeof(unsigned char));
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(rawData, width, height,
bitsPerComponent, bytesPerRow, colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);


CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGContextRelease(context);


// Now your rawData contains the image data in the RGBA8888 pixel format.
NSUInteger byteIndex = (bytesPerRow * y) + x * bytesPerPixel;
for (int i = 0 ; i < count ; ++i)
{
CGFloat alpha = ((CGFloat) rawData[byteIndex + 3] ) / 255.0f;
CGFloat red   = ((CGFloat) rawData[byteIndex]     ) / alpha;
CGFloat green = ((CGFloat) rawData[byteIndex + 1] ) / alpha;
CGFloat blue  = ((CGFloat) rawData[byteIndex + 2] ) / alpha;
byteIndex += bytesPerPixel;


UIColor *acolor = [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
[result addObject:acolor];
}


free(rawData);


return result;
}
NSString * path = [[NSBundle mainBundle] pathForResource:@"filename" ofType:@"jpg"];
UIImage * img = [[UIImage alloc]initWithContentsOfFile:path];
CGImageRef image = [img CGImage];
CFDataRef data = CGDataProviderCopyData(CGImageGetDataProvider(image));
const unsigned char * buffer =  CFDataGetBytePtr(data);

这里是一个SO 线程,其中@Matt通过替换图像,仅将所需像素渲染到1x1上下文中,以便所需像素与上下文中的一个像素对齐。

基于不同的答案,但主要是,这是我需要的:

UIImage *image1 = ...; // The image from where you want a pixel data
int pixelX = ...; // The X coordinate of the pixel you want to retrieve
int pixelY = ...; // The Y coordinate of the pixel you want to retrieve


uint32_t pixel1; // Where the pixel data is to be stored
CGContextRef context1 = CGBitmapContextCreate(&pixel1, 1, 1, 8, 4, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaNoneSkipFirst);
CGContextDrawImage(context1, CGRectMake(-pixelX, -pixelY, CGImageGetWidth(image1.CGImage), CGImageGetHeight(image1.CGImage)), image1.CGImage);
CGContextRelease(context1);

由于这些行,你将有一个AARRGGBB格式的像素,alpha总是设置为FF,在4字节无符号整数pixel1中。

UIImage是一个包装器,字节是CGImage或CIImage

根据苹果参考UIImage,对象是不可变的,你不能访问后台字节。虽然你确实可以访问CGImage数据,如果你用CGImage填充UIImage(显式或隐式),如果UIImageCIImage支持,它将返回NULL,反之亦然。

图像对象不提供对其底层图像的直接访问 数据。但是,您可以以其他格式检索图像数据 具体来说,你可以使用cgImage和ciImage 属性检索与之兼容的图像版本 分别是Core Graphics和Core Image。你也可以使用 UIImagePNGRepresentation (:)和UIImageJPEGRepresentation(: _:) 函数生成包含图像数据的NSData对象

. PNG或JPEG格式

解决这个问题的常见技巧

你的选择是

  • UIImagePNGRepresentation或JPEG
  • 确定image是否有CGImage或CIImage备份数据并将其获取到那里

如果您想要输出不是ARGB、PNG或JPEG数据,而且数据还没有得到CIImage的支持,这两种方法都不是特别好的技巧。

我的建议是,试试CIImage

在开发你的项目时,避免使用UIImage而选择其他的可能更有意义。UIImage,作为一个Obj-C图像包装器,经常被CGImage支持到我们认为理所当然的地步。CIImage往往是一种更好的包装器格式,因为你可以使用CIContext来获得你想要的格式,而不需要知道它是如何创建的。在您的情况下,获取位图将是一个调用问题

- render:toBitmap:rowBytes:bounds:format:colorSpace:

作为一个额外的奖励,你可以开始对图像进行良好的操作,将过滤器链接到图像上。这解决了很多问题,图像是倒置的或需要旋转/缩放等。

基于Olie和Algal的回答,以下是Swift 3的最新答案

public func getRGBAs(fromImage image: UIImage, x: Int, y: Int, count: Int) -> [UIColor] {


var result = [UIColor]()


// First get the image into your data buffer
guard let cgImage = image.cgImage else {
print("CGContext creation failed")
return []
}


let width = cgImage.width
let height = cgImage.height
let colorSpace = CGColorSpaceCreateDeviceRGB()
let rawdata = calloc(height*width*4, MemoryLayout<CUnsignedChar>.size)
let bytesPerPixel = 4
let bytesPerRow = bytesPerPixel * width
let bitsPerComponent = 8
let bitmapInfo: UInt32 = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue


guard let context = CGContext(data: rawdata, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) else {
print("CGContext creation failed")
return result
}


context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))


// Now your rawData contains the image data in the RGBA8888 pixel format.
var byteIndex = bytesPerRow * y + bytesPerPixel * x


for _ in 0..<count {
let alpha = CGFloat(rawdata!.load(fromByteOffset: byteIndex + 3, as: UInt8.self)) / 255.0
let red = CGFloat(rawdata!.load(fromByteOffset: byteIndex, as: UInt8.self)) / alpha
let green = CGFloat(rawdata!.load(fromByteOffset: byteIndex + 1, as: UInt8.self)) / alpha
let blue = CGFloat(rawdata!.load(fromByteOffset: byteIndex + 2, as: UInt8.self)) / alpha
byteIndex += bytesPerPixel


let aColor = UIColor(red: red, green: green, blue: blue, alpha: alpha)
result.append(aColor)
}


free(rawdata)


return result
}

不敢相信这里有没有一个正确答案。不需要分配指针,未乘的值仍然需要规范化。开门见山地说,下面是Swift 4的正确版本。对于UIImage只使用.cgImage

extension CGImage {
func colors(at: [CGPoint]) -> [UIColor]? {
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bytesPerPixel = 4
let bytesPerRow = bytesPerPixel * width
let bitsPerComponent = 8
let bitmapInfo: UInt32 = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue
    

guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo),
let ptr = context.data?.assumingMemoryBound(to: UInt8.self) else {
return nil
}
    

context.draw(self, in: CGRect(x: 0, y: 0, width: width, height: height))
    

return at.map { p in
let i = bytesPerRow * Int(p.y) + bytesPerPixel * Int(p.x)
            

let a = CGFloat(ptr[i + 3]) / 255.0
let r = (CGFloat(ptr[i]) / a) / 255.0
let g = (CGFloat(ptr[i + 1]) / a) / 255.0
let b = (CGFloat(ptr[i + 2]) / a) / 255.0
            

return UIColor(red: r, green: g, blue: b, alpha: a)
}
}
}

必须首先将图像绘制/转换为缓冲区的原因是,图像可以有几种不同的格式。需要此步骤将其转换为您可以读取的一致格式。

Swift 5版本

这里给出的答案要么是过时的,要么是不正确的,因为他们没有考虑到以下因素:

  1. 图像的像素大小可以与由image.size.width/image.size.height返回的点大小不同。
  2. 图像中的像素组件可以使用各种布局,如BGRA、ABGR、ARGB等,也可能根本没有alpha组件,如BGR和RGB。例如,UIView.drawHierarchy(in:afterScreenUpdates:)方法可以生成BGRA图像。
  3. 对于图像中的所有像素,颜色组件可以预先乘以alpha,并且需要除以alpha以恢复原始颜色。
  4. 对于CGImage使用的内存优化,像素行(以字节为单位)的大小可以大于像素宽度乘以4。

下面的代码提供了一个通用的Swift 5解决方案,为所有这些特殊情况获取像素的UIColor。代码在可用性和清晰度方面进行了优化,用于性能。

public extension UIImage {


var pixelWidth: Int {
return cgImage?.width ?? 0
}


var pixelHeight: Int {
return cgImage?.height ?? 0
}


func pixelColor(x: Int, y: Int) -> UIColor {
assert(
0..<pixelWidth ~= x && 0..<pixelHeight ~= y,
"Pixel coordinates are out of bounds")


guard
let cgImage = cgImage,
let data = cgImage.dataProvider?.data,
let dataPtr = CFDataGetBytePtr(data),
let colorSpaceModel = cgImage.colorSpace?.model,
let componentLayout = cgImage.bitmapInfo.componentLayout
else {
assertionFailure("Could not get a pixel of an image")
return .clear
}


assert(
colorSpaceModel == .rgb,
"The only supported color space model is RGB")
assert(
cgImage.bitsPerPixel == 32 || cgImage.bitsPerPixel == 24,
"A pixel is expected to be either 4 or 3 bytes in size")


let bytesPerRow = cgImage.bytesPerRow
let bytesPerPixel = cgImage.bitsPerPixel/8
let pixelOffset = y*bytesPerRow + x*bytesPerPixel


if componentLayout.count == 4 {
let components = (
dataPtr[pixelOffset + 0],
dataPtr[pixelOffset + 1],
dataPtr[pixelOffset + 2],
dataPtr[pixelOffset + 3]
)


var alpha: UInt8 = 0
var red: UInt8 = 0
var green: UInt8 = 0
var blue: UInt8 = 0


switch componentLayout {
case .bgra:
alpha = components.3
red = components.2
green = components.1
blue = components.0
case .abgr:
alpha = components.0
red = components.3
green = components.2
blue = components.1
case .argb:
alpha = components.0
red = components.1
green = components.2
blue = components.3
case .rgba:
alpha = components.3
red = components.0
green = components.1
blue = components.2
default:
return .clear
}


// If chroma components are premultiplied by alpha and the alpha is `0`,
// keep the chroma components to their current values.
if cgImage.bitmapInfo.chromaIsPremultipliedByAlpha && alpha != 0 {
let invUnitAlpha = 255/CGFloat(alpha)
red = UInt8((CGFloat(red)*invUnitAlpha).rounded())
green = UInt8((CGFloat(green)*invUnitAlpha).rounded())
blue = UInt8((CGFloat(blue)*invUnitAlpha).rounded())
}


return .init(red: red, green: green, blue: blue, alpha: alpha)


} else if componentLayout.count == 3 {
let components = (
dataPtr[pixelOffset + 0],
dataPtr[pixelOffset + 1],
dataPtr[pixelOffset + 2]
)


var red: UInt8 = 0
var green: UInt8 = 0
var blue: UInt8 = 0


switch componentLayout {
case .bgr:
red = components.2
green = components.1
blue = components.0
case .rgb:
red = components.0
green = components.1
blue = components.2
default:
return .clear
}


return .init(red: red, green: green, blue: blue, alpha: UInt8(255))


} else {
assertionFailure("Unsupported number of pixel components")
return .clear
}
}


}


public extension UIColor {


convenience init(red: UInt8, green: UInt8, blue: UInt8, alpha: UInt8) {
self.init(
red: CGFloat(red)/255,
green: CGFloat(green)/255,
blue: CGFloat(blue)/255,
alpha: CGFloat(alpha)/255)
}


}


public extension CGBitmapInfo {


enum ComponentLayout {


case bgra
case abgr
case argb
case rgba
case bgr
case rgb


var count: Int {
switch self {
case .bgr, .rgb: return 3
default: return 4
}
}


}


var componentLayout: ComponentLayout? {
guard let alphaInfo = CGImageAlphaInfo(rawValue: rawValue & Self.alphaInfoMask.rawValue) else { return nil }
let isLittleEndian = contains(.byteOrder32Little)


if alphaInfo == .none {
return isLittleEndian ? .bgr : .rgb
}
let alphaIsFirst = alphaInfo == .premultipliedFirst || alphaInfo == .first || alphaInfo == .noneSkipFirst


if isLittleEndian {
return alphaIsFirst ? .bgra : .abgr
} else {
return alphaIsFirst ? .argb : .rgba
}
}


var chromaIsPremultipliedByAlpha: Bool {
let alphaInfo = CGImageAlphaInfo(rawValue: rawValue & Self.alphaInfoMask.rawValue)
return alphaInfo == .premultipliedFirst || alphaInfo == .premultipliedLast
}


}

要在Swift 5中访问UIImage的原始RGB值,请使用底层CGImage及其dataProvider:

import UIKit


let image = UIImage(named: "example.png")!


guard let cgImage = image.cgImage,
let data = cgImage.dataProvider?.data,
let bytes = CFDataGetBytePtr(data) else {
fatalError("Couldn't access image data")
}
assert(cgImage.colorSpace?.model == .rgb)


let bytesPerPixel = cgImage.bitsPerPixel / cgImage.bitsPerComponent
for y in 0 ..< cgImage.height {
for x in 0 ..< cgImage.width {
let offset = (y * cgImage.bytesPerRow) + (x * bytesPerPixel)
let components = (r: bytes[offset], g: bytes[offset + 1], b: bytes[offset + 2])
print("[x:\(x), y:\(y)] \(components)")
}
print("---")
}

https://www.ralfebert.de/ios/examples/image-processing/uiimage-raw-pixels/