It turns out the problem was caused by how we loaded the cube data. Previously, we did it like this:
let cubeImage: CGImage = ...
// render cube image into a 32-bit float context, since that's the data format needed by CIColorCube
let pixelData = UnsafeMutablePointer<simd_float4>.allocate(capacity: cubeImage.width * cubeImage.height)
let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.floatComponents.rawValue | CGBitmapInfo.byteOrder32Little.rawValue
let colorSpace = cubeImage.colorSpace ?? CGColorSpace.sRGBColorSpace
guard let bitmapContext = CGContext(data: pixelData,
width: cubeImage.width,
height: cubeImage.height,
bitsPerComponent: MemoryLayout<simd_float4.Scalar>.size * 8,
bytesPerRow: MemoryLayout<simd_float4>.size * cubeImage.width,
space: colorSpace,
bitmapInfo: bitmapInfo)
else {
assertionFailure("Failed to create bitmap context for conversion")
}
bitmapContext.draw(cubeImage, in: CGRect(x: 0, y: 0, width: cubeImage.width, height: cubeImage.height))
let data = Data(bytesNoCopy: pixelData, count: bitmapContext.bytesPerRow * bitmapContext.height, deallocator: .free)
// pass data to filter
Note that we pre-allocated the pixelData
buffer and gave it to the CGContext
to render the cube image into it. It seems that data was corrupted or released too early in some cases, causing the erroneous behavior described above, even though we assumed that Data(bytesNoCopy:...)
would take ownership of the data.
To fix this, we let CGContext
create its own buffer and copy the cube data after the draw
:
let cubeImage: CGImage = ...
// render cube image into a 32-bit float context, since that's the data format needed by CIColorCube
let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.floatComponents.rawValue | CGBitmapInfo.byteOrder32Little.rawValue
let colorSpace = cubeImage.colorSpace ?? CGColorSpace.sRGBColorSpace
guard let bitmapContext = CGContext(data: nil,
width: cubeImage.width,
height: cubeImage.height,
bitsPerComponent: MemoryLayout<simd_float4.Scalar>.size * 8,
bytesPerRow: MemoryLayout<simd_float4>.size * cubeImage.width,
space: colorSpace,
bitmapInfo: bitmapInfo)
else {
assertionFailure("Failed to create bitmap context for conversion")
}
bitmapContext.draw(cubeImage, in: CGRect(x: 0, y: 0, width: cubeImage.width, height: cubeImage.height))
guard let pixelData = bitmapContext.data else {
assertionFailure("Failed to get cube data")
}
let data = Data(bytes: pixelData, count: bitmapContext.bytesPerRow * bitmapContext.height)
// pass data to filter
This was also filed as FB11736373, together with a demo project.