Errors in bitmap creation in Java

169 views Asked by At

I'm trying to create a bitmap from a 2d array of pixel values. I've found some code on the internet (taken from http://forum.codecall.net/topic/62457-creating-a-bmp-image-in-java/):

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class BMP {
    private final static int BMP_CODE = 19778;

    byte [] bytes;

    public void saveBMP(String filename, int [][] rgbValues){
        try {
            FileOutputStream fos = new FileOutputStream(new File(filename));

            bytes = new byte[54 + 3*rgbValues.length*rgbValues[0].length + getPadding(rgbValues[0].length)*rgbValues.length];

            saveFileHeader();
            saveInfoHeader(rgbValues.length, rgbValues[0].length);
            saveRgbQuad();
            saveBitmapData(rgbValues);

            fos.write(bytes);

            fos.close();

        } catch (FileNotFoundException e) {

        } catch (IOException e) {

        }

    }

    private void saveFileHeader() {
        byte[]a=intToByteCouple(BMP_CODE);
        bytes[0]=a[1];
        bytes[1]=a[0];

        a=intToFourBytes(bytes.length);
        bytes[5]=a[0];
        bytes[4]=a[1];
        bytes[3]=a[2];
        bytes[2]=a[3];

        //data offset
        bytes[10]=54;
    }

    private void saveInfoHeader(int height, int width) {
        bytes[14]=40;

        byte[]a=intToFourBytes(width);
        bytes[22]=a[3];
        bytes[23]=a[2];
        bytes[24]=a[1];
        bytes[25]=a[0];

        a=intToFourBytes(height);
        bytes[18]=a[3];
        bytes[19]=a[2];
        bytes[20]=a[1];
        bytes[21]=a[0];

        bytes[26]=1;

        bytes[28]=24;
    }

    private void saveRgbQuad() {

    }

    private void saveBitmapData(int[][]rgbValues) {
        int i;

        for(i=0;i<rgbValues.length;i++){
            writeLine(i, rgbValues);
        }

    }

    private void writeLine(int row, int [][] rgbValues) {
        final int offset=54;
        final int rowLength=rgbValues[row].length;
        final int padding = getPadding(rgbValues[0].length);
        int i;

        for(i=0;i<rowLength;i++){
            int rgb=rgbValues[row][i];
            int temp=offset + 3*(i+rowLength*row) + row*padding;

            bytes[temp]    = (byte) (rgb>>16);
            bytes[temp +1] = (byte) (rgb>>8);
            bytes[temp +2] = (byte) rgb;
        }
        i--;
        int temp=offset + 3*(i+rowLength*row) + row*padding+3;

        for(int j=0;j<padding;j++)
            bytes[temp +j]=0;

    }

    private byte[] intToByteCouple(int x){
        byte [] array = new byte[2];

        array[1]=(byte)  x;
        array[0]=(byte) (x>>8);

        return array;
    }

    private byte[] intToFourBytes(int x){
        byte [] array = new byte[4];

        array[3]=(byte)  x;
        array[2]=(byte) (x>>8);
        array[1]=(byte) (x>>16);
        array[0]=(byte) (x>>24);

        return array;
    }

    private int getPadding(int rowLength){

        int padding = (3*rowLength)%4;
        if(padding!=0)
            padding=4-padding;


        return padding;
    }

}

Now I try to create a bitmap with size 10x5 with the following code:

int[][] bmpData = new int[10][5];
for(int x = 0; x < 10; x++) {
    for(int y = 0; y < 5; y++) {
        bmpData[x][y] = 127 | 127 << 8 | 127 << 16;
    }
}
new BMP().saveBMP("test.bmp", bmpData);

The result I have, is incorrect. I expect to have a 10x5 gray bmp but instead, I see a yellowish vertical line in the image. When drawing a larger image (100x50), I get this:

enter image description here

It looks like the columns are drawn as rows. I'm not sure why this is. I found something about padding in the source code but I have a hard time understanding how it works.

Can anyone explain this behavior? Note that I'm not working on Android so I'm unable to use the Bitmap class.

1

There are 1 answers

2
default locale On BEST ANSWER

It seems to me that this code doesn't work as intended.

Check out this part, for example:

saveInfoHeader(rgbValues.length, rgbValues[0].length);

saveInfoHeader has this signature:

saveInfoHeader(int height, int width)

So, when you pass int[100][50]; you should get bmp with height 100 and width 50.

I suppose this part of the code is incorrect:

byte[]a=intToFourBytes(width);
bytes[22]=a[3];
bytes[23]=a[2];
bytes[24]=a[1];
bytes[25]=a[0];

a=intToFourBytes(height);
bytes[18]=a[3];
bytes[19]=a[2];
bytes[20]=a[1];
bytes[21]=a[0];

By BMP standard width should be declared before height, here width is getting processed earlier, but (I suppose, through editing error) it is written with bigger offset.

If you change this to:

byte[]a=intToFourBytes(width);
bytes[18]=a[3];
bytes[19]=a[2];
bytes[20]=a[1];
bytes[21]=a[0];

a=intToFourBytes(height);
bytes[22]=a[3];
bytes[23]=a[2];
bytes[24]=a[1];
bytes[25]=a[0];

, you'll get this as a result:

enter image description here

Pattern that you get is a result of miscalculated BMP padding due to height/width mismatch.