1
votes

I want to add a screenshot function to my game.

int width = 320;
int height = width / 4*3;

pixels is an int[] containing 76800 RGB int values corresponding to every pixel present on the screen at any time

public static void buildBitmap() {      
    File f = new File("C:/scr/game_name" + LocalDate.now().toString() +".bmp");
    try(FileOutputStream fos = new FileOutputStream(f)){
        fos.write(66);  
        fos.write(77);

        fos.write(230428);     //width * height * 3
        fos.write(0);
        fos.write(0);
        fos.write(0);

        fos.write(0);
        fos.write(0);

        fos.write(0);
        fos.write(0);

        fos.write(26);
        fos.write(0);
        fos.write(0);
        fos.write(0);


        fos.write(12);
        fos.write(0);
        fos.write(0);
        fos.write(0);

        fos.write(320);
        fos.write(0);
        fos.write(240);
        fos.write(0);

        fos.write(1);
        fos.write(0);

        fos.write(24);
        fos.write(0);            

        for(int y = height-1; y > 0; y--) {
            for(int x = 0; x < width-1; x++){              
               fos.write(pixels[x + y * width] & 0xFF);     //blue
               fos.write((pixels[x + y * width] >> 8) & 0xFF);  //green
               fos.write((pixels[x + y * width] >> 16) & 0xFF); //red
            }
        }

        fos.write(0);
        fos.write(0);
    }catch(IOException e) {
        e.printStackTrace();
    }
    System.out.println("Screenshot saved to " + f);
}

The nested loop that should fill write to the file the actual image data is made to iterate through the array bottom to top: left to right, convert the RGB int value into seperate blue, green, red and write them to the file (in that order).

It's mathematically sound and the resulting image, while warped and disfigured, is at least recognisable to be from the game. What's wrong with my code?

Also the output image comes out with a width of 64, why is this?

1

1 Answers

0
votes

You need to make sure your headers are correct. For example, the file size field in the header needs to be 4 bytes (see: BMP file format). To write exactly the correct number of bytes you can use DataOutputStream.

You need to reverse the endianess of the values. Java is big endian and BMP files are little endian. (See all the calls to reverseBytes in the code).

Your loops ignore the last byte in each row and the last row.

You also ignore the bitmap stride. These are extra padding bytes at the end of each line. Since your example is 320 pixels wide, there happens to be no extra bytes, but to handle odd sized widths you should handle these.

File f = new File("C:/scr/game_name" + LocalDate.now().toString() +".bmp");
try (FileOutputStream fos = new FileOutputStream(f)){
    DataOutputStream dos = new DataOutputStream(fos);

    int header_size = 14 + 40;  // Size of both headers
    int width = 320;
    int height = 240;
    short bpp = 24;
    // Calculate the stride
    int stride = 4 * ((width * bpp + 31) / 32);

    // BITMAPFILEHEADER
    dos.writeByte(66);                                // B
    dos.writeByte(77);                                // M
    int fileSize = (stride * height) + header_size;
    dos.writeInt(Integer.reverseBytes(fileSize));     // Actual size of entire file
    dos.writeShort(0);                                // Reserved
    dos.writeShort(0);                                // Reserved
    dos.writeInt(Integer.reverseBytes(header_size));  // starting address of bitmap image data

    // BITMAPINFOHEADER
    dos.writeInt(Integer.reverseBytes(40));           // Size of header
    dos.writeInt(Integer.reverseBytes(width));        // Width
    dos.writeInt(Integer.reverseBytes(height));       // Height
    dos.writeShort(Short.reverseBytes((short)1));     // Color planes
    dos.writeShort(Short.reverseBytes(bpp));          // BPP
    dos.writeInt(0);                                  // Compression method
    dos.writeInt(0);                                  // Image size
    dos.writeInt(0);                                  // Horizontal res
    dos.writeInt(0);                                  // Vertical res
    dos.writeInt(0);                                  // Number of colors
    dos.writeInt(0);                                  // Important colors

    for (int y = height - 1; y >= 0; y--) {
        for(int x = 0; x < width; x++) {
            dos.writeByte(pixels[x + y * width] & 0xFF);         //blue
            dos.writeByte((pixels[x + y * width] >> 8) & 0xFF);  //green
            dos.writeByte((pixels[x + y * width] >> 16) & 0xFF); //red
        }
        // Add padding bytes
        for (int s = width * 3; s < stride; s++) {
           dos.writeByte(0);
        }
    }
    fos.close();
}
catch(IOException e) {
    e.printStackTrace();
}

In the long run, you might be better off finding a 3rd party library that can do all this work for you.