modules/graphics/src/main/java/com/sun/javafx/iio/bmp/BMPImageLoaderFactory.java

Print this page

        

@@ -82,10 +82,16 @@
 final class BitmapInfoHeader {
 
     static final int BIH_SIZE = 40;
     static final int BIH4_SIZE = 108;
     static final int BIH5_SIZE = 124;
+    static final int BI_RGB = 0;
+    static final int BI_RLE8 = 1;
+    static final int BI_RLE4 = 2;
+    static final int BI_BITFIELDS = 3;
+    static final int BI_JPEG = 4;
+    static final int BI_PNG = 5;
 
     final int    biSize;
     final int    biWidth;
     final int    biHeight;
     final short  biPlanes;

@@ -118,15 +124,32 @@
             }
         }
         validate();
     }
 
-    void validate() {
-        if (biCompression != 0 || biPlanes != 1 || biBitCount != 24) {
-            throw new RuntimeException(
-                    "Unsupported BMP image: " +
-                    "only 24 bit uncompressed BMP`s is supported");
+    void validate() throws IOException {
+        if (biBitCount < 1
+                || biCompression == BI_JPEG || biCompression == BI_PNG) {
+            throw new IOException(
+                    "Unsupported BMP image: "
+                    + "Embedded JPEG or PNG images are not supported");
+        }
+
+        if (biCompression == BI_RLE4 && biBitCount != 4) {
+            throw new IOException(
+                    "Invalid BMP image: "
+                    + "Only 4 bpp images can be RLE4 compressed");
+        }
+        if (biCompression == BI_RLE8 && biBitCount != 8) {
+            throw new IOException(
+                    "Invalid BMP image: "
+                    + "Only 8 bpp images can be RLE8 compressed");
+        }
+        if (biCompression == BI_BITFIELDS) {
+            throw new IOException(
+                    "Unsupported BMP image: "
+                    + "Bitfields BMP files are not supported");
         }
     }
 }
 
 final class BMPImageLoader extends ImageLoaderImpl {

@@ -134,40 +157,247 @@
     static final short BM = 0x4D42;
     static final int BFH_SIZE = 14;
 
     final LEInputStream data;
 
-    short bfType; // must be equal to BM
     int   bfSize;
     int   bfOffBits;
-    int   bgra_palette[];
+    byte  bgra_palette[];
+    int   masks[] = new int[3];
+    int   maskOffsets[] = new int[3];
     BitmapInfoHeader bih;
 
     BMPImageLoader(InputStream input) throws IOException {
         super(BMPDescriptor.theInstance);
         data = new LEInputStream(input);
-        bfType = data.readShort();
-        if (isValid()) {
-            readHeader();
+        if (data.readShort() != BM) {
+            throw new IOException("Invalid BMP file signature");
         }
+        readHeader();
     }
 
     private void readHeader() throws IOException {
         bfSize = data.readInt();
         data.skipBytes(4); // 32  bits reserved
         bfOffBits = data.readInt();
         bih = new BitmapInfoHeader(data);
+        if (bfOffBits < bih.biSize + BFH_SIZE) {
+            throw new IOException("Invalid bitmap bits offset");
+        }
+
+        // assign default masks
+        // TODO: parse BI_BITFIELDS
+        if (bih.biBitCount == 16) {
+            masks[0] = 0x7C00;
+            masks[1] = 0x03E0;
+            masks[2] = 0x001F;
+            maskOffsets[0] = 10;
+            maskOffsets[1] = 5;
+            maskOffsets[2] = 0;
+        } else if (bih.biBitCount == 32) {
+            masks[0] = 0x00FF0000;
+            masks[1] = 0x0000FF00;
+            masks[2] = 0x000000FF;
+            maskOffsets[0] = 24;
+            maskOffsets[1] = 16;
+            maskOffsets[2] = 0;
+        }
+
         if (bih.biSize + BFH_SIZE != bfOffBits) {
-            data.skipBytes(bfOffBits - bih.biSize - BFH_SIZE);
+            int length = bfOffBits - bih.biSize - BFH_SIZE;
+            int paletteSize = length / 4;
+            bgra_palette = new byte[paletteSize * 4];
+            int read = data.in.read(bgra_palette);
+            // goto bitmap bits
+            if (read < length) {
+                data.in.skip(length - read);
+            }
+        }
+    }
+
+    @Override
+    public void dispose() {
+    }
+
+    
+    private void readRLE(byte[] image, int rowLength, int hght, boolean isRLE4)
+            throws IOException
+    {
+        int imgSize = bih.biSizeImage;
+        if (imgSize == 0) {
+            imgSize = bfSize - bfOffBits;
+        }
+        byte imgData[] = new byte[imgSize];
+        if (data.in.read(imgData) < imgSize) {
+            return;
+        }
+
+        boolean isBottomUp = bih.biHeight > 0;
+        int line = isBottomUp ? hght - 1 : 0;
+        int i = 0;
+        int x = 0;
+        while (i < imgSize) {
+            int b1 = getByte(imgData, i++);
+            int b2 = getByte(imgData, i++);
+            if (b1 == 0) { // absolute
+                switch (b2) {
+                    case 0: // end of line
+                        x = 0;
+                        line += isBottomUp ? -1 : 1;
+                        break;
+                    case 1: // end of bitmap
+                        return;
+                    case 2: // delta
+                        int deltaX = getByte(imgData, i++);
+                        int deltaY = getByte(imgData, i++);
+                        x += deltaX;
+                        line += deltaY;
+                        break;
+                    default:
+                        int indexData = 0;
+                        int index;
+                        for (int p = 0; p < b2; p++) {
+                            if (isRLE4) {
+                                if ((p & 1) == 0) {
+                                    indexData = getByte(imgData, i++);
+                                    index = (indexData & 0xf0) >> 4;
+                                } else {
+                                    index = indexData & 0x0f;
+                                }
+                            } else {
+                                index = getByte(imgData, i++);
+                            }
+                            setRGBFromPalette(image, rowLength, x++, line, index);
+                        }
+                        if (isRLE4) {
+                            if ((b2 & 3) == 1 || (b2 & 3) == 2) i++;
+                        } else {
+                            if ((b2 & 1) == 1) i++;
+                        }
+                        break;
+                }
+            } else { // encoded
+                if (isRLE4) {
+                    int index1 = (b2 & 0xf0) >> 4;
+                    int index2 = b2 & 0x0f;
+                    for (int p = 0; p < b1; p++) {
+                        setRGBFromPalette(image, rowLength, x++, line,
+                                (p & 1) == 0 ? index1 : index2);
+                    }
+                } else {
+                    for (int p = 0; p < b1; p++) {
+                        setRGBFromPalette(image, rowLength, x++, line, b2);
+                    }
+                }
+            }
+        }
+
+    }
+
+    private void setRGBFromPalette(byte[] image, int rowLength, int x, int y, int index) {
+        int i = y * rowLength + x * 3;
+        image[i]     = bgra_palette[index * 4 + 2];
+        image[i + 1] = bgra_palette[index * 4 + 1];
+        image[i + 2] = bgra_palette[index * 4];
+    }
+
+    private void readPackedBits(byte[] image, int rowLength, int hght)
+            throws IOException
+    {
+        int pixPerByte = 8 / bih.biBitCount;
+        int bytesPerLine = (bih.biWidth + pixPerByte - 1) / pixPerByte;
+        int srcStride = (bytesPerLine + 3) & ~3;
+        int bitMask = (1 << bih.biBitCount) - 1;
+
+        byte lineBuf[] = new byte[srcStride];
+        for (int i = 0; i != hght; ++i) {
+            int line = bih.biHeight < 0 ? i : hght - i - 1;
+            int nRead = data.in.read(lineBuf);
+
+            for (int x = 0; x != bih.biWidth; x++) {
+                int bitnum = x * bih.biBitCount;
+                int element = lineBuf[bitnum / 8];
+                int shift = 8 - (bitnum & 7) - bih.biBitCount;
+                int index = (element >> shift) & bitMask;
+                setRGBFromPalette(image, rowLength, x, line, index);
+            }
+            if (nRead != srcStride) {
+                break;
+            }
+        }
+    }
+
+    private static int getWord(byte[] buf, int pos) {
+        return buf[pos] & 0xff | buf[pos + 1] << 8 & 0xff00;
+    }
+
+    private static int getByte(byte[] buf, int pos) {
+        return buf[pos] & 0xff;
         }
+
+    private void read16Bit(byte[] image, int rowLength, int hght) throws IOException {
+        int bytesPerLine = bih.biWidth * 2;
+        int srcStride = (bytesPerLine + 3) & ~3;
+        byte lineBuf[] = new byte[srcStride];
+        for (int i = 0; i != hght; ++i) {
+            int line = bih.biHeight < 0 ? i : hght - i - 1;
+            int nRead = data.in.read(lineBuf);
+
+            for (int x = 0; x != bih.biWidth; x++) {
+                int element = getWord(lineBuf, x * 2);
+                for (int b = 0; b < 3; b++) {
+                    byte c = (byte) ((element & masks[b]) >>> maskOffsets[b]);
+                    c = (byte) ((double) c / ((1 << 5) - 1) * 255 + 0.5);
+                    image[line * rowLength + x * 3 + b] = c;
+                }
+            }
+            if (nRead != srcStride) {
+                break;
+            }
+        }
+    }
+
+    private void read32Bit(byte[] image, int rowLength, int hght) throws IOException {
+        int bytesPerLine = bih.biWidth * 4;
+        byte lineBuf[] = new byte[bytesPerLine];
+        for (int i = 0; i != hght; ++i) {
+            int line = bih.biHeight < 0 ? i : hght - i - 1;
+            int nRead = data.in.read(lineBuf);
+
+            for (int x = 0; x != bih.biWidth; x++) {
+                int element = lineBuf[x] << 24
+                        | lineBuf[x + 1] << 16
+                        | lineBuf[x + 2] << 8
+                        | lineBuf[x + 3];
+                for (int b = 0; b < 3; b++) {
+                    image[line * rowLength + x * 3 + b]
+                            = (byte) ((element & masks[b]) >>> maskOffsets[b]);
     }
+            }
+            if (nRead != bytesPerLine) {
+                break;
+            }
+        }
+    }
+
+    private void read24Bit(byte[] image, int rowLength, int hght) throws IOException {
+        int bmpStride = (rowLength + 3) & ~3;
 
-    private boolean isValid() {
-        return bfType == BM;
+        for (int i = 0; i != hght; ++i) {
+            int line = bih.biHeight < 0 ? i : hght - i - 1;
+            int nRead = data.in.read(image, line * rowLength, rowLength);
+            GBRtoRGB(image, line * rowLength, nRead);
+
+            if (nRead != rowLength) {
+                break;
     }
 
-    public void dispose() { }
+            if (nRead < bmpStride) {
+                data.skipBytes(bmpStride - nRead);
+            }
+        }
+    }
 
     static void GBRtoRGB(byte data[], int pos, int size) {
         for (int sz = size / 3; sz != 0; --sz) {
             byte x = data[pos], y = data[pos + 2];
             data[pos + 2] = x; data[pos] = y;

@@ -175,50 +405,64 @@
         }
     }
 
     public ImageFrame load(int imageIndex, int width, int height,
             boolean preserveAspectRatio, boolean smooth) throws IOException
-{
+    {
         if (0 != imageIndex) {
             return null;
         }
 
+        int hght = Math.abs(bih.biHeight);
+
         if ((width > 0 && width != bih.biWidth) ||
-            (height > 0 && height != bih.biHeight))
+            (height > 0 && height != hght))
         {
-            throw new RuntimeException("scaling for BMP is not supported");
+            throw new IOException("scaling for BMP is not supported");
         }
 
         // Pass image metadata to any listeners.
         ImageMetadata imageMetadata = new ImageMetadata(null, Boolean.TRUE,
-            null, null, null, null, bih.biWidth, bih.biHeight,
+            null, null, null, null, bih.biWidth, hght,
             null, null, null);
         updateImageMetadata(imageMetadata);
 
-        int bmpStride = (bih.biBitCount*bih.biWidth/8 + 3) & ~3;
-        int rowLength = (bih.biBitCount/8)*bih.biWidth;
-
-        int hght = Math.abs(bih.biHeight);
-
-        byte image[] = new byte[rowLength * hght];
+        int stride = bih.biWidth * 3;
 
-        for (int i = 0; i != hght; ++i) {
-            int line = bih.biHeight < 0 ? i : hght-i-1;
-            int nRead = data.in.read(image, line * rowLength, rowLength);
-            GBRtoRGB(image, line * rowLength, nRead);
+        byte image[] = new byte[stride * hght];
 
-            if (nRead != rowLength) {
+        switch (bih.biBitCount) {
+            case 1:
+                readPackedBits(image, stride, hght);
                 break;
+            case 4:
+                if (bih.biCompression == BitmapInfoHeader.BI_RLE4) {
+                    readRLE(image, stride, hght, true);
+                } else {
+                    readPackedBits(image, stride, hght);
             }
-
-            if (nRead < bmpStride) {
-                data.skipBytes(bmpStride-nRead);
+                break;
+            case 8:
+                if (bih.biCompression == BitmapInfoHeader.BI_RLE8) {
+                    readRLE(image, stride, hght, false);
+                } else {
+                    readPackedBits(image, stride, hght);
             }
+                break;
+            case 16:
+                read16Bit(image, stride, hght);
+                break;
+            case 32:
+                read32Bit(image, stride, hght);
+                break;
+            case 24:
+                read24Bit(image, stride, hght);
+                break;
         }
 
         return new ImageFrame(ImageStorage.ImageType.RGB, ByteBuffer.wrap(image),
-                bih.biWidth, hght, rowLength, null, null);
+                bih.biWidth, hght, stride, null, null);
     }
 }
 
 public final class BMPImageLoaderFactory implements ImageLoaderFactory {
 

@@ -235,6 +479,5 @@
 
     public ImageLoader createImageLoader(InputStream input) throws IOException {
         return new BMPImageLoader(input);
     }
 }
-