1 /*
   2  * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.javafx.iio.bmp;
  27 
  28 import com.sun.javafx.iio.*;
  29 import com.sun.javafx.iio.common.*;
  30 import java.io.*;
  31 import java.nio.ByteBuffer;
  32 
  33 final class BMPDescriptor extends ImageDescriptor {
  34 
  35     static final String formatName = "BMP";
  36     static final String extensions[] = { "bmp" };
  37     static final Signature signatures[] = {new Signature((byte)0x42, (byte)0x4D)};
  38     static final ImageDescriptor theInstance = new BMPDescriptor();
  39 
  40     private BMPDescriptor() {
  41         super(formatName, extensions, signatures);
  42     }
  43 }
  44 
  45 // the difference of LEInputStream from DataInputStream is Endianness
  46 final class LEInputStream {
  47 
  48     final public InputStream in;
  49 
  50     LEInputStream(InputStream is) {
  51         in = is;
  52     }
  53 
  54     public final short readShort() throws IOException {
  55         int ch1 = in.read();
  56         int ch2 = in.read();
  57         if ((ch1 | ch2) < 0) {
  58             throw new EOFException();
  59         }
  60         return (short)((ch2 << 8) + ch1);
  61     }
  62 
  63     public final int readInt() throws IOException {
  64         int ch1 = in.read();
  65         int ch2 = in.read();
  66         int ch3 = in.read();
  67         int ch4 = in.read();
  68         if ((ch1 | ch2 | ch3 | ch4) < 0) {
  69             throw new EOFException();
  70         }
  71         return ((ch4 << 24) + (ch3 << 16) + (ch2 << 8) + ch1);
  72     }
  73 
  74     public final void skipBytes(int n) throws IOException {
  75         int m = (int)in.skip(n);
  76         if (m < n) {
  77             throw new EOFException();
  78         }
  79     }
  80 }
  81 
  82 final class BitmapInfoHeader {
  83 
  84     static final int BIH_SIZE = 40;
  85     static final int BIH4_SIZE = 108;
  86     static final int BIH5_SIZE = 124;
  87     static final int BI_RGB = 0;
  88     static final int BI_RLE8 = 1;
  89     static final int BI_RLE4 = 2;
  90     static final int BI_BITFIELDS = 3;
  91     static final int BI_JPEG = 4;
  92     static final int BI_PNG = 5;
  93 
  94     final int    biSize;
  95     final int    biWidth;
  96     final int    biHeight;
  97     final short  biPlanes;
  98     final short  biBitCount;
  99     final int    biCompression;
 100     final int    biSizeImage;
 101     final int    biXPelsPerMeter;
 102     final int    biYPelsPerMeter;
 103     final int    biClrUsed;
 104     final int    biClrImportant;
 105 
 106     BitmapInfoHeader(LEInputStream data) throws IOException {
 107         biSize = data.readInt();
 108         biWidth = data.readInt();
 109         biHeight = data.readInt();
 110         biPlanes = data.readShort();
 111         biBitCount = data.readShort();
 112         biCompression = data.readInt();
 113         biSizeImage = data.readInt();
 114         biXPelsPerMeter = data.readInt();
 115         biYPelsPerMeter = data.readInt();
 116         biClrUsed = data.readInt();
 117         biClrImportant = data.readInt();
 118 
 119         if (biSize > BIH_SIZE) {
 120             if (biSize == BIH4_SIZE || biSize == BIH5_SIZE) {
 121                 data.skipBytes(biSize - BIH_SIZE);
 122             } else {
 123                 throw new IOException("BitmapInfoHeader is corrupt");
 124             }
 125         }
 126         validate();
 127     }
 128 
 129     void validate() throws IOException {
 130         if (biBitCount < 1
 131                 || biCompression == BI_JPEG || biCompression == BI_PNG) {
 132             throw new IOException(
 133                     "Unsupported BMP image: "
 134                     + "Embedded JPEG or PNG images are not supported");
 135         }
 136 
 137         if (biCompression == BI_RLE4 && biBitCount != 4) {
 138             throw new IOException(
 139                     "Invalid BMP image: "
 140                     + "Only 4 bpp images can be RLE4 compressed");
 141         }
 142         if (biCompression == BI_RLE8 && biBitCount != 8) {
 143             throw new IOException(
 144                     "Invalid BMP image: "
 145                     + "Only 8 bpp images can be RLE8 compressed");
 146         }
 147         if (biCompression == BI_BITFIELDS) {
 148             throw new IOException(
 149                     "Unsupported BMP image: "
 150                     + "Bitfields BMP files are not supported");
 151         }
 152     }
 153 }
 154 
 155 final class BMPImageLoader extends ImageLoaderImpl {
 156 
 157     static final short BM = 0x4D42;
 158     static final int BFH_SIZE = 14;
 159 
 160     final LEInputStream data;
 161 
 162     int   bfSize;
 163     int   bfOffBits;
 164     byte  bgra_palette[];
 165     int   masks[] = new int[3];
 166     int   maskOffsets[] = new int[3];
 167     BitmapInfoHeader bih;
 168 
 169     BMPImageLoader(InputStream input) throws IOException {
 170         super(BMPDescriptor.theInstance);
 171         data = new LEInputStream(input);
 172         if (data.readShort() != BM) {
 173             throw new IOException("Invalid BMP file signature");
 174         }
 175         readHeader();
 176     }
 177 
 178     private void readHeader() throws IOException {
 179         bfSize = data.readInt();
 180         data.skipBytes(4); // 32  bits reserved
 181         bfOffBits = data.readInt();
 182         bih = new BitmapInfoHeader(data);
 183         if (bfOffBits < bih.biSize + BFH_SIZE) {
 184             throw new IOException("Invalid bitmap bits offset");
 185         }
 186 
 187         // assign default masks
 188         // TODO: parse BI_BITFIELDS
 189         if (bih.biBitCount == 16) {
 190             masks[0] = 0x7C00;
 191             masks[1] = 0x03E0;
 192             masks[2] = 0x001F;
 193             maskOffsets[0] = 10;
 194             maskOffsets[1] = 5;
 195             maskOffsets[2] = 0;
 196         } else if (bih.biBitCount == 32) {
 197             masks[0] = 0x00FF0000;
 198             masks[1] = 0x0000FF00;
 199             masks[2] = 0x000000FF;
 200             maskOffsets[0] = 24;
 201             maskOffsets[1] = 16;
 202             maskOffsets[2] = 0;
 203         }
 204 
 205         if (bih.biSize + BFH_SIZE != bfOffBits) {
 206             int length = bfOffBits - bih.biSize - BFH_SIZE;
 207             int paletteSize = length / 4;
 208             bgra_palette = new byte[paletteSize * 4];
 209             int read = data.in.read(bgra_palette);
 210             // goto bitmap bits
 211             if (read < length) {
 212                 data.in.skip(length - read);
 213             }
 214         }
 215     }
 216 
 217     @Override
 218     public void dispose() {
 219     }
 220 
 221     
 222     private void readRLE(byte[] image, int rowLength, int hght, boolean isRLE4)
 223             throws IOException
 224     {
 225         int imgSize = bih.biSizeImage;
 226         if (imgSize == 0) {
 227             imgSize = bfSize - bfOffBits;
 228         }
 229         byte imgData[] = new byte[imgSize];
 230         if (data.in.read(imgData) < imgSize) {
 231             return;
 232         }
 233 
 234         boolean isBottomUp = bih.biHeight > 0;
 235         int line = isBottomUp ? hght - 1 : 0;
 236         int i = 0;
 237         int x = 0;
 238         while (i < imgSize) {
 239             int b1 = getByte(imgData, i++);
 240             int b2 = getByte(imgData, i++);
 241             if (b1 == 0) { // absolute
 242                 switch (b2) {
 243                     case 0: // end of line
 244                         x = 0;
 245                         line += isBottomUp ? -1 : 1;
 246                         break;
 247                     case 1: // end of bitmap
 248                         return;
 249                     case 2: // delta
 250                         int deltaX = getByte(imgData, i++);
 251                         int deltaY = getByte(imgData, i++);
 252                         x += deltaX;
 253                         line += deltaY;
 254                         break;
 255                     default:
 256                         int indexData = 0;
 257                         int index;
 258                         for (int p = 0; p < b2; p++) {
 259                             if (isRLE4) {
 260                                 if ((p & 1) == 0) {
 261                                     indexData = getByte(imgData, i++);
 262                                     index = (indexData & 0xf0) >> 4;
 263                                 } else {
 264                                     index = indexData & 0x0f;
 265                                 }
 266                             } else {
 267                                 index = getByte(imgData, i++);
 268                             }
 269                             setRGBFromPalette(image, rowLength, x++, line, index);
 270                         }
 271                         if (isRLE4) {
 272                             if ((b2 & 3) == 1 || (b2 & 3) == 2) i++;
 273                         } else {
 274                             if ((b2 & 1) == 1) i++;
 275                         }
 276                         break;
 277                 }
 278             } else { // encoded
 279                 if (isRLE4) {
 280                     int index1 = (b2 & 0xf0) >> 4;
 281                     int index2 = b2 & 0x0f;
 282                     for (int p = 0; p < b1; p++) {
 283                         setRGBFromPalette(image, rowLength, x++, line,
 284                                 (p & 1) == 0 ? index1 : index2);
 285                     }
 286                 } else {
 287                     for (int p = 0; p < b1; p++) {
 288                         setRGBFromPalette(image, rowLength, x++, line, b2);
 289                     }
 290                 }
 291             }
 292         }
 293 
 294     }
 295 
 296     private void setRGBFromPalette(byte[] image, int rowLength, int x, int y, int index) {
 297         int i = y * rowLength + x * 3;
 298         image[i]     = bgra_palette[index * 4 + 2];
 299         image[i + 1] = bgra_palette[index * 4 + 1];
 300         image[i + 2] = bgra_palette[index * 4];
 301     }
 302 
 303     private void readPackedBits(byte[] image, int rowLength, int hght)
 304             throws IOException
 305     {
 306         int pixPerByte = 8 / bih.biBitCount;
 307         int bytesPerLine = (bih.biWidth + pixPerByte - 1) / pixPerByte;
 308         int srcStride = (bytesPerLine + 3) & ~3;
 309         int bitMask = (1 << bih.biBitCount) - 1;
 310 
 311         byte lineBuf[] = new byte[srcStride];
 312         for (int i = 0; i != hght; ++i) {
 313             int line = bih.biHeight < 0 ? i : hght - i - 1;
 314             int nRead = data.in.read(lineBuf);
 315 
 316             for (int x = 0; x != bih.biWidth; x++) {
 317                 int bitnum = x * bih.biBitCount;
 318                 int element = lineBuf[bitnum / 8];
 319                 int shift = 8 - (bitnum & 7) - bih.biBitCount;
 320                 int index = (element >> shift) & bitMask;
 321                 setRGBFromPalette(image, rowLength, x, line, index);
 322             }
 323             if (nRead != srcStride) {
 324                 break;
 325             }
 326         }
 327     }
 328 
 329     private static int getWord(byte[] buf, int pos) {
 330         return buf[pos] & 0xff | buf[pos + 1] << 8 & 0xff00;
 331     }
 332 
 333     private static int getByte(byte[] buf, int pos) {
 334         return buf[pos] & 0xff;
 335     }
 336 
 337     private void read16Bit(byte[] image, int rowLength, int hght) throws IOException {
 338         int bytesPerLine = bih.biWidth * 2;
 339         int srcStride = (bytesPerLine + 3) & ~3;
 340         byte lineBuf[] = new byte[srcStride];
 341         for (int i = 0; i != hght; ++i) {
 342             int line = bih.biHeight < 0 ? i : hght - i - 1;
 343             int nRead = data.in.read(lineBuf);
 344 
 345             for (int x = 0; x != bih.biWidth; x++) {
 346                 int element = getWord(lineBuf, x * 2);
 347                 for (int b = 0; b < 3; b++) {
 348                     byte c = (byte) ((element & masks[b]) >>> maskOffsets[b]);
 349                     c = (byte) ((double) c / ((1 << 5) - 1) * 255 + 0.5);
 350                     image[line * rowLength + x * 3 + b] = c;
 351                 }
 352             }
 353             if (nRead != srcStride) {
 354                 break;
 355             }
 356         }
 357     }
 358 
 359     private void read32Bit(byte[] image, int rowLength, int hght) throws IOException {
 360         int bytesPerLine = bih.biWidth * 4;
 361         byte lineBuf[] = new byte[bytesPerLine];
 362         for (int i = 0; i != hght; ++i) {
 363             int line = bih.biHeight < 0 ? i : hght - i - 1;
 364             int nRead = data.in.read(lineBuf);
 365 
 366             for (int x = 0; x != bih.biWidth; x++) {
 367                 int element = lineBuf[x] << 24
 368                         | lineBuf[x + 1] << 16
 369                         | lineBuf[x + 2] << 8
 370                         | lineBuf[x + 3];
 371                 for (int b = 0; b < 3; b++) {
 372                     image[line * rowLength + x * 3 + b]
 373                             = (byte) ((element & masks[b]) >>> maskOffsets[b]);
 374                 }
 375             }
 376             if (nRead != bytesPerLine) {
 377                 break;
 378             }
 379         }
 380     }
 381 
 382     private void read24Bit(byte[] image, int rowLength, int hght) throws IOException {
 383         int bmpStride = (rowLength + 3) & ~3;
 384 
 385         for (int i = 0; i != hght; ++i) {
 386             int line = bih.biHeight < 0 ? i : hght - i - 1;
 387             int nRead = data.in.read(image, line * rowLength, rowLength);
 388             GBRtoRGB(image, line * rowLength, nRead);
 389 
 390             if (nRead != rowLength) {
 391                 break;
 392             }
 393 
 394             if (nRead < bmpStride) {
 395                 data.skipBytes(bmpStride - nRead);
 396             }
 397         }
 398     }
 399 
 400     static void GBRtoRGB(byte data[], int pos, int size) {
 401         for (int sz = size / 3; sz != 0; --sz) {
 402             byte x = data[pos], y = data[pos + 2];
 403             data[pos + 2] = x; data[pos] = y;
 404             pos += 3;
 405         }
 406     }
 407 
 408     public ImageFrame load(int imageIndex, int width, int height,
 409             boolean preserveAspectRatio, boolean smooth) throws IOException
 410     {
 411         if (0 != imageIndex) {
 412             return null;
 413         }
 414 
 415         int hght = Math.abs(bih.biHeight);
 416 
 417         if ((width > 0 && width != bih.biWidth) ||
 418             (height > 0 && height != hght))
 419         {
 420             throw new IOException("scaling for BMP is not supported");
 421         }
 422 
 423         // Pass image metadata to any listeners.
 424         ImageMetadata imageMetadata = new ImageMetadata(null, Boolean.TRUE,
 425             null, null, null, null, bih.biWidth, hght,
 426             null, null, null);
 427         updateImageMetadata(imageMetadata);
 428 
 429         int stride = bih.biWidth * 3;
 430 
 431         byte image[] = new byte[stride * hght];
 432 
 433         switch (bih.biBitCount) {
 434             case 1:
 435                 readPackedBits(image, stride, hght);
 436                 break;
 437             case 4:
 438                 if (bih.biCompression == BitmapInfoHeader.BI_RLE4) {
 439                     readRLE(image, stride, hght, true);
 440                 } else {
 441                     readPackedBits(image, stride, hght);
 442                 }
 443                 break;
 444             case 8:
 445                 if (bih.biCompression == BitmapInfoHeader.BI_RLE8) {
 446                     readRLE(image, stride, hght, false);
 447                 } else {
 448                     readPackedBits(image, stride, hght);
 449                 }
 450                 break;
 451             case 16:
 452                 read16Bit(image, stride, hght);
 453                 break;
 454             case 32:
 455                 read32Bit(image, stride, hght);
 456                 break;
 457             case 24:
 458                 read24Bit(image, stride, hght);
 459                 break;
 460         }
 461 
 462         return new ImageFrame(ImageStorage.ImageType.RGB, ByteBuffer.wrap(image),
 463                 bih.biWidth, hght, stride, null, null);
 464     }
 465 }
 466 
 467 public final class BMPImageLoaderFactory implements ImageLoaderFactory {
 468 
 469     private static final BMPImageLoaderFactory theInstance =
 470             new BMPImageLoaderFactory();
 471 
 472     public static ImageLoaderFactory getInstance() {
 473         return theInstance;
 474     }
 475 
 476     public ImageFormatDescription getFormatDescription() {
 477         return BMPDescriptor.theInstance;
 478     }
 479 
 480     public ImageLoader createImageLoader(InputStream input) throws IOException {
 481         return new BMPImageLoader(input);
 482     }
 483 }