1 /*
   2  * Copyright (c) 2011, 2015, 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         ImageTools.skipFully(in, n);
  76     }
  77 }
  78 
  79 final class BitmapInfoHeader {
  80 
  81     static final int BIH_SIZE = 40;
  82     static final int BIH4_SIZE = 108;
  83     static final int BIH5_SIZE = 124;
  84     static final int BI_RGB = 0;
  85     static final int BI_RLE8 = 1;
  86     static final int BI_RLE4 = 2;
  87     static final int BI_BITFIELDS = 3;
  88     static final int BI_JPEG = 4;
  89     static final int BI_PNG = 5;
  90 
  91     final int    biSize;
  92     final int    biWidth;
  93     final int    biHeight;
  94     final short  biPlanes;
  95     final short  biBitCount;
  96     final int    biCompression;
  97     final int    biSizeImage;
  98     final int    biXPelsPerMeter;
  99     final int    biYPelsPerMeter;
 100     final int    biClrUsed;
 101     final int    biClrImportant;
 102 
 103     BitmapInfoHeader(LEInputStream data) throws IOException {
 104         biSize = data.readInt();
 105         biWidth = data.readInt();
 106         biHeight = data.readInt();
 107         biPlanes = data.readShort();
 108         biBitCount = data.readShort();
 109         biCompression = data.readInt();
 110         biSizeImage = data.readInt();
 111         biXPelsPerMeter = data.readInt();
 112         biYPelsPerMeter = data.readInt();
 113         biClrUsed = data.readInt();
 114         biClrImportant = data.readInt();
 115 
 116         if (biSize > BIH_SIZE) {
 117             if (biSize == BIH4_SIZE || biSize == BIH5_SIZE) {
 118                 data.skipBytes(biSize - BIH_SIZE);
 119             } else {
 120                 throw new IOException("BitmapInfoHeader is corrupt");
 121             }
 122         }
 123         validate();
 124     }
 125 
 126     void validate() throws IOException {
 127         if (biBitCount < 1 ||
 128                 biCompression == BI_JPEG || biCompression == BI_PNG)
 129         {
 130             throw new IOException("Unsupported BMP image: " +
 131                     "Embedded JPEG or PNG images are not supported");
 132         }
 133 
 134         switch (biCompression) {
 135             case BI_RLE4:
 136                 if (biBitCount != 4) {
 137                     throw new IOException("Invalid BMP image: " +
 138                             "Only 4 bpp images can be RLE4 compressed");
 139                 }
 140                 break;
 141             case BI_RLE8:
 142                 if (biBitCount != 8) {
 143                     throw new IOException("Invalid BMP image: " +
 144                             "Only 8 bpp images can be RLE8 compressed");
 145                 }
 146                 break;
 147             case BI_BITFIELDS:
 148                 if (biBitCount != 16 && biBitCount != 32) {
 149                     throw new IOException("Invalid BMP image: " +
 150                             "Only 16 or 32 bpp images can use BITFIELDS compression");
 151                 }
 152                 break;
 153             case BI_RGB:
 154                 break;
 155             default:
 156                 throw new IOException("Unknown BMP compression type");
 157         }
 158     }
 159 }
 160 
 161 final class BMPImageLoader extends ImageLoaderImpl {
 162 
 163     static final short BM = 0x4D42;
 164     static final int BFH_SIZE = 14;
 165 
 166     final LEInputStream data;
 167 
 168     int   bfSize;
 169     int   bfOffBits;
 170     byte  bgra_palette[];
 171     BitmapInfoHeader bih;
 172     
 173     // BI_BITFIELDS support
 174     int bitMasks[];
 175     int bitOffsets[];
 176 
 177     BMPImageLoader(InputStream input) throws IOException {
 178         super(BMPDescriptor.theInstance);
 179         data = new LEInputStream(input);
 180         if (data.readShort() != BM) {
 181             throw new IOException("Invalid BMP file signature");
 182         }
 183         readHeader();
 184     }
 185 
 186     private void readHeader() throws IOException {
 187         bfSize = data.readInt();
 188         data.skipBytes(4); // 32  bits reserved
 189         bfOffBits = data.readInt();
 190         bih = new BitmapInfoHeader(data);
 191         if (bfOffBits < bih.biSize + BFH_SIZE) {
 192             throw new IOException("Invalid bitmap bits offset");
 193         }
 194 
 195         if (bih.biSize + BFH_SIZE != bfOffBits) {
 196             int length = bfOffBits - bih.biSize - BFH_SIZE;
 197             int paletteSize = length / 4;
 198             bgra_palette = new byte[paletteSize * 4];
 199             int read = data.in.read(bgra_palette);
 200             // goto bitmap bits
 201             if (read < length) {
 202                 data.skipBytes(length - read);
 203             }
 204         }
 205         
 206         if (bih.biCompression == BitmapInfoHeader.BI_BITFIELDS) {
 207             parseBitfields();
 208         } else if (bih.biCompression == BitmapInfoHeader.BI_RGB &&
 209                 bih.biBitCount == 16)
 210         {
 211             bitMasks = new int[] { 0x7C00, 0x3E0, 0x1F };
 212             bitOffsets = new int[] { 10, 5, 0 };
 213         }
 214     }
 215 
 216     private void parseBitfields() throws IOException {
 217         if (bgra_palette.length != 12) {
 218             throw new IOException("Invalid bit masks");
 219         }
 220         bitMasks = new int[3];
 221         bitOffsets = new int[3];
 222         for (int i = 0; i < 3; i++) {
 223             int mask = getDWord(bgra_palette, i * 4);
 224             bitMasks[i] = mask;
 225             int offset = 0;
 226             if (mask != 0) {
 227                 while ((mask & 1) == 0) {
 228                     offset++;
 229                     mask = mask >>> 1;
 230                 }
 231                 if (!isPow2Minus1(mask)) {
 232                     throw new IOException("Bit mask is not contiguous");
 233                 }
 234             }
 235             bitOffsets[i] = offset;
 236         }
 237         if (!checkDisjointMasks(bitMasks[0], bitMasks[1], bitMasks[2])) {
 238             throw new IOException("Bit masks overlap");
 239         }
 240     }
 241 
 242     static boolean checkDisjointMasks(int m1, int m2, int m3) {
 243         return ((m1 & m2) | (m1 & m3) | (m2 & m3)) == 0;
 244     }
 245 
 246     static boolean isPow2Minus1(int i) {
 247         return (i & (i + 1)) == 0;
 248     }
 249 
 250     @Override
 251     public void dispose() {
 252     }
 253 
 254     private void readRLE(byte[] image, int rowLength, int hght, boolean isRLE4)
 255             throws IOException
 256     {
 257         int imgSize = bih.biSizeImage;
 258         if (imgSize == 0) {
 259             imgSize = bfSize - bfOffBits;
 260         }
 261         byte imgData[] = new byte[imgSize];
 262         ImageTools.readFully(data.in, imgData);
 263 
 264         boolean isBottomUp = bih.biHeight > 0;
 265         int line = isBottomUp ? hght - 1 : 0;
 266         int i = 0;
 267         int dstOffset = line * rowLength;
 268         while (i < imgSize) {
 269             int b1 = getByte(imgData, i++);
 270             int b2 = getByte(imgData, i++);
 271             if (b1 == 0) { // absolute
 272                 switch (b2) {
 273                     case 0: // end of line
 274                         line += isBottomUp ? -1 : 1;
 275                         dstOffset = line * rowLength;
 276                         break;
 277                     case 1: // end of bitmap
 278                         return;
 279                     case 2: // delta
 280                         int deltaX = getByte(imgData, i++);
 281                         int deltaY = getByte(imgData, i++);
 282                         line += deltaY;
 283                         dstOffset += (deltaY * rowLength);
 284                         dstOffset += deltaX * 3;
 285                         break;
 286                     default:
 287                         int indexData = 0;
 288                         int index;
 289                         for (int p = 0; p < b2; p++) {
 290                             if (isRLE4) {
 291                                 if ((p & 1) == 0) {
 292                                     indexData = getByte(imgData, i++);
 293                                     index = (indexData & 0xf0) >> 4;
 294                                 } else {
 295                                     index = indexData & 0x0f;
 296                                 }
 297                             } else {
 298                                 index = getByte(imgData, i++);
 299                             }
 300                             dstOffset = setRGBFromPalette(image, dstOffset, index);
 301                         }
 302                         if (isRLE4) {
 303                             if ((b2 & 3) == 1 || (b2 & 3) == 2) i++;
 304                         } else {
 305                             if ((b2 & 1) == 1) i++;
 306                         }
 307                         break;
 308                 }
 309             } else { // encoded
 310                 if (isRLE4) {
 311                     int index1 = (b2 & 0xf0) >> 4;
 312                     int index2 = b2 & 0x0f;
 313                     for (int p = 0; p < b1; p++) {
 314                         dstOffset = setRGBFromPalette(image, dstOffset,
 315                                 (p & 1) == 0 ? index1 : index2);
 316                     }
 317                 } else {
 318                     for (int p = 0; p < b1; p++) {
 319                         dstOffset = setRGBFromPalette(image, dstOffset, b2);
 320                     }
 321                 }
 322             }
 323         }
 324 
 325     }
 326 
 327     private int setRGBFromPalette(byte[] image, int dstOffset, int index) {
 328         index *= 4;
 329         image[dstOffset++] = bgra_palette[index + 2];
 330         image[dstOffset++] = bgra_palette[index + 1];
 331         image[dstOffset++] = bgra_palette[index];
 332         return dstOffset;
 333     }
 334 
 335     private void readPackedBits(byte[] image, int rowLength, int hght)
 336             throws IOException
 337     {
 338         int pixPerByte = 8 / bih.biBitCount;
 339         int bytesPerLine = (bih.biWidth + pixPerByte - 1) / pixPerByte;
 340         int srcStride = (bytesPerLine + 3) & ~3;
 341         int bitMask = (1 << bih.biBitCount) - 1;
 342 
 343         byte lineBuf[] = new byte[srcStride];
 344         for (int i = 0; i != hght; ++i) {
 345             ImageTools.readFully(data.in, lineBuf);
 346             int line = bih.biHeight < 0 ? i : hght - i - 1;
 347             int dstOffset = line * rowLength;
 348 
 349             for (int x = 0; x != bih.biWidth; x++) {
 350                 int bitnum = x * bih.biBitCount;
 351                 int element = lineBuf[bitnum / 8];
 352                 int shift = 8 - (bitnum & 7) - bih.biBitCount;
 353                 int index = (element >> shift) & bitMask;
 354                 dstOffset = setRGBFromPalette(image, dstOffset, index);
 355             }
 356         }
 357     }
 358 
 359     private static int getDWord(byte[] buf, int pos) {
 360         return ((buf[pos    ] & 0xff)     ) |
 361                ((buf[pos + 1] & 0xff) << 8) |
 362                ((buf[pos + 2] & 0xff) << 16) |
 363                ((buf[pos + 3] & 0xff) << 24);
 364     }
 365 
 366     private static int getWord(byte[] buf, int pos) {
 367         return ((buf[pos    ] & 0xff)     ) |
 368                ((buf[pos + 1] & 0xff) << 8);
 369     }
 370 
 371     private static int getByte(byte[] buf, int pos) {
 372         return buf[pos] & 0xff;
 373     }
 374 
 375     @FunctionalInterface
 376     private interface BitConverter {
 377         public byte convert(int i, int mask, int offset);
 378     }
 379 
 380     private static byte convertFrom5To8Bit(int i, int mask, int offset) {
 381         int b = (i & mask) >>> offset;
 382         return (byte)(b << 3 | b >> 2);
 383     }
 384 
 385     private static byte convertFromXTo8Bit(int i, int mask, int offset) {
 386         int b = (i & mask) >>> offset;
 387         return (byte)(b * 255.0 / (mask >>> offset));
 388     }
 389 
 390     private void read16Bit(byte[] image, int rowLength, int hght, BitConverter converter)
 391             throws IOException
 392     {
 393         int bytesPerLine = bih.biWidth * 2;
 394         int srcStride = (bytesPerLine + 3) & ~3;
 395         byte lineBuf[] = new byte[srcStride];
 396         for (int i = 0; i != hght; ++i) {
 397             ImageTools.readFully(data.in, lineBuf);
 398             int line = bih.biHeight < 0 ? i : hght - i - 1;
 399             int dstOffset = line * rowLength;
 400 
 401             for (int x = 0; x != bih.biWidth; x++) {
 402                 int element = getWord(lineBuf, x * 2);
 403                 for (int j = 0; j < 3; j++) {
 404                     image[dstOffset++] =
 405                             converter.convert(element, bitMasks[j], bitOffsets[j]);
 406                 }
 407             }
 408         }
 409     }
 410 
 411     private void read32BitRGB(byte[] image, int rowLength, int hght) throws IOException {
 412         int bytesPerLine = bih.biWidth * 4;
 413         byte lineBuf[] = new byte[bytesPerLine];
 414         for (int i = 0; i != hght; ++i) {
 415             ImageTools.readFully(data.in, lineBuf);
 416             int line = bih.biHeight < 0 ? i : hght - i - 1;
 417             int dstOff = line * rowLength;
 418 
 419             for (int x = 0; x != bih.biWidth; x++) {
 420                 int srcOff = x * 4;
 421                 image[dstOff++] = lineBuf[srcOff + 2];
 422                 image[dstOff++] = lineBuf[srcOff + 1];
 423                 image[dstOff++] = lineBuf[srcOff    ];
 424             }
 425         }
 426     }
 427 
 428     private void read32BitBF(byte[] image, int rowLength, int hght) throws IOException {
 429         int bytesPerLine = bih.biWidth * 4;
 430         byte lineBuf[] = new byte[bytesPerLine];
 431         for (int i = 0; i != hght; ++i) {
 432             ImageTools.readFully(data.in, lineBuf);
 433             int line = bih.biHeight < 0 ? i : hght - i - 1;
 434             int dstOff = line * rowLength;
 435 
 436             for (int x = 0; x != bih.biWidth; x++) {
 437                 int srcOff = x * 4;
 438                 int element = getDWord(lineBuf, srcOff);
 439                 for (int j = 0; j < 3; j++) {
 440                     image[dstOff++] =
 441                             convertFromXTo8Bit(element, bitMasks[j], bitOffsets[j]);
 442                 }
 443             }
 444         }
 445     }
 446 
 447     private void read24Bit(byte[] image, int rowLength, int hght) throws IOException {
 448         int bmpStride = (rowLength + 3) & ~3;
 449         int padding = bmpStride - rowLength;
 450 
 451         for (int i = 0; i != hght; ++i) {
 452             int line = bih.biHeight < 0 ? i : hght - i - 1;
 453             int lineOffset = line * rowLength;
 454             ImageTools.readFully(data.in, image, lineOffset, rowLength);
 455             data.skipBytes(padding);
 456             BGRtoRGB(image, lineOffset, rowLength);
 457         }
 458     }
 459 
 460     static void BGRtoRGB(byte data[], int pos, int size) {
 461         for (int sz = size / 3; sz != 0; --sz) {
 462             byte b = data[pos], r = data[pos + 2];
 463             data[pos + 2] = b; data[pos] = r;
 464             pos += 3;
 465         }
 466     }
 467 
 468     public ImageFrame load(int imageIndex, int width, int height,
 469             boolean preserveAspectRatio, boolean smooth) throws IOException
 470     {
 471         if (0 != imageIndex) {
 472             return null;
 473         }
 474 
 475         int hght = Math.abs(bih.biHeight);
 476 
 477         if ((width > 0 && width != bih.biWidth) ||
 478             (height > 0 && height != hght))
 479         {
 480             throw new IOException("scaling for BMP is not supported");
 481         }
 482 
 483         // Pass image metadata to any listeners.
 484         ImageMetadata imageMetadata = new ImageMetadata(null, Boolean.TRUE,
 485             null, null, null, null, null, bih.biWidth, hght,
 486             null, null, null);
 487         updateImageMetadata(imageMetadata);
 488 
 489         int stride = bih.biWidth * 3;
 490 
 491         byte image[] = new byte[stride * hght];
 492 
 493         switch (bih.biBitCount) {
 494             case 1:
 495                 readPackedBits(image, stride, hght);
 496                 break;
 497             case 4:
 498                 if (bih.biCompression == BitmapInfoHeader.BI_RLE4) {
 499                     readRLE(image, stride, hght, true);
 500                 } else {
 501                     readPackedBits(image, stride, hght);
 502                 }
 503                 break;
 504             case 8:
 505                 if (bih.biCompression == BitmapInfoHeader.BI_RLE8) {
 506                     readRLE(image, stride, hght, false);
 507                 } else {
 508                     readPackedBits(image, stride, hght);
 509                 }
 510                 break;
 511             case 16:
 512                 if (bih.biCompression == BitmapInfoHeader.BI_BITFIELDS) {
 513                     read16Bit(image, stride, hght, BMPImageLoader::convertFromXTo8Bit);
 514                 } else {
 515                     read16Bit(image, stride, hght, BMPImageLoader::convertFrom5To8Bit);
 516                 }
 517                 break;
 518             case 32:
 519                 if (bih.biCompression == BitmapInfoHeader.BI_BITFIELDS) {
 520                     read32BitBF(image, stride, hght);
 521                 } else {
 522                     read32BitRGB(image, stride, hght);
 523                 }
 524                 break;
 525             case 24:
 526                 read24Bit(image, stride, hght);
 527                 break;
 528             default:
 529                 throw new IOException("Unknown BMP bit depth");
 530         }
 531 
 532         return new ImageFrame(ImageStorage.ImageType.RGB, ByteBuffer.wrap(image),
 533                 bih.biWidth, hght, stride, null, null);
 534     }
 535 }
 536 
 537 public final class BMPImageLoaderFactory implements ImageLoaderFactory {
 538 
 539     private static final BMPImageLoaderFactory theInstance =
 540             new BMPImageLoaderFactory();
 541 
 542     public static ImageLoaderFactory getInstance() {
 543         return theInstance;
 544     }
 545 
 546     public ImageFormatDescription getFormatDescription() {
 547         return BMPDescriptor.theInstance;
 548     }
 549 
 550     public ImageLoader createImageLoader(InputStream input) throws IOException {
 551         return new BMPImageLoader(input);
 552     }
 553 }