Vinoth ios
Vinoth ios

Reputation: 265

how to create a bitmap image from the byte array in swift

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

Answers (1)

Rob
Rob

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

Related Questions