Reputation: 53
How can I convert two bytes (UInt8) to a half-precision (16-bit) Float in Swift, such as needed when reading the output of CIAreaHistogram with the kCIFormatRGBAh, as in the following example:
func areaHistogram(image : UIImage) {
let inputImage = CIImage(image: image)
let totalBytes : Int = bpp * BINS //8 * 64 for example
let bitmap : UnsafeMutablePointer<Void> = calloc(totalBytes, bpp)
let filter = CIFilter(name: "CIAreaHistogram")!
filter.setValue(inputImage, forKey: kCIInputImageKey)
filter.setValue(CIVector(x: 0, y: 0, z: image.size.width, w: image.size.height), forKey: kCIInputExtentKey)
filter.setValue(BINS, forKey: "inputCount")
filter.setValue(1, forKey: "inputScale")
let myEAGLContext = EAGLContext(API: .OpenGLES2)
let options = [kCIContextWorkingColorSpace : kCFNull]
let context : CIContext = CIContext(EAGLContext: myEAGLContext, options: options)
context.render(filter.outputImage!, toBitmap: bitmap, rowBytes: totalBytes, bounds: filter.outputImage!.extent, format: kCIFormatRGBAh, colorSpace: CGColorSpaceCreateDeviceRGB())
let bytes = UnsafeBufferPointer<UInt8>(start: UnsafePointer<UInt8>(bitmap), count: bpp * BINS)
//HOW TO CONVERT TWO CONSECUTIVE BYTES AS 16-BIT FLOATS?
//THIS CODE DOES NOT WORK (I guess because Float in Swift is 32-bit):
for var i=0; i < self.bpp * self.BINS; i+=self.bpp {
let bitsR = UnsafePointer<Float._BitsType>(self.queryHist!)[i+0].bigEndian
let R = Float( Float._fromBitPattern(bitsR) )
let bitsG = UnsafePointer<Float._BitsType>(self.queryHist!)[i+2].bigEndian
let G = Float( Float._fromBitPattern(bitsG) )
let bitsB = UnsafePointer<Float._BitsType>(self.queryHist!)[i+4].bigEndian
let B = Float( Float._fromBitPattern(bitsB) )
print("R/G/B = \(R) \(G) \(B)")
}
free(bitmap)
}
Upvotes: 1
Views: 3681
Reputation: 539685
There is no 16-bit floating point type in Swift, but you can convert
the results to 32-bit floating point numbers (Float
).
This thread
contains a lot of information about the Half-precision floating-point format, and various conversion methods. The crucial hint however is in Ian Ollman's answer:
On OS X / iOS, you can use
vImageConvert_PlanarFtoPlanar16F
andvImageConvert_Planar16FtoPlanarF
. See Accelerate.framework.
Ian did provide no code however, so here is a possible implementation in Swift:
func areaHistogram(image : UIImage) {
let inputImage = CIImage(image: image)
let totalBytes : Int = bpp * BINS //8 * 64 for example
let bitmap = calloc(1, totalBytes)
let filter = CIFilter(name: "CIAreaHistogram")!
filter.setValue(inputImage, forKey: kCIInputImageKey)
filter.setValue(CIVector(x: 0, y: 0, z: image.size.width, w: image.size.height), forKey: kCIInputExtentKey)
filter.setValue(BINS, forKey: "inputCount")
filter.setValue(1, forKey: "inputScale")
let myEAGLContext = EAGLContext(API: .OpenGLES2)
let options = [kCIContextWorkingColorSpace : kCFNull]
let context : CIContext = CIContext(EAGLContext: myEAGLContext, options: options)
context.render(filter.outputImage!, toBitmap: bitmap, rowBytes: totalBytes, bounds: filter.outputImage!.extent, format: kCIFormatRGBAh, colorSpace: CGColorSpaceCreateDeviceRGB())
// *** CONVERSION FROM 16-bit TO 32-bit FLOAT ARRAY STARTS HERE ***
let comps = 4 // Number of components (RGBA)
// Array for the RGBA values of the histogram:
var rgbaFloat = [Float](count: comps * BINS, repeatedValue: 0)
// Source and image buffer structure for vImage conversion function:
var srcBuffer = vImage_Buffer(data: bitmap, height: 1, width: UInt(comps * BINS), rowBytes: bpp * BINS)
var dstBuffer = vImage_Buffer(data: &rgbaFloat, height: 1, width: UInt(comps * BINS), rowBytes: comps * sizeof(Float) * BINS)
// Half-precision float to Float conversion of entire buffer:
if vImageConvert_Planar16FtoPlanarF(&srcBuffer, &dstBuffer, 0) == kvImageNoError {
for bin in 0 ..< BINS {
let R = rgbaFloat[comps * bin + 0]
let G = rgbaFloat[comps * bin + 1]
let B = rgbaFloat[comps * bin + 2]
print("R/G/B = \(R) \(G) \(B)")
}
}
free(bitmap)
}
Remarks:
import Accelerate
.totalBytes * bpp
bytes instead
of the necessary totalBytes
.kCIFormatRGBAh
pixel format is not supported on the Simulator (as of Xcode 7), so you have to test the code on a real device.Update: Swift 5.3 (Xcode 12, currently in beta) introduces a new Float16
type which is available in iOS 14, see SE-0277 Float16 on Swift Evolution.
This simplifies the code because a conversion to Float
is no longer necessary. I have also removed the use of OpenGL functions which are deprecated as of iOS 12:
func areaHistogram(image: UIImage, bins: Int) -> [Float16] {
let comps = 4 // Number of components (RGBA)
let inputImage = CIImage(image: image)
var rgbaFloat = [Float16](repeating: 0, count: comps * bins)
let totalBytes = MemoryLayout<Float16>.size * comps * bins
let filter = CIFilter(name: "CIAreaHistogram")!
filter.setValue(inputImage, forKey: kCIInputImageKey)
filter.setValue(CIVector(x: 0, y: 0, z: image.size.width, w: image.size.height), forKey: kCIInputExtentKey)
filter.setValue(bins, forKey: "inputCount")
filter.setValue(1, forKey: "inputScale")
let options: [CIContextOption : Any] = [.workingColorSpace : NSNull()]
let context = CIContext(options: options)
rgbaFloat.withUnsafeMutableBytes {
context.render(filter.outputImage!, toBitmap: $0.baseAddress!, rowBytes: totalBytes,
bounds: filter.outputImage!.extent, format: CIFormat.RGBAh,
colorSpace: CGColorSpaceCreateDeviceRGB())
}
return rgbaFloat
}
Upvotes: 5