由于您可以访问您的 CVPixelBuffer,您可以直接使用 Accelerate 框架为您进行转换。
我没有在这段代码中检查任何错误、try/catch 语句、没有保护等。您需要确保代码是防错的。
让我们首先定义我们的 BGRA 颜色格式。由于我们有 4 个通道,因此每个像素需要 32 位。我们还定义了我们的 alpha 通道是最后一位。
var bgraSourceFormat = vImage_CGImageFormat(
bitsPerComponent: 8,
bitsPerPixel: 32,
colorSpace: Unmanaged.passRetained(CGColorSpaceCreateDeviceRGB()),
bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.last.rawValue),
version: 0,
decode: nil,
renderingIntent: .defaultIntent
)
现在我们可以定义 BGR 格式。我们需要 3 个通道,所以每个像素 24 位就足够了。我们还定义了这种格式不会有 Alpha 通道。
var bgrDestinationFormat = vImage_CGImageFormat(
bitsPerComponent: 8,
bitsPerPixel: 24,
colorSpace: nil,
bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue),
version: 0,
decode: nil,
renderingIntent: .defaultIntent
)
然后创建转换器...
let bgraToRgbConverter = vImageConverter_CreateWithCGImageFormat(
&bgraSourceFormat,
&bgrDestinationFormat,
nil,
vImage_Flags(kvImagePrintDiagnosticsToConsole),
nil
)
let converter = bgraToRgbConverter!.takeRetainedValue()
现在我们需要从我们现有的像素数据创建一个读取缓冲区,以及一个用于复制我们需要的内容的写入缓冲区。要从 CVPixelBuffer 创建一个读取缓冲区,我们可以这样做:
var bgraBuffer = vImage_Buffer()
let imageFormat = vImageCVImageFormat_CreateWithCVPixelBuffer(cvPixelBuffer).takeRetainedValue()
vImageCVImageFormat_SetColorSpace(imageFormat, CGColorSpaceCreateDeviceRGB())
vImageBuffer_InitWithCVPixelBuffer(
&bgraBuffer,
&bgraSourceFormat,
cvPixelBuffer,
imageFormat,
nil,
vImage_Flags(kvImageNoFlags)
)
并创建空的写入缓冲区...
var bgrBuffer = vImage_Buffer()
vImageBuffer_Init(
&bgrBuffer,
bgraBuffer.height,
bgraBuffer.width,
bgrDestinationFormat.bitsPerPixel,
vImage_Flags(kvImageNoFlags)
)
我们准备好了...让我们告诉加速框架从一种格式转换为另一种格式
vImageConvert_AnyToAny(
converter,
&bgraBuffer,
&bgrBuffer,
nil,
vImage_Flags(kvImagePrintDiagnosticsToConsole)
)
仅此而已。您的 BGRA 现在以 vImage_Buffer 的形式转换为 BGR。我们可以通过直接读取像素数据来检查我们是否完成了我们想要的事情。首先,我们需要访问数据:
let bgraData = bgraBuffer.data!.assumingMemoryBound(to: UInt8.self)
let bgrData = bgrBuffer.data!.assumingMemoryBound(to: UInt8.self)
现在,我们可以打印第一个和第二个像素
print(bgraData[0], bgraData[1], bgraData[2], bgraData[3])
print(bgrData[0], bgrData[1], bgrData[2])
print(bgraData[4], bgraData[5], bgraData[6], bgraData[7])
print(bgrData[3], bgrData[4], bgrData[5])
这是我在 Playground 中用于测试的 png 图像看到的输出:
249 244 234 255
249 244 234
251 242 233 255
251 242 233
如您所见,像素是在没有 Alpha 通道的情况下复制的。如果您要使用任何循环,请注意您的 for 循环,因为我们现在有 3 个通道。
如果您正在开发游戏 + 在每一帧上都这样做,请尝试让您的对象保持活力。加速格式定义,写入缓冲区和转换器对于相同的图像大小 + 格式永远不会改变,因此它们可以创建一次并保存在内存中以供将来使用。
看起来您正在返回一个 Data 对象。您可以将UnsafeMutablePointer 构造转换为您需要的任何内容。
或者您也可以使用加速的vImageBuffer_CopyToCVPixelBuffer 方法将vImage_Buffer 转换回CVPixelBuffer(如果需要)。 vImage_Buffer 有很多转换器,一个肯定会满足您的需求。检查this link 以获取有关如何使用复制到像素缓冲区方法的更多信息。 This link 有一个很好的用法示例。
编辑:您的 CVPixelBuffer 可能已被填充。
由于硬件要求,您的图像可能有一个填充,以确保缓冲区的宽度和高度是 16 的倍数。这也将导致您的 vImage_Buffer 结构中的填充。如果您需要循环,但只需要访问/更新单个像素,您可以使用 Accelerate 的功能来提高速度。检查此link 以了解可能的方法,页面末尾有很好的示例。
要完全读取数据,你可以这样写:
var bgrData = bgrBuffer.data!.assumingMemoryBound(to: UInt8.self)
print(bgrBuffer.width, bgrBuffer.height, bgrBuffer.rowBytes)
for _ in 0 ..< Int(bgrBuffer.height) {
for x in 0 ..< Int(bgrBuffer.width) {
let b = (bgrData + x * 3 + 0).pointee
let g = (bgrData + x * 3 + 1).pointee
let r = (bgrData + x * 3 + 2).pointee
print(b, g, r)
}
bgrData = bgrData.advanced(by: bgrBuffer.rowBytes)
}
这将确保您正在读取全宽像素,但在最后传递填充。