As you have access to your CVPixelBuffer, you can directly use Accelerate framework to do the convertion for you.
I am not checking for any errors, try/catch statements, no guards, etc, in this code. You will need to make sure that the code is error proofed.
Lets first define our BGRA color format. As we have 4 channels, we need 32 bits per pixel. We are also defining that our alpha channel is the last bit.
var bgraSourceFormat = vImage_CGImageFormat(
bitsPerComponent: 8,
bitsPerPixel: 32,
colorSpace: Unmanaged.passRetained(CGColorSpaceCreateDeviceRGB()),
bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.last.rawValue),
version: 0,
decode: nil,
renderingIntent: .defaultIntent
)
Now we can define a BGR format. We need 3 channels, so 24 bits per pixel is enough. We are also defining that this format will not have an alpha channel.
var bgrDestinationFormat = vImage_CGImageFormat(
bitsPerComponent: 8,
bitsPerPixel: 24,
colorSpace: nil,
bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue),
version: 0,
decode: nil,
renderingIntent: .defaultIntent
)
And create the converter...
let bgraToRgbConverter = vImageConverter_CreateWithCGImageFormat(
&bgraSourceFormat,
&bgrDestinationFormat,
nil,
vImage_Flags(kvImagePrintDiagnosticsToConsole),
nil
)
let converter = bgraToRgbConverter!.takeRetainedValue()
Now we need to create a read buffer from our existing pixel data, and a write buffer for copying what we need. To create a read buffer from CVPixelBuffer, we can do something like this:
var bgraBuffer = vImage_Buffer()
let imageFormat = vImageCVImageFormat_CreateWithCVPixelBuffer(cvPixelBuffer).takeRetainedValue()
vImageCVImageFormat_SetColorSpace(imageFormat, CGColorSpaceCreateDeviceRGB())
vImageBuffer_InitWithCVPixelBuffer(
&bgraBuffer,
&bgraSourceFormat,
cvPixelBuffer,
imageFormat,
nil,
vImage_Flags(kvImageNoFlags)
)
And create the empty write buffer...
var bgrBuffer = vImage_Buffer()
vImageBuffer_Init(
&bgrBuffer,
bgraBuffer.height,
bgraBuffer.width,
bgrDestinationFormat.bitsPerPixel,
vImage_Flags(kvImageNoFlags)
)
We are ready... Let's tell the accelerate framework to convert from one format to another
vImageConvert_AnyToAny(
converter,
&bgraBuffer,
&bgrBuffer,
nil,
vImage_Flags(kvImagePrintDiagnosticsToConsole)
)
And that's all. Your BGRA is now converted to BGR as a vImage_Buffer. We can check if we accomplished what we wanted, by directly reading from the pixel data. First, we need to get access to the data:
let bgraData = bgraBuffer.data!.assumingMemoryBound(to: UInt8.self)
let bgrData = bgrBuffer.data!.assumingMemoryBound(to: UInt8.self)
Now, we can print the first and second pixels
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])
This is the output I'm seeing from a png image I used in playgrounds for testing:
249 244 234 255
249 244 234
251 242 233 255
251 242 233
As you can see, pixels are copied without the alpha channel. Be careful on your for loops, if you are going to use any, as we now have 3 channels.
If you are developing a game + doing this on each frame, try keeping your objects alive. Accelerate format definitions, write buffer and converter never change for same image size + format, so they can be created once and kept in memory for future use.
Looks like you are returning a Data object. You can convert UnsafeMutablePointer construct to whatever you need.
Or you can also convert vImage_Buffer back to CVPixelBuffer (if you need) using accelerate's vImageBuffer_CopyToCVPixelBuffer method. vImage_Buffer has a lot of converters, one definitely will suit your needs. Check this link for more information on how to use copy to pixel buffer method. This link has a great example on the usage.
Edit: Your CVPixelBuffer might be padded.
Because of the hardware requirements your image might have a padding to make sure that the buffer width and height is a multiple of 16. This will also result in a padding in your vImage_Buffer structures. If you need to loop, but only need to access/update single pixels, you can use Accelerate's functions for speed. Check this link for possible methods and there are great examples at the end of the page.
To read the data fully, you can write something like this:
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)
}
This will make sure that you are reading full width pixels, but pass the padding at the end.