Получение данных пикселей не работает правильно с изображением UIGraphicsImageRenderer - PullRequest
0 голосов
/ 03 марта 2019

У меня есть следующий код для получения данных пикселей от 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 Ответ

0 голосов
/ 03 марта 2019

Несколько замечаний:

  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 (из которой ваш код, несомненно, был изначально адаптирован), они не просто предполагают, что буфер будет в определенном формате.Когда мы хотим манипулировать / исследовать буфер, мы должны (а) создать контекст желаемого формата, (б) нарисовать наше изображение (или что-то еще) в этом контексте, и только тогда мы сможем надежно посмотреть на данные поставщика.

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