0

I'm using CVPixelBufferCreateWithBytes(_:_:_:_:_:_:_:_:_:_:) to read a 24-bit BMP from disk:

var data = try! Data(contentsOf: url.appendingPathExtension(for: .bmp))
data.withUnsafeMutableBytes { (unsafeMutableRawBufferPointer: UnsafeMutableRawBufferPointer) in
  var pixelBuffer: CVPixelBuffer?
  CVPixelBufferCreateWithBytes(
    kCFAllocatorDefault,
    imageWidth,
    imageHeight,
    kCVPixelFormatType_24RGB,
    unsafeMutableRawBufferPointer.baseAddress!,
    bytesPerRow,
    nil, nil, nil,
    &pixelBuffer
  )
}

I need to convert it to JPEG, but all the methods I've tried leak, i.e.:

try! UIImage(ciImage: CIImage(cvPixelBuffer: pixelBuffer!))
    .jpegData(compressionQuality: 0.0)!
    .write(to: someUrl)

and:

try! CIContext().writeJPEGRepresentation(
    of: CIImage(cvPixelBuffer: pixelBuffer!),
    to: someUrl,
    colorSpace: CGColorSpaceCreateDeviceRGB()
)

autoreleasepool doesn't help:

autoreleasepool {
  var pixelBuffer: CVPixelBuffer?
  CVPixelBufferCreateWithBytes(
    kCFAllocatorDefault,
    imageWidth,
    imageHeight,
    kCVPixelFormatType_24RGB,
    unsafeMutableRawBufferPointer.baseAddress!,
    bytesPerRow,
    nil, nil, nil,
    &pixelBuffer
  )
  try! UIImage(ciImage: CIImage(cvPixelBuffer: pixelBuffer!))
    .jpegData(compressionQuality: 0.0)!
    .write(to: someUrl)
}

And none of the *Release methods are available, they produce compiler errors like: 'CFRelease' is unavailable: Core Foundation objects are automatically memory managed.

The issue seems to be the pointer gets retained when any of the JPEG-related methods get called. This leaks:

let uiImage = UIImage(ciImage: CIImage(cvPixelBuffer: pixelBuffer!))
    .jpegData(compressionQuality: 0.0)!

But this doesn't:

let uiImage = UIImage(ciImage: CIImage(cvPixelBuffer: pixelBuffer!))

Same with Core Image. This doesn't leak:

CIContext().createCGImage(CIImage(cvPixelBuffer: pixelBuffer!), from: CGRect(x: 0, y: 0, width: imageWidth, height: imageHeight))

But this does:

CIContext().writeJPEGRepresentation(
    of: CIImage(cvPixelBuffer: pixelBuffer!),
    to: someUrl,
    colorSpace: CGColorSpaceCreateDeviceRGB()
)

I guess I need to release the memory by passing CVPixelBufferReleaseBytesCallback to seems to CVPixelBufferCreateWithBytes, but it's not clear to me how to use it in this case. Most examples suggests doing something like:

let releaseCallBack: CVPixelBufferReleaseBytesCallback = { _, pointer in
  if let pointer = pointer {
     free(UnsafeMutableRawPointer(mutating: pointer))
  }
}

But it makes the autoreleasepool call to throw because it's trying to free memory that's not there any more, and removing it makes other objects to keep leaking. Manually calling deallocate on the point doesn't help either

Tae
  • 1,665
  • 5
  • 24
  • 45
  • Have you tried loading the bitmap into a `CIImage` directly via `CIImage(contentsOf: url)`? It might not be necessary to create a `CVPixelBuffer` first. Or do you need that for something else? – Frank Rupprecht Sep 13 '22 at 18:38
  • @FrankRupprecht I need the `CVPixelBuffer` because the file is a 24-bit BMP, which isn't supported by Core Image. Maybe there's another API I can use to load it? [Here is the context](https://stackoverflow.com/questions/71956907/is-it-possible-to-load-a-24-bits-bmp-with-a-native-ios-api) – Tae Sep 14 '22 at 07:20
  • Ah yes, I remember. Are you doing all the conversions inside the `withUnsafeMutableBytes` block? – Frank Rupprecht Sep 14 '22 at 17:36
  • @FrankRupprecht yes, I tried moving things outside, but I didn't see any improvement, maybe I missed something – Tae Sep 15 '22 at 07:14

0 Answers0