Получение данных пикселей не работает правильно с изображением UIGraphicsImageRenderer
У меня есть следующий код, чтобы получить данные пикселей из 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)
}
1 ответ
Несколько замечаний:
Ваш код предполагает, что
UIGraphicsImageRenderer
генерирует изображения с масштабом 1, тогда как по умолчанию он равен 0 (т.е. независимо от масштаба, используемого вашим устройством).Вместо этого настройте масштаб на 1:
let format = UIGraphicsImageRendererFormat() format.scale = 1 let renderer = UIGraphicsImageRenderer(size: CGSize(width: 200, height: 200), format: format)
Это не проблема здесь, но мы должны отметить, что ваш код предполагает, что формат
UIGraphicsImageRendererFormat
будет определенный порядок байтов и формат, как и вашimageFromARGB32Bitmap
, Если вы посмотрите на Техническую ноту Apple 1509(из которой ваш код, несомненно, был изначально адаптирован), они не просто предполагают, что буфер будет в определенном формате. Когда мы хотим манипулировать / исследовать буфер, мы должны (а) создать контекст желаемого формата, (б) нарисовать наше изображение (или что-то еще) в этом контексте, и только тогда мы сможем надежно посмотреть на данные поставщика.imageFromARGB32Bitmap
работает, но это заставляет меня немного нервничать.Использование
MemoryLayout<PixelData>.size
Apple советуетПри выделении памяти для нескольких экземпляров T с использованием небезопасного указателя, используйте кратные типа
stride
вместо его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: $0, decode: nil, shouldInterpolate: true, intent: .defaultIntent) } .flatMap { UIImage(cgImage: $0) } } ?? 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, смещения исчезли.
Я не знаю причины, и если кто-то знал, пожалуйста, добавьте комментарий под моим ответом.