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 }