1 /* 2 * Copyright (c) 2011, 2014, 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 }