Search code examples
javabufferedimagefileoutputstreamimageicondataoutputstream

How do I convert byte data into an image format that's usable within Java?


I'm currently making a Java application to take an image from a fingerprint scanner (ZK4500 model) and display it on the screen. The SDK code is pretty straight forward and I have everything working from the user's perspective. However, the only method the SDK has for drawing the image to the screen is by taking the buffered image data and writing it to a file. The file is then read into an icon data type and displayed in a JLabel.

The problem I'd like to solve is that the image buffer data is constantly written to the the hard drive and then read from the hard drive just to see what the finger print image looks like. I'd like to translate the image buffer data already in memory to be drawn to the screen... preferably in a JLabel object, but it can be in a different object if need be.

The following prepares the image data to be read from the scanner and then displayed in a JLabel...

private long device = 0;
private byte[] imageData = null; // image buffer data
private int imageWidth = 0;
private int imageHeight = 0;
private byte[] parameter = new byte[4];
private int[] size = new int[1];

device = FingerprintSensorEx.OpenDevice(0);
FingerprintSensorEx.GetParameters(device, 1, parameter, size);
imageWidth = byteArrayToInt(parameter); // (!) see next code snippet below
FingerprintSensorEx.GetParameters(device, 2, parameter, size);
imageHeight = byteArrayToInt(parameter); // (!) see next code snippet below
imageData = new byte[imageWidth * imageHeight]; // data size (284 x 369)

FingerprintSensorEx.AcquireFingerprintImage(device, imageData); // loads image buffer data

writeImageFile(imageData, imageWidth, imageHeight); // (!) see next code snippet below

imageDisplay.setIcon(new ImageIcon(ImageIO.read(new File("fingerprint.bmp")))); // jlabel object

The following is how the SDK writes the image data to a file...

private void writeImageFile(byte[] imageBuf, int nWidth, int nHeight) throws IOException {
    java.io.FileOutputStream fos = new java.io.FileOutputStream("fingerprint.bmp");
    java.io.DataOutputStream dos = new java.io.DataOutputStream(fos);

    int w = (((nWidth + 3) / 4) * 4);
    int bfType = 0x424d;
    int bfSize = 54 + 1024 + w * nHeight;
    int bfReserved1 = 0;
    int bfReserved2 = 0;
    int bfOffBits = 54 + 1024;

    dos.writeShort(bfType);
    dos.write(changeByte(bfSize), 0, 4);
    dos.write(changeByte(bfReserved1), 0, 2);
    dos.write(changeByte(bfReserved2), 0, 2);
    dos.write(changeByte(bfOffBits), 0, 4);

    int biSize = 40;
    int biPlanes = 1;
    int biBitcount = 8;
    int biCompression = 0;
    int biSizeImage = w * nHeight;
    int biXPelsPerMeter = 0;
    int biYPelsPerMeter = 0;
    int biClrUsed = 0;
    int biClrImportant = 0;

    dos.write(changeByte(biSize), 0, 4);
    dos.write(changeByte(nWidth), 0, 4);
    dos.write(changeByte(nHeight), 0, 4);
    dos.write(changeByte(biPlanes), 0, 2);
    dos.write(changeByte(biBitcount), 0, 2);
    dos.write(changeByte(biCompression), 0, 4);
    dos.write(changeByte(biSizeImage), 0, 4);
    dos.write(changeByte(biXPelsPerMeter), 0, 4);
    dos.write(changeByte(biYPelsPerMeter), 0, 4);
    dos.write(changeByte(biClrUsed), 0, 4);
    dos.write(changeByte(biClrImportant), 0, 4);

    for (int i = 0; i < 256; i++) {
        dos.writeByte(i);
        dos.writeByte(i);
        dos.writeByte(i);
        dos.writeByte(0);
    }

    byte[] filter = null;
    if (w > nWidth) {
        filter = new byte[w - nWidth];
    }

    for (int i = 0; i < nHeight; i++) {
        dos.write(imageBuf, (nHeight - 1 - i) * nWidth, nWidth);
        if (w > nWidth)
            dos.write(filter, 0, w - nWidth);
    }
    dos.flush();
    dos.close();
    fos.close();
}

private int byteArrayToInt(byte[] bytes) {
    int number = bytes[0] & 0xFF;
    number |= ((bytes[1] << 8) & 0xFF00);
    number |= ((bytes[2] << 16) & 0xFF0000);
    number |= ((bytes[3] << 24) & 0xFF000000);
    return number;
}

private byte[] intToByteArray(final int number) {
    byte[] abyte = new byte[4];
    abyte[0] = (byte) (0xff & number);
    abyte[1] = (byte) ((0xff00 & number) >> 8);
    abyte[2] = (byte) ((0xff0000 & number) >> 16);
    abyte[3] = (byte) ((0xff000000 & number) >> 24);
    return abyte;
}

private byte[] changeByte(int data) {
    return intToByteArray(data);
}

I included how the image data is written to the file output stream in case there is some clue as to what the real format of the scanner's image buffer data is. GIMP tells me that the written file is an 8-bit grayscale gamma integer BMP.

I know practically nothing about Java so I hope someone can point me in the right direction from a beginner's perspective. I read that a BufferedImage is the best way to work with images in Java, but I just couldn't connect the dots with the byte data from the scanner. I tried things along the line of...

BufferedImage img = ImageIO.read(new ByteArrayInputStream(imageData));
imageDisplay.setIcon(new ImageIcon(img)); // jlabel object

...but it returned an error because the image was "null". I think the image data needs to be in an array format first? Maybe the code in how the SDK writes the BMP file helps solve that, but I'm just grasping at straws here.


Solution

  • The writeImageFile does seem correct to me, and writes a valid BMP file that ImageIO should handle fine. However, writing the data to disk, just to read it back in, is a waste of time (and disk storage)... Instead, I would just create a BufferedImage directly from the image data.

    I don't have your SDK or device, so I'm assuming the image dimensions and arrays are correctly filled (I'm just filling it with a gradient in the example):

    // Dimensions from your sample code
    int imageWidth = 284;
    int imageHeight = 369;
    
    byte[] imageData = new byte[imageWidth * imageHeight];
    simulateCapture(imageData, imageWidth, imageHeight);
    
    // The important parts:
    // 1: Creating a new image to hold 8 bit gray data
    BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_BYTE_GRAY);
    
    // 2: Setting the image data from your array to the image
    image.getRaster().setDataElements(0, 0, imageWidth, imageHeight, imageData);
    
    // And just to prove that it works
    System.out.println("image = " + image);
    JOptionPane.showMessageDialog(null, new ImageIcon(image), "image", JOptionPane.INFORMATION_MESSAGE);
    
    public void simluateCapture(byte[] imageData, int imageWidth, int imageHeight) {
        // Filling the image data with a gradient from black upper-left to white lower-right
        for (int y = 0; y < imageHeight; y++) {
            for (int x = 0; x < imageWidth; x++) {
                imageData[imageWidth * y + x] = (byte) (255 * y * x / (imageHeight * imageWidth));
            }
        }
    }
    

    Output:

    image = BufferedImage@4923ab24: type = 10 ColorModel: #pixelBits = 8 numComponents = 1 color space = java.awt.color.ICC_ColorSpace@44c8afef transparency = 1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 284 height = 369 #numDataElements 1 dataOff[0] = 0
    

    Screenshot:

    enter image description here