一些想法:
在你编写自己的图像处理程序之前,你可能考虑应用Core Image filters中的一个。它可能会让你的生活更轻松,并且可能会给你更精细的结果。仅通过减少每个通道的一些固定数字来引入你没有预期的失真(例如颜色偏移、饱和度变化等)。
如果你要这样做,我会对直接获取数据提供者并按原样操作它持怀疑态度。你可能需要为图像提供者的性质(每个通道的位数、ARGB与RGBA等)引入各种条件逻辑。如果你查看苹果在Q&A #1509中的示例,你可以取而代之地检索预定格式的像素缓冲区(在他们的示例中是ARGB、每个组件8位、每个像素四个字节)。
这个例子有点过时,但它展示了如何创建一个预定格式的上下文,然后将一个图像绘制到该上下文中。然后,你可以操作那些数据,并使用你自己的提供者创建一个使用此预定格式的新图像,而不是JPEG数据提供者。
你代码示例中最重要的问题是,你试图使用CGImageCreateWithJPEGDataProvider
,它期望一个“提供JPEG编码数据的数据提供者”。但你的提供者可能不是JPEG编码的,所以它会失败。如果你要使用原始图像提供者格式的数据,则必须使用CGImageCreate
创建一个新的图像(手动提供宽度、高度、每个组件的位数、每个像素的位数、每行字节数、颜色空间、位图信息等)。
你的程序还存在一些较小的问题:
你指出你看到每四个字节中有一个0xff
。回答你在代码注释中的问题,那无疑是alpha通道。(你可以通过检查原始CGImageRef
的CGBitmapInfo
来确认这一点。)你可能没有使用alpha通道,但它显然存在。
如果通道的值小于0x0a
,你的程序将其设置为0xff
。这显然不是你的意图(例如,如果像素是黑色,你会使它变成白色!)。你应该检查一下这个逻辑。
在我的测试中,迭代/操作Byte
数组的这种方法非常慢。我不完全确定原因是什么,但如果你直接操作字节缓冲区,它会快得多。
下面是一个创建预定格式上下文(RGBA,每个分量8位等)的常规例程,对其进行操作(我将其转换为黑白,但您可以根据需要进行其他操作),并从中创建新图像。因此,在Swift 2中:
func blackAndWhiteImage(image: UIImage) -> UIImage? {
let imageref = image.CGImage
let width = CGImageGetWidth(imageref)
let height = CGImageGetHeight(imageref)
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)
let rect = CGRectMake(0, 0, CGFloat(width), CGFloat(height))
CGContextDrawImage(context, rect, imageref)
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)
}
}
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? {
let imageref = image.cgImage!
let width = imageref.width
let height = imageref.height
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)!
let rect = CGRect(x: 0, y: 0, width: CGFloat(width), height: CGFloat(height))
context.draw(imageref, in: rect)
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)
}
}
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
}
}