从缓冲区创建的NSData创建UIImage返回nil?

3
我正在尝试通过获取 CGImage,获取每个像素并将其减去 0xa,然后将每个像素保存到一个新的缓冲区来使 UIImage 变暗。但是,当我尝试将该缓冲区作为图像重新加载时,函数 [用于创建 CGImage] 返回nil。这意味着我在代码中做错了什么(我不会感到惊讶)。我期望它与缓冲区格式不正确或其他相关。是否有熟悉核心图形的人可以帮助我找出错误?
var provider = CGImageGetDataProvider(imageArray[imageNumber]?.CGImage) //Get data provider for image in an array at index No. imageNumber
    let data = CGDataProviderCopyData(provider)
    var buffer = [Byte](count: CFDataGetLength(data), repeatedValue: 0) //create buffer for image data
    CFDataGetBytes(data, CFRangeMake(0, CFDataGetLength(data)), &buffer) //load the image's bytes into buffer
    var newBuffer = [Byte](count:buffer.count, repeatedValue: 0) //Going to make some changes, need a place to save new image
    var index = 0
    for aByte in buffer {
        if aByte > 0xa && aByte != 0xff {
       newBuffer[index] = (aByte - 0xa) //subtract 0xa from buffer, where possible
        }
        else{
            newBuffer[index] = (0xff) //I *think* there is no alpha channel, but every fourth byte in buffer is 0xff
        }
        index += 1
    }
    var coreGraphicsImage = CGImageCreateWithJPEGDataProvider(CGDataProviderCreateWithCFData( CFDataCreate(kCFAllocatorDefault, newBuffer, newBuffer.count)), nil, true, kCGRenderingIntentDefault) //create CGimage from newBuffer.RETURNS NIL!
    let myImage = UIImage(CGImage: coreGraphicsImage) //also nil
    imageView.image = myImage

不要逐像素地进行操作,而是创建一个填充有颜色(0xa)的图像,并从原始图像中减去它。您可以使用UIRectFill将图像填充为一种颜色。使用UIColor上的setFill()设置填充颜色。然后,执行减法操作- https://dev59.com/vGHVa4cB1Zd3GeqPnX47 - Anna Dickinson
1个回答

4
一些想法:
  1. 在你编写自己的图像处理程序之前,你可能考虑应用Core Image filters中的一个。它可能会让你的生活更轻松,并且可能会给你更精细的结果。仅通过减少每个通道的一些固定数字来引入你没有预期的失真(例如颜色偏移、饱和度变化等)。

  2. 如果你要这样做,我会对直接获取数据提供者并按原样操作它持怀疑态度。你可能需要为图像提供者的性质(每个通道的位数、ARGB与RGBA等)引入各种条件逻辑。如果你查看苹果在Q&A #1509中的示例,你可以取而代之地检索预定格式的像素缓冲区(在他们的示例中是ARGB、每个组件8位、每个像素四个字节)。

    这个例子有点过时,但它展示了如何创建一个预定格式的上下文,然后将一个图像绘制到该上下文中。然后,你可以操作那些数据,并使用你自己的提供者创建一个使用此预定格式的新图像,而不是JPEG数据提供者。

  3. 你代码示例中最重要的问题是,你试图使用CGImageCreateWithJPEGDataProvider,它期望一个“提供JPEG编码数据的数据提供者”。但你的提供者可能不是JPEG编码的,所以它会失败。如果你要使用原始图像提供者格式的数据,则必须使用CGImageCreate创建一个新的图像(手动提供宽度、高度、每个组件的位数、每个像素的位数、每行字节数、颜色空间、位图信息等)。

  4. 你的程序还存在一些较小的问题:

    • 你指出你看到每四个字节中有一个0xff。回答你在代码注释中的问题,那无疑是alpha通道。(你可以通过检查原始CGImageRefCGBitmapInfo来确认这一点。)你可能没有使用alpha通道,但它显然存在。

    • 如果通道的值小于0x0a,你的程序将其设置为0xff。这显然不是你的意图(例如,如果像素是黑色,你会使它变成白色!)。你应该检查一下这个逻辑。

    • 在我的测试中,迭代/操作Byte数组的这种方法非常慢。我不完全确定原因是什么,但如果你直接操作字节缓冲区,它会快得多。

下面是一个创建预定格式上下文(RGBA,每个分量8位等)的常规例程,对其进行操作(我将其转换为黑白,但您可以根据需要进行其他操作),并从中创建新图像。因此,在Swift 2中:

func blackAndWhiteImage(image: UIImage) -> UIImage? {
    // get information about image

    let imageref = image.CGImage
    let width = CGImageGetWidth(imageref)
    let height = CGImageGetHeight(imageref)

    // create new bitmap context

    let bitsPerComponent = 8
    let bytesPerPixel = 4
    let bytesPerRow = width * bytesPerPixel
    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let bitmapInfo = Pixel.bitmapInfo
    let context = CGBitmapContextCreate(nil, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo)

    // draw image to context

    let rect = CGRectMake(0, 0, CGFloat(width), CGFloat(height))
    CGContextDrawImage(context, rect, imageref)

    // manipulate binary data

    let pixels = UnsafeMutablePointer<Pixel>(CGBitmapContextGetData(context))

    for row in 0 ..< height {
        for col in 0 ..< width {
            let offset = Int(row * width + col)

            let red = Float(pixels[offset].red)
            let green = Float(pixels[offset].green)
            let blue = Float(pixels[offset].blue)
            let alpha = pixels[offset].alpha
            let luminance = UInt8(0.2126 * red + 0.7152 * green + 0.0722 * blue)
            pixels[offset] = Pixel(red: luminance, green: luminance, blue: luminance, alpha: alpha)
        }
    }

    // return the image

    let outputImage = CGBitmapContextCreateImage(context)!
    return UIImage(CGImage: outputImage, scale: image.scale, orientation: image.imageOrientation)
}

哪里

struct Pixel: Equatable {
    private var rgba: UInt32

    var red: UInt8 {
        return UInt8((rgba >> 24) & 255)
    }

    var green: UInt8 {
        return UInt8((rgba >> 16) & 255)
    }

    var blue: UInt8 {
        return UInt8((rgba >> 8) & 255)
    }

    var alpha: UInt8 {
        return UInt8((rgba >> 0) & 255)
    }

    init(red: UInt8, green: UInt8, blue: UInt8, alpha: UInt8) {
        rgba = (UInt32(red) << 24) | (UInt32(green) << 16) | (UInt32(blue) << 8) | (UInt32(alpha) << 0)
    }

    static let bitmapInfo = CGImageAlphaInfo.PremultipliedLast.rawValue | CGBitmapInfo.ByteOrder32Little.rawValue
}

func ==(lhs: Pixel, rhs: Pixel) -> Bool {
    return lhs.rgba == rhs.rgba
}

或者,在 Swift 3 中:

func blackAndWhite(image: UIImage) -> UIImage? {
    // get information about image

    let imageref = image.cgImage!
    let width = imageref.width
    let height = imageref.height

    // create new bitmap context

    let bitsPerComponent = 8
    let bytesPerPixel = 4
    let bytesPerRow = width * bytesPerPixel
    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let bitmapInfo = Pixel.bitmapInfo
    let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo)!

    // draw image to context

    let rect = CGRect(x: 0, y: 0, width: CGFloat(width), height: CGFloat(height))
    context.draw(imageref, in: rect)

    // manipulate binary data

    guard let buffer = context.data else {
        print("unable to get context data")
        return nil
    }

    let pixels = buffer.bindMemory(to: Pixel.self, capacity: width * height)

    for row in 0 ..< height {
        for col in 0 ..< width {
            let offset = Int(row * width + col)

            let red = Float(pixels[offset].red)
            let green = Float(pixels[offset].green)
            let blue = Float(pixels[offset].blue)
            let alpha = pixels[offset].alpha
            let luminance = UInt8(0.2126 * red + 0.7152 * green + 0.0722 * blue)
            pixels[offset] = Pixel(red: luminance, green: luminance, blue: luminance, alpha: alpha)
        }
    }

    // return the image

    let outputImage = context.makeImage()!
    return UIImage(cgImage: outputImage, scale: image.scale, orientation: image.imageOrientation)
}

struct Pixel: Equatable {
    private var rgba: UInt32

    var red: UInt8 {
        return UInt8((rgba >> 24) & 255)
    }

    var green: UInt8 {
        return UInt8((rgba >> 16) & 255)
    }

    var blue: UInt8 {
        return UInt8((rgba >> 8) & 255)
    }

    var alpha: UInt8 {
        return UInt8((rgba >> 0) & 255)
    }

    init(red: UInt8, green: UInt8, blue: UInt8, alpha: UInt8) {
        rgba = (UInt32(red) << 24) | (UInt32(green) << 16) | (UInt32(blue) << 8) | (UInt32(alpha) << 0)
    }

    static let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Little.rawValue

    static func ==(lhs: Pixel, rhs: Pixel) -> Bool {
        return lhs.rgba == rhs.rgba
    }
}

你的回答非常好并且受到赞赏,但是我想指出这更多是一个关于操作缓冲区、十六进制值和像素的学术练习--我知道实际结果会被扭曲且此函数效率低下--它需要在我的2012年iMac上运行两分钟!;) 话虽如此,你的回答非常有用,特别是当我把这个实验转化为一个实际产品时。不过现在我想知道创建CGImage返回nil的原因是什么?我是否需要以某种方式标记缓冲区为图像数据,或者我的控制流程导致了无效的十六进制值? - PopKernel
2
  1. 你得到nil的原因很可能是由于你创建的提供程序中不包含“JPEG编码数据”。
  2. 由于Array<Byte>中存在某些问题,它变得非常缓慢:当我使用UnsafeMutablePointer<Byte>时,速度提高了两个数量级。请参见修订后的答案。
- Rob
谢谢,这很完美! - PopKernel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接