检索像素数据不能正确使用 UIGraphicsImageRenderer 图像
Retrieving pixel data not working correctly with UIGraphicsImageRenderer image
我有以下代码从 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)
}
几点观察:
您的代码假定 UIGraphicsImageRenderer
生成比例为 1 的图像,而它默认为 0(即您的设备使用的任何比例)。
相反,强制比例为 1:
let format = UIGraphicsImageRendererFormat()
format.scale = 1
let renderer = UIGraphicsImageRenderer(size: CGSize(width: 200, height: 200), format: format)
这不是这里的问题,但我们必须注意,您的代码只是假设 UIGraphicsImageRendererFormat
的格式将是特定的字节顺序和格式,您的 imageFromARGB32Bitmap
.如果你看看 Apple Technical Note 1509(你的代码最初无疑是从它改编的),他们不只是假设缓冲区将采用特定格式。当我们想要 manipulate/examine 缓冲区时,我们应该 (a) 创建所需格式的上下文,(b) 将我们的图像(或其他)绘制到该上下文,只有这样我们才能可靠地查看提供者数据.
imageFromARGB32Bitmap
有效,但让我有点紧张。
使用MemoryLayout<PixelData>.size
:Apple advises:
When allocating memory for multiple instances of T using an unsafe pointer, use a multiple of the type’s stride
instead of its size
.
所以,我会使用 stride
。
如果 stride
不是您期望的 4 怎么办?我无法想象它永远不会是 4,但数据提供者假设它们将被打包。这是一个次要的观察,但我可能会明确这个假设。
我们能 100% 确定取消引用 &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: [=11=],
decode: nil,
shouldInterpolate: true,
intent: .defaultIntent) }
.flatMap { UIImage(cgImage: [=11=]) }
} ?? nil
}
使用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 时,偏移消失了。
我不知道原因,如果有人知道,请在我的回答下方添加评论。
我有以下代码从 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)
}
几点观察:
您的代码假定
UIGraphicsImageRenderer
生成比例为 1 的图像,而它默认为 0(即您的设备使用的任何比例)。相反,强制比例为 1:
let format = UIGraphicsImageRendererFormat() format.scale = 1 let renderer = UIGraphicsImageRenderer(size: CGSize(width: 200, height: 200), format: format)
这不是这里的问题,但我们必须注意,您的代码只是假设
UIGraphicsImageRendererFormat
的格式将是特定的字节顺序和格式,您的imageFromARGB32Bitmap
.如果你看看 Apple Technical Note 1509(你的代码最初无疑是从它改编的),他们不只是假设缓冲区将采用特定格式。当我们想要 manipulate/examine 缓冲区时,我们应该 (a) 创建所需格式的上下文,(b) 将我们的图像(或其他)绘制到该上下文,只有这样我们才能可靠地查看提供者数据.imageFromARGB32Bitmap
有效,但让我有点紧张。使用
MemoryLayout<PixelData>.size
:Apple advises:When allocating memory for multiple instances of T using an unsafe pointer, use a multiple of the type’s
stride
instead of itssize
.所以,我会使用
stride
。如果
stride
不是您期望的 4 怎么办?我无法想象它永远不会是 4,但数据提供者假设它们将被打包。这是一个次要的观察,但我可能会明确这个假设。我们能 100% 确定取消引用
&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: [=11=], decode: nil, shouldInterpolate: true, intent: .defaultIntent) } .flatMap { UIImage(cgImage: [=11=]) } } ?? nil }
使用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 时,偏移消失了。
我不知道原因,如果有人知道,请在我的回答下方添加评论。