【问题标题】:Retrieving pixel data not working correctly with UIGraphicsImageRenderer image使用 UIGraphicsImageRenderer 图像检索像素数据无法正常工作
【发布时间】:2019-03-03 15:06:10
【问题描述】:

我有以下代码从UIImage 获取像素数据,这适用于大多数图像,但是当我使用UIGraphicsImageRenderer 创建图像时不起作用。我希望有人知道这个问题的解决方案。

我当前的代码生成了一个简单的图像,但随后访问数据会产生意想不到的结果。

func myDraw() {

    let renderer = UIGraphicsImageRenderer(size: CGSize(width: 200, height: 200))
    let image = renderer.image { context in

        context.cgContext.setFillColor(UIColor.black.cgColor)
        context.cgContext.addRect(CGRect(x: 0, y: 0, width: 100, height: 100))
        context.cgContext.fillPath()

        context.cgContext.setFillColor(UIColor.red.cgColor)
        context.cgContext.addRect(CGRect(x: 100, y: 100, width: 100, height: 100))
        context.cgContext.fillPath()

    }

    let providerData = image.cgImage!.dataProvider!.data
    let data = CFDataGetBytePtr(providerData)!
    var pixels = [PixelData]()
    for i in stride(from: 0, to: 160000-1, by: 4) {
        pixels.append(PixelData(a:data[i+3], r:data[i+0], g:data[i+1], b:data[i+2]))
    }
    self.canvas.image = self.imageFromARGB32Bitmap(pixels: pixels, width: 200, height: 200)

}

我已使用以下代码生成图像以查看它是否正常工作。

func imageFromARGB32Bitmap(pixels: [PixelData], width: Int, height: Int) -> UIImage? {
    guard width > 0 && height > 0 else { return nil }
    guard pixels.count == width * height else { return nil }

    let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
    let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue)
    let bitsPerComponent = 8
    let bitsPerPixel = 32

    var data = pixels // Copy to mutable []
    guard let providerRef = CGDataProvider(data: NSData(bytes: &data,
                                                        length: data.count * MemoryLayout<PixelData>.size)
        )
        else { return nil }

    guard let cgim = CGImage(
        width: width,
        height: height,
        bitsPerComponent: bitsPerComponent,
        bitsPerPixel: bitsPerPixel,
        bytesPerRow: width * MemoryLayout<PixelData>.size,
        space: rgbColorSpace,
        bitmapInfo: bitmapInfo,
        provider: providerRef,
        decode: nil,
        shouldInterpolate: true,
        intent: .defaultIntent
        )
        else { return nil }

    return UIImage(cgImage: cgim)
}

【问题讨论】:

  • 一个超级次要的、无关的点,但您的stride(from: 0, to: 160000-1, by: 4) 似乎暗示您认为stride 将跨越该最终值。但事实并非如此。与stride(from:through:by:) 不同,它在到达之前就停止了。你可能会在你的stride 通话中失去那个-1。
  • @OscarApeland pngData 绝对不是他想要的。这让他离他想要达到的目标更进一步。如果他要检索未压缩的提供程序数据,他需要该图像。

标签: swift uigraphicsrenderer


【解决方案1】:

一些观察:

  1. 您的代码假定 UIGraphicsImageRenderer 生成比例为 1 的图像,而它默认为 0(即您的设备使用的任何比例)。

    相反,强制比例为 1:

    let format = UIGraphicsImageRendererFormat()
    format.scale = 1
    let renderer = UIGraphicsImageRenderer(size: CGSize(width: 200, height: 200), format: format)
    
  2. 这不是这里的问题,但我们必须注意,您的代码只是假设 UIGraphicsImageRendererFormat 的格式将是特定的字节顺序和格式,就像您的 imageFromARGB32Bitmap 一样。如果您查看 Apple Technical Note 1509(您的代码无疑最初是从那里改编而来的),他们并不仅仅假设缓冲区将采用特定格式。当我们想要操作/检查缓冲区时,我们应该 (a) 创建所需格式的上下文,(b) 将我们的图像(或其他)绘制到该上下文,然后我们才能可靠地查看提供程序数据。

  3. imageFromARGB32Bitmap 有效,但它让我有点紧张。

    • MemoryLayout&lt;PixelData&gt;.size的使用:Apple advises:

      当使用不安全指针为 T 的多个实例分配内存时,请使用该类型的 stride 的倍数,而不是其 size。

      所以,我会使用stride。

    • 如果stride 不是您期望的 4 怎么办?我无法想象它永远不会是 4,但数据提供者假设它们将被打包。这是一个小观察,但我可能会明确这个假设。

    • 我们是否 100% 确信取消引用 &amp;data 会给我们一个连续的缓冲区?为了安全起见,我倾向于withContiguousStorageIfAvailable。
       

    例如:

    func imageFromARGB32Bitmap(pixels: [PixelData], width: Int, height: Int) -> UIImage? {
        guard width > 0,
            height > 0,
            pixels.count == width * height else { return nil }
    
        let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
        let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue)
        let bitsPerComponent = 8
        let bitsPerPixel = 32
    
        let stride = MemoryLayout<PixelData>.stride
        assert(stride == 4)
    
        return pixels.withContiguousStorageIfAvailable { bufferPointer -> UIImage? in
            let data = Data(buffer: bufferPointer)
    
            return CGDataProvider(data: data as CFData)
                .flatMap { CGImage(width: width,
                                   height: height,
                                   bitsPerComponent: bitsPerComponent,
                                   bitsPerPixel: bitsPerPixel,
                                   bytesPerRow: width * stride,
                                   space: rgbColorSpace,
                                   bitmapInfo: bitmapInfo,
                                   provider: $0,
                                   decode: nil,
                                   shouldInterpolate: true,
                                   intent: .defaultIntent) }
                .flatMap { UIImage(cgImage: $0) }
            } ?? nil
    }
    

【讨论】:

    【解决方案2】:

    使用render.pngData 代替render.image,然后从UIImage.init(data: pngData) 获取图像

    当我想从UIGraphicsImageRenderer 生成的图像中获取像素颜色时,我遇到了同样的问题。

    代码:

      let render = UIGraphicsImageRenderer(size: .init(width: 414, height: 100))
      // iPhone Xs, so scale is 3, the img actual size is 1242x300
      let img = render.image { ctx in
          ctx.cgContext.setFillColor(UIColor.red.cgColor)
          ctx.cgContext.addRect(.init(x: 0, y: 0, width: 10, height: 10))
          ctx.cgContext.drawPath(using: .fill)
      }
    

    打印img像素颜色信息:

     let pixelData = img.cgImage!.dataProvider!.data
     let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)
     let width = img.cgImage!.width
     let height = img.cgImage!.height
    
     for y in 0..<height {
         for x in 0..<width {
             let pixelInfo = (width * y + x) * 4
    
             print("xy: \(x) \(y) rgba: \(data[pixelInfo]) \(data[pixelInfo + 1]) \(data[pixelInfo + 2]) \(data[pixelInfo + 3])")
         }
     }
    

    打印结果:

     xy: 0 0 rgba: 255 0 0 255
     xy: 1 0 rgba: 255 0 0 255
     xy: 2 0 rgba: 255 0 0 255
     xy: 3 0 rgba: 255 0 0 255
     xy: 4 0 rgba: 255 0 0 255
     ...
     xy: 1240 0 rgba: 0 0 0 0
     xy: 1241 0 rgba: 0 0 0 0
     xy: 0 1 rgba: 0 0 0 0
     ...
     xy: 5 1 rgba: 0 0 0 0
     xy: 6 1 rgba: 255 0 0 255
     xy: 7 1 rgba: 255 0 0 255
     xy: 8 1 rgba: 255 0 0 255
     ...
     xy: 1240 1 rgba: 0 0 0 0
     xy: 1241 1 rgba: 0 0 0 0
     xy: 0 2 rgba: 0 0 0 0
     xy: 1 2 rgba: 0 0 0 0
     ...     
     xy: 11 2 rgba: 0 0 0 0
     xy: 12 2 rgba: 255 0 0 255
     ...
    

    我发现第二行有一些偏移,当我将img宽度414更改为400时,偏移消失了。

    我不知道原因,如果有人知道,请在我的答案下方添加评论。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-11-04
      • 1970-01-01
      • 2013-04-17
      • 2015-07-25
      • 2019-01-11
      • 2012-07-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多