在幕后,UIGraphicsBeginImageContext
创建一个CGBitmapContext
。您可以使用以下方式访问上下文的像素存储CGBitmapContextGetData
。这种方法的问题在于UIGraphicsBeginImageContext
函数选择用于存储像素数据的字节顺序和颜色空间。这些选择(特别是字节顺序)可能会在未来版本的 iOS 中(甚至在不同的设备上)发生变化。
因此,让我们直接创建上下文CGBitmapContextCreate
,这样我们就可以确定字节顺序和颜色空间。
在我的游乐场中,我添加了一个名为的测试图像[email protected]
.
import XCPlayground
import UIKit
let image = UIImage(named: "pic.jpeg")!
XCPCaptureValue("image", value: image)
以下是我们创建位图上下文的方式,考虑到图像比例(您在问题中没有这样做):
let rowCount = Int(image.size.height * image.scale)
let columnCount = Int(image.size.width * image.scale)
let stride = 64 * ((columnCount * 4 + 63) / 64)
let context = CGBitmapContextCreate(nil, columnCount, rowCount, 8, stride,
CGColorSpaceCreateDeviceRGB(),
CGBitmapInfo.ByteOrder32Little.rawValue |
CGImageAlphaInfo.PremultipliedLast.rawValue)
接下来,我们调整坐标系以匹配UIGraphicsBeginImageContextWithOptions
就可以了,这样我们就可以正确、轻松地绘制图像:
CGContextTranslateCTM(context, 0, CGFloat(rowCount))
CGContextScaleCTM(context, image.scale, -image.scale)
UIGraphicsPushContext(context!)
image.drawAtPoint(CGPointZero)
UIGraphicsPopContext()
注意UIImage.drawAtPoint
takes image.orientation
考虑到。CGContextDrawImage
才不是。
现在让我们从上下文中获取指向原始像素数据的指针。如果我们定义一个结构来访问每个像素的各个组件,则代码会更清晰:
struct Pixel {
var a: UInt8
var b: UInt8
var g: UInt8
var r: UInt8
}
let pixels = UnsafeMutablePointer<Pixel>(CGBitmapContextGetData(context))
请注意,顺序Pixel
成员被定义为匹配我在中设置的特定位bitmapInfo
论证CGBitmapContextCreate
.
现在我们可以循环像素。请注意,我们使用rowCount
and columnCount
,如上计算,访问所有像素,无论图像比例如何:
for y in 0 ..< rowCount {
if y % 2 == 0 {
for x in 0 ..< columnCount {
let pixel = pixels.advancedBy(y * stride / sizeof(Pixel.self) + x)
pixel.memory.r = 255
}
}
}
最后,我们从上下文中得到一个新图像:
let newImage = UIImage(CGImage: CGBitmapContextCreateImage(context)!, scale: image.scale, orientation: UIImageOrientation.Up)
XCPCaptureValue("newImage", value: newImage)
结果,在我的游乐场的时间表中:
![timeline screenshot](https://i.stack.imgur.com/0dnsy.png)
最后,请注意,如果您的图像很大,则逐像素浏览可能会很慢。如果您能找到一种使用 Core Image 或 GPUImage 执行图像操作的方法,速度会快很多。如果做不到这一点,使用 Objective-C 并手动对其进行矢量化(使用 NEON 内在函数)可能会带来很大的提升。