//from JAVA Expert Solutions
//by Mark Wutka, et. al.

package net.starmen.pkhack;

import java.awt.image.ImageProducer;
import java.awt.image.MemoryImageSource;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;

// This class provides a public static method that takes an InputStream
// to a Windows .BMP file and converts it into an ImageProducer via
// a MemoryImageSource.
// You can fetch a .BMP through a URL with the following code:
// URL url = new URL( <wherever your URL is> )
// Image img = createImage(BMPReader.getBMPImage(url.openStream()));

public class BMPReader extends Object
{
// Constants indicating how the data is stored
    public static final int BI_RGB = 0;
    public static final int BI_RLE8 = 1;
    public static final int BI_RLE4 = 2;

    public static ImageProducer getBMPImage(InputStream stream)
    throws IOException
    {
// The DataInputStream allows you to read in 16 and 32 bit numbers
        DataInputStream in = new DataInputStream(stream);

// Verify that the header starts with 'BM'

        if (in.read() != 'B') {
            throw new IOException("Not a .BMP file");
        }
        if (in.read() != 'M') {
            throw new IOException("Not a .BMP file");
        }

// Get the total file size
        int fileSize = intelInt(in.readInt());

// Skip the 2 16-bit reserved words
        in.readUnsignedShort();
        in.readUnsignedShort();

        int bitmapOffset = intelInt(in.readInt());

        int bitmapInfoSize = intelInt(in.readInt());

        int width = intelInt(in.readInt());
        int height = intelInt(in.readInt());

// Skip the 16-bit bitplane size
        in.readUnsignedShort();

        int bitCount = intelShort(in.readUnsignedShort());

        int compressionType = intelInt(in.readInt());

        int imageSize = intelInt(in.readInt());

// Skip pixels per meter
        in.readInt();
        in.readInt();

        int colorsUsed = intelInt(in.readInt());
        int colorsImportant = intelInt(in.readInt());
        if (colorsUsed == 0) colorsUsed = 1 << bitCount;

        int colorTable[] = new int[colorsUsed];

// Read the bitmap's color table
        for (int i=0; i < colorsUsed; i++) {
            colorTable[i] = (intelInt(in.readInt()) & 0xffffff) + 0xff000000;
        }

// Create space for the pixels
        int pixels[] = new int[width * height];

// Read the pixels from the stream based on the compression type
        if (compressionType == BI_RGB) {
            if (bitCount == 24) {
                readRGB24(width, height, pixels, in);
            } else {
                readRGB(width, height, colorTable, bitCount,
                    pixels, in);
            }
        } else if (compressionType == BI_RLE8) {
            readRLE(width, height, colorTable, bitCount,
                pixels, in, imageSize, 8);
        } else if (compressionType == BI_RLE4) {
            readRLE(width, height, colorTable, bitCount,
                pixels, in, imageSize, 4);
        }

// Create a memory image source from the pixels
        return new MemoryImageSource(width, height, pixels, 0,
            width);
    }

// Reads in pixels in 24-bit format. There is no color table, and the
// pixels are stored in 3-byte pairs. Oddly, all windows bitmaps are
// stored upside-down - the bottom line is stored first.

    protected static void readRGB24(int width, int height, int pixels[],
        DataInputStream in)
    throws IOException
    {

// Start storing at the bottom of the array
        for (int h = height-1; h >= 0; h--) {
         int pos = h * width;
            for (int w = 0; w < width; w++) {

// Read in the red, green, and blue components
          int red = in.read();
          int green = in.read();
          int blue = in.read();

// Turn the red, green, and blue values into an RGB color with
// an alpha value of 255 (fully opaque)
                pixels[pos++] = 0xff000000 + (red << 16) +
               (green << 8) + blue;
            }
        }
    }
//  readRGB reads in pixels values that are stored uncompressed.
//  The bits represent indices into the color table.

     protected static void readRGB(int width, int height, int colorTable[],
         int bitCount, int pixels[], DataInputStream in)
     throws IOException
     {

//  How many pixels can be stored in a byte?
         int pixelsPerByte = 8 / bitCount;

//  A bit mask containing the number of bits in a pixel
         int bitMask = (1 << bitCount) - 1;

//  The shift values that will move each pixel to the far right
         int bitShifts[] = new int[pixelsPerByte];

         for (int i=0; i < pixelsPerByte; i++) {
             bitShifts[i] = 8 - ((i+1) * bitCount);
         }

         int whichBit = 0;

//  Read in the first byte
         int currByte = in.read();

//  Start at the bottom of the pixel array and work up
         for (int h=height-1; h >= 0; h--) {
             int pos = h * width;
             for (int w=0; w < width; w++) {

//  Get the next pixel from the current byte
                 pixels[pos] = colorTable[
                     (currByte >> bitShifts[whichBit]) &
                     bitMask];
           pos++;
                 whichBit++;

//  If the current bit position is past the number of pixels in
//  a byte, we advance to the next byte
                 if (whichBit >= pixelsPerByte) {
                     whichBit = 0;
                     currByte = in.read();
                 }
             }
         }
     }


//  readRLE reads run-length encoded data in either RLE4 or RLE8 format.

     protected static void readRLE(int width, int height, int colorTable[],
         int bitCount, int pixels[], DataInputStream in,
         int imageSize, int pixelSize)
     throws IOException
     {
         int x = 0;
         int y = height-1;

//  You already know how many bytes are in the image, so only go
//  through that many.

         for (int i=0; i < imageSize; i++) {

//  RLE encoding is defined by two bytes
             int byte1 = in.read();
             int byte2 = in.read();
             i += 2;

//  If byte 0 == 0, this is an escape code
             if (byte1 == 0) {

//  If escaped, byte 2 == 0 means you are at end of line
                 if (byte2 == 0) {
                     x = 0;
                     y--;

//  If escaped, byte 2 == 1 means end of bitmap
                 } else if (byte2 == 1) {
                     return;

//  if escaped, byte 2 == 2 adjusts the current x and y by
//  an offset stored in the next two words
                 } else if (byte2 == 2) {
                     int xoff = (char) intelShort(
                         in.readUnsignedShort());
                     i+= 2;
                     int yoff = (char) intelShort(
                         in.readUnsignedShort());
                     i+= 2;
                     x += xoff;
                     y -= yoff;

//  If escaped, any other value for byte 2 is the number of bytes
//  that you should read as pixel values (these pixels are not
//  run-length encoded)
                 } else {
                     int whichBit = 0;

//  Read in the next byte
                     int currByte = in.read();

                     i++;
                     for (int j=0; j < byte2; j++) {

                         if (pixelSize == 4) {
//  The pixels are 4-bits, so half the time you shift the current byte
//  to the right as the pixel value
                             if (whichBit == 0) {
                                 pixels[y*width+x] = colorTable[(currByte >> 4)
                                     & 0xf];
                             } else {

//  The rest of the time, you mask out the upper 4 bits, save the pixel
//  value, then read in the next byte

                                 pixels[y*width+x] = colorTable[currByte & 0xf];
                                 currByte = in.read();
                                 i++;
                             }
                         } else {
                             pixels[y*width+x] = colorTable[currByte];
                             currByte = in.read();
                             i++;
                         }
                         x++;
                         if (x >= width) {
                             x = 0;
                             y--;
                         }
                     }
//  The pixels must be word-aligned, so if you read an uneven number of
//  bytes, read and ignore a byte to get aligned again.
                     if ((byte2 & 1) == 1) {
                         in.read();
                         i++;
                     }
                 }


//  If the first byte was not 0, it is the number of pixels that
//  are encoded by byte 2
             } else {
                 for (int j=0; j < byte1; j++) {

                    if (pixelSize == 4) {
//  If j is odd, use the upper 4 bits
                        if ((j & 1) == 0) {
                            pixels[y*width+x] = colorTable[(byte2 >> 4) & 0xf];
                        } else {
                            pixels[y*width+x+1] = colorTable[byte2 & 0xf];
                        }
                    } else {
                        pixels[y*width+x+1] = colorTable[byte2];
                    }
                    x++;
                    if (x >= width) {
                        x = 0;
                        y--;
                    }
                 }
             }
         }
     }
//  intelShort converts a 16-bit number stored in intel byte order into
//  the local host format

     protected static int intelShort(int i)
     {
         return ((i >> 8) & 0xff) + ((i << 8) & 0xff00);
     }

//  intelInt converts a 32-bit number stored in intel byte order into
//  the local host format

     protected static int intelInt(int i)
     {
         return ((i & 0xff) << 24) + ((i & 0xff00) << 8) +
             ((i & 0xff0000) >> 8) + ((i >> 24) & 0xff);
     }
 }