【问题标题】:How to get pixel data from a UIImage (Cocoa Touch) or CGImage (Core Graphics)?如何从 UIImage (Cocoa Touch) 或 CGImage (Core Graphics) 获取像素数据?
【发布时间】:2010-10-01 16:17:25
【问题描述】:

我有一个 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;
}

【问题讨论】:

    标签: cocoa cocoa-touch core-graphics


    【解决方案1】:

    这样做的一种方法是将图像绘制到位图上下文,该上下文由给定颜色空间的给定缓冲区支持(在本例中为 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];
    

    【讨论】:

    • 我在我的应用程序中做了类似的事情。要提取任意像素,您只需使用已知位图格式绘制 1x1 位图,并适当调整 CGBitmapContext 的原点。
    • 这会泄漏内存。释放 rawData: free(rawData);
    • CGContextDrawImage 需要三个参数,上面只给出了两个...我认为应该是CGContextDrawImage(context, CGRectMake(0, 0, width, height), image)
    【解决方案2】:

    Apple 的Technical Q&A QA1509 显示了以下简单方法:

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

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

    【讨论】:

    • 这不是一个好方法,因为每个图像的像素格式往往变化很大。有几件事可以改变,包括 1. 图像的方向 2. alpha 组件的格式和 3. RGB 的字节顺序。我个人花了一些时间尝试解码 Apple 的文档以了解如何做到这一点,但我不确定这是否值得。如果您想快速完成,只需 keremk 的解决方案。
    • 即使我保存图像(从我的应用程序中的色彩空间 RGBA 中),此方法总是给我 BGR 格式
    • @Soumyaljit 这将以最初存储 CGImageRef 数据的格式复制数据。在大多数情况下,这将是 BGRA,但也可能是 YUV。
    【解决方案3】:

    仅供参考,我将 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;
    }
    

    【讨论】:

    • 不要忘记取消预乘颜色。此外,这些乘以 1 不会做任何事情。
    • 即使它是正确的。当 count 是一个很大的数字时(比如图像的一行的长度或整个图像的大小!)我不认为这种方法的性能会很好,因为有太多的 UIColor 对象真的很重。在现实生活中,当我需要一个像素时,UIColor 就可以了,(UIColors 的 NSArray 对我来说太多了)。当你需要一堆颜色时,我不会使用 UIColors,因为我可能会使用 OpenGL 等。在我看来,这种方法在现实生活中没有用处,但非常适合教育目的。
    • malloc 一个大空间只能读取一个像素太麻烦了。
    • 使用 CALLOC 代替 MALLOC!!!!我正在使用这段代码来测试某些像素是否透明,并且我得到了虚假信息,因为没有先清除内存,所以像素应该是透明的。使用 calloc(width*height, 4) 而不是 malloc 就可以了。其余的代码很棒,谢谢!
    • 这很棒。谢谢。使用注意事项:x 和 y 是图像中开始获取 RGBA 的坐标,“count”是从该点开始获取的像素数,从左到右,然后逐行。示例用法:获取整个图像的 RGBA:getRGBAsFromImage:image atX:0 andY:0 count:image.size.width*image.size.height 获取图像最后一行的 RGBA:getRGBAsFromImage:image atX:0 andY:image.size.height-1 count:image.size.width
    【解决方案4】:
    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);
    

    【讨论】:

    • 我发布了这个,它对我有用,但是从缓冲区回到 UIImage 我似乎还无法弄清楚,有什么想法吗?
    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

      基于不同的答案,但主要基于this,这可以满足我的需要:

      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 在 4 字节无符号整数 pixel1 中始终设置为 FF。

      【讨论】:

        【解决方案7】:

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

        根据Apple Reference on UIImage,该对象是不可变的,您无法访问支持字节。如果您使用CGImage(显式或隐式)填充UIImage,则确实可以访问CGImage数据,但如果UIImageCIImage支持,它将返回NULL,反之亦然反之亦然。

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

        解决此问题的常用技巧

        如上所述,您的选择是

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

        如果您想要的输出不是 ARGB、PNG 或 JPEG 数据并且数据尚未由 CIImage 支持,那么这两种方法都不是特别好的技巧。

        我的推荐,试试 CIImage

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

        的问题

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

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

        【讨论】:

          【解决方案8】:

          基于 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
          }
          

          【讨论】:

            【解决方案9】:

            不敢相信这里没有一个正确的答案。无需分配指针,未相乘的值仍需归一化。言归正传,这里是 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)
                    }
                }
            }
            

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

            【讨论】:

            • 如何为我的图片使用这个扩展?让 imageObj = UIImage(named:"greencolorImg.png")
            • let imageObj = UIImage(named:"greencolorImg.png"); imageObj.cgImage?.colors(at: [pt1, pt2])
            • 请记住,pt1pt2CGPoint 变量。
            • 拯救了我的一天 :)
            • @ErikAigner 如果我想获取图像每个像素的颜色,这会起作用吗?性能成本是多少?
            【解决方案10】:

            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
                }
            
            }
            

            【讨论】:

            • 方向变化怎么样?你不需要检查 UIImage.Orientation 吗?
            • 另外,您的 componentLayout 缺少只有 alpha 的情况,.alphaOnly
            • @endavid “仅限 Alpha”不受支持(因为这种情况非常罕见)。图像方向超出了此代码的范围,但是有很多资源可以说明如何规范化 UIImage 的方向,这是您在开始读取其像素颜色之前要做的事情。
            【解决方案11】:

            要在 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/

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2011-01-23
              • 1970-01-01
              • 2011-08-29
              • 1970-01-01
              • 2012-05-11
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多