Reputation: 265
I have a byte array which comes from a finger print sensor device. I wanted to create a bitmap out of it. I have tried few examples but all I am getting is a nil UIImage.
If there are any steps to do that, pls tell me.
Thanks.
This is what my func does:
func didFingerGrabDataReceived(data: [UInt8]) {
if data[0] == 0 {
let width1 = Int16(data[0]) << 8
let finalWidth = Int(Int16(data[1]) | width1)
let height1 = Int16(data[2]) << 8
let finalHeight = Int(Int16(data[3]) | height1)
var finalData:[UInt8] = [UInt8]()
// i dont want the first 8 bytes, so am removing it
for i in 8 ..< data.count {
finalData.append(data[i])
}
dispatch_async(dispatch_get_main_queue()) { () -> Void in
let msgData = NSMutableData(bytes: finalData, length: finalData.count)
let ptr = UnsafeMutablePointer<UInt8>(msgData.mutableBytes)
let colorSpace = CGColorSpaceCreateDeviceGray()
if colorSpace == nil {
self.showToast("color space is nil")
return
}
let bitmapContext = CGBitmapContextCreate(ptr, finalWidth, finalHeight, 8, 4 * finalWidth, colorSpace, CGImageAlphaInfo.Only.rawValue);
if bitmapContext == nil {
self.showToast("context is nil")
return
}
let cgImage=CGBitmapContextCreateImage(bitmapContext);
if cgImage == nil {
self.showToast("image is nil")
return
}
let newimage = UIImage(CGImage: cgImage!)
self.imageViewFinger.image = newimage
}
}
I am getting a distorted image. someone please help
Upvotes: 3
Views: 12641
Reputation: 437582
The significant issue here is that when you called CGBitmapContextCreate
, you specified that you're building an alpha channel alone, your data
buffer is clearly using one byte per pixel, but for the "bytes per row" parameter, you've specified 4 * width
. It should just be width
. You generally use 4x when you're capturing four bytes per pixel (e.g. RGBA), but since your buffer is using one byte per pixel, you should remove that 4x factor.
Personally, I'd also advise a range of other improvements, namely:
The only thing that should be dispatched to the main queue is the updating of the UIKit control
You can retire finalData
, as you don't need to copy from one buffer to another, but rather you can build msgData
directly.
You should probably bypass the creation of your own buffer completely, though, and call CGBitmapContextCreate
with nil
for the data parameter, in which case, it will create its own buffer which you can retrieve via CGBitmapContextGetData
. If you pass it a buffer, it assumes you'll manage this buffer yourself, which we're not doing here.
If you create your own buffer and don't manage that memory properly, you'll experience difficult-to-reproduce errors where it looks like it works, but suddenly you'll see the buffer corrupted for no reason in seemingly similar situations. By letting Core Graphics manage the memory, these sorts of problems are prevented.
I might separate the conversion of this byte buffer to a UIImage
from the updating of the UIImageView
.
So that yields something like:
func mask(from data: [UInt8]) -> UIImage? {
guard data.count >= 8 else {
print("data too small")
return nil
}
let width = Int(data[1]) | Int(data[0]) << 8
let height = Int(data[3]) | Int(data[2]) << 8
let colorSpace = CGColorSpaceCreateDeviceGray()
guard
data.count >= width * height + 8,
let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: width, space: colorSpace, bitmapInfo: CGImageAlphaInfo.alphaOnly.rawValue),
let buffer = context.data?.bindMemory(to: UInt8.self, capacity: width * height)
else {
return nil
}
for index in 0 ..< width * height {
buffer[index] = data[index + 8]
}
return context.makeImage().flatMap { UIImage(cgImage: $0) }
}
And then
if let image = mask(from: data) {
DispatchQueue.main.async {
self.imageViewFinger.image = image
}
}
For Swift 2 rendition, see previous revision of this answer.
Upvotes: 9