Search code examples
javaimagetype-conversionbufferedimage

Convert 8bit Grayscale image byte array to a BufferedImage


I have a byte array containing data of the raw grayscale 8bit image, which I need to convert to a BufferedImage. I've tried doing:

BufferedImage image = ImageIO.read(new ByteArrayInputStream(bytes));

However, the resulting image object is null which means I'm doing something wrong here.

What's the correct way of making such a conversion?


Solution

  • There are two good ways to do this, depending on your use case.

    Either create a new, gray image, and copy the data into it. This will keep the image "managed", which may lead to better rendering performance (ie. on screen). But it will need twice as much memory, and copy the data from your input to the image.

    The other, is to create the gray image directly "around" your existing pixel data. This will be faster, and use almost no extra heap, as it avoids copying the pixel data. But the image will not be managed (as the backing array is exposed and mutable).

    Both options are demonstrated below:

    int w = 640;
    int h = 480;
    
    byte[] imageBytes = new byte[w * h];
    
    // 1 Keeps the image "managed" at the expense of twice the memory + a large array copy
    BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_GRAY);
    image.getRaster().setDataElements(0, 0, w, h, imageBytes);
    
    System.out.println("image: " + image);
    
    // 2 Faster, and uses less memory, but will make the image "unmanaged"
    ColorModel cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
    WritableRaster raster = Raster.createInterleavedRaster(new DataBufferByte(imageBytes, imageBytes.length), w, h, w, 1, new int[]{0}, null);
    BufferedImage image2 = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
    
    System.out.println("image2: " + image2);
    

    If the image data isn't in linear gray color space, one could use an IndexColorModel to map the input into whatever range you want:

    // Alternate, using IndexColorModel, if your input isn't in linear gray color space
    int[] cmap = new int[256]; // TODO: Add ARGB packed colors here...
    IndexColorModel icm = new IndexColorModel(8, 256, cmap, 0, false, -1, DataBuffer.TYPE_BYTE);
    
    // As 1
    BufferedImage image3 = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_INDEXED, icm);
    image3.getRaster().setDataElements(0, 0, w, h, imageBytes);
    System.out.println("image3: " + image3);
    
    // As 2
    BufferedImage image4 = new BufferedImage(icm, raster, cm.isAlphaPremultiplied(), null);
    
    System.out.println("image4: " + image4);