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

Oli*_*lie 196 cocoa cocoa-touch 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;
}
Run Code Online (Sandbox Code Playgroud)

谢谢!

Oli*_*lie 247

仅供参考,我将Keremk的答案与原始大纲相结合,清理了拼写错误,将其归结为返回一系列颜色并完成整个编译.结果如下:

+ (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;
}
Run Code Online (Sandbox Code Playgroud)

  • 使用CALLOC INSTEAD OF MALLOC !!!! 我正在使用此代码来测试某些像素是否透明,并且我正在获取虚假信息,其中像素是透明的,因为内存未先被清除.使用calloc(width*height,4)而不是malloc就可以了.其余代码很棒,谢谢! (43认同)
  • 这很棒.谢谢.关于用法的注意事项:x和y是图像中开始获取RGBAs的坐标,'count'是从该点获取的像素数,从左到右,然后逐行.示例用法:获取整个图像的RGBAs:`getRGBAsFromImage:image atX:0和Y:0 count:image.size.width*image.size.height`获取图像最后一行的RGBAs:`getRGBAsFromImage:image atX:0和Y:image.size.height-1 count:image.size.width` (5认同)
  • 不要忘记对颜色进行多次选择.而且,这些乘以1不会做任何事情. (4认同)
  • 方式过于繁琐的malloc大空间只能读取一个像素. (4认同)
  • 即使它是正确的.当count是一个很大的数字时(比如一行图像的长度或整个图像的大小!)我不认为这个方法的性能会很好,因为太多的UIColor对象真的很重.在现实生活中,当我需要一个像素时,UIColor就可以了,(UIColors的NSArray对我来说太过分了).当你需要一堆颜色我不会使用UIColors,因为我可能会使用OpenGL等.在我看来,这种方法没有现实生活用途,但非常适合教育目的. (2认同)
  • 请注意,使用相机垂直拍摄的图像会在`UIImage`对象中设置`imageOrientation`标志,而不是旋转图像.这可能会导致一些令人讨厌的错误,因为转换为`CGImage`会忽略这一点.因此,如果`imageOrientation`是'UIImageOrientationUp`(用于手机横向拍摄的图像),那么这将正常工作,但如果它是'UIImageOrientationRight`(它是用相机垂直拍摄的图像)那么整个图像将在阵列中旋转90度.谨防! (2认同)
  • 在为"红色","绿色","蓝色"赋值时,数字不在0和1之间.因此,使用这些值创建的"UIColor"几乎总是白色.每个这些代码值必须分为255.0f,如:`UIColor*acolor = [UIColor colorWithRed:red/255.0f green:green/255.0f blue:blue/255.0f alpha:alpha]; (2认同)
  • 感谢您的分享。对于复杂的,半成品的,换句话说,糟糕的API,Apple确实做了出色的工作。 (2认同)

ker*_*emk 48

一种方法是将图像绘制到由给定缓冲区支持的位图上下文(对于给定的颜色空间)(在这种情况下它是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];
Run Code Online (Sandbox Code Playgroud)

  • 这会泄漏内存.发布rawData:free(rawData); (5认同)
  • `CGContextDrawImage`需要三个参数,上面只有两个...我认为它应该是`CGContextDrawImage(context,CGRectMake(0,0,width,height),image)` (5认同)

yak*_*lev 25

Apple的技术问答QA1509展示了以下简单方法:

CFDataRef CopyImagePixels(CGImageRef inImage)
{
    return CGDataProviderCopyData(CGImageGetDataProvider(inImage));
}
Run Code Online (Sandbox Code Playgroud)

使用CFDataGetBytePtr去实际字节(和各种CGImageGet*方法来了解如何对其进行解释).

  • 这不是一个很好的方法,因为每个图像的像素格式往往变化很大.有几件事可以改变,包括1.图像的方向2. alpha分量的格式和3. RGB的字节顺序.我个人花了一些时间试图解码Apple的文档如何做到这一点,但我不确定它是否值得.如果你想快速完成,只需keremk的解决方案. (10认同)

Eri*_*ner 15

无法相信这里没有一个正确答案.无需分配指针,仍然需要对未经过相乘的值进行规范化.为了切入追逐,这里是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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Nid*_*uri 9

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);
Run Code Online (Sandbox Code Playgroud)

  • 我刚刚找到这个链接,它的工作效果很棒:https://gist.github.com/739132 (2认同)

edd*_*ddy 9

这是一个SO 线程,其中@Matt通过移动图像将所需像素仅渲染到1x1上下文中,以使所需像素与上下文中的一个像素对齐.


Cam*_*mer 7

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

根据UIImage上Apple Reference,对象是不可变的,你无法访问后备字节.而如果你填充这是事实,你可以访问CGImage数据UIImageCGImage(显式或隐式),它将返回NULL如果UIImage通过支持CIImage,反之亦然.

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

解决这个问题的常用技巧

如上所述,您的选择是

  • UIImagePNGRepresentation或JPEG
  • 确定图像是否具有CGImage或CIImage支持数据并将其获取

如果您希望输出不是ARGB,PNG或JPEG数据并且数据尚未由CIImage支持,则这些都不是特别好的技巧.

我的推荐,试试CIImage

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

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

作为额外的奖励,您可以通过将滤镜链接到图像上来开始对图像进行良好的操作.这解决了图像颠倒或需要旋转/缩放等许多问题.


swi*_*sea 6

基于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
}
Run Code Online (Sandbox Code Playgroud)


Des*_*ume 6

斯威夫特 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
    }

}
Run Code Online (Sandbox Code Playgroud)