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 88 final int biSize; 89 final int biWidth; 90 final int biHeight; 91 final short biPlanes; 92 final short biBitCount; 93 final int biCompression; 94 final int biSizeImage; 95 final int biXPelsPerMeter; 96 final int biYPelsPerMeter; 97 final int biClrUsed; 98 final int biClrImportant; 99 100 BitmapInfoHeader(LEInputStream data) throws IOException { 101 biSize = data.readInt(); 102 biWidth = data.readInt(); 103 biHeight = data.readInt(); 104 biPlanes = data.readShort(); 105 biBitCount = data.readShort(); 106 biCompression = data.readInt(); 107 biSizeImage = data.readInt(); 108 biXPelsPerMeter = data.readInt(); 109 biYPelsPerMeter = data.readInt(); 110 biClrUsed = data.readInt(); 111 biClrImportant = data.readInt(); 112 113 if (biSize > BIH_SIZE) { 114 if (biSize == BIH4_SIZE || biSize == BIH5_SIZE) { 115 data.skipBytes(biSize - BIH_SIZE); 116 } else { 117 throw new IOException("BitmapInfoHeader is corrupt"); 118 } 119 } 120 validate(); 121 } 122 123 void validate() { 124 if (biCompression != 0 || biPlanes != 1 || biBitCount != 24) { 125 throw new RuntimeException( 126 "Unsupported BMP image: " + 127 "only 24 bit uncompressed BMP`s is supported"); 128 } 129 } 130 } 131 132 final class BMPImageLoader extends ImageLoaderImpl { 133 134 static final short BM = 0x4D42; 135 static final int BFH_SIZE = 14; 136 137 final LEInputStream data; 138 139 short bfType; // must be equal to BM 140 int bfSize; 141 int bfOffBits; 142 int bgra_palette[]; 143 BitmapInfoHeader bih; 144 145 BMPImageLoader(InputStream input) throws IOException { 146 super(BMPDescriptor.theInstance); 147 data = new LEInputStream(input); 148 bfType = data.readShort(); 149 if (isValid()) { 150 readHeader(); 151 } 152 } 153 154 private void readHeader() throws IOException { 155 bfSize = data.readInt(); 156 data.skipBytes(4); // 32 bits reserved 157 bfOffBits = data.readInt(); 158 bih = new BitmapInfoHeader(data); 159 if (bih.biSize + BFH_SIZE != bfOffBits) { 160 data.skipBytes(bfOffBits - bih.biSize - BFH_SIZE); 161 } 162 } 163 164 private boolean isValid() { 165 return bfType == BM; 166 } 167 168 public void dispose() { } 169 170 static void GBRtoRGB(byte data[], int pos, int size) { 171 for (int sz = size / 3; sz != 0; --sz) { 172 byte x = data[pos], y = data[pos + 2]; 173 data[pos + 2] = x; data[pos] = y; 174 pos += 3; 175 } 176 } 177 178 public ImageFrame load(int imageIndex, int width, int height, 179 boolean preserveAspectRatio, boolean smooth) throws IOException 180 { 181 if (0 != imageIndex) { 182 return null; 183 } 184 185 if ((width > 0 && width != bih.biWidth) || 186 (height > 0 && height != bih.biHeight)) 187 { 188 throw new RuntimeException("scaling for BMP is not supported"); 189 } 190 191 // Pass image metadata to any listeners. 192 ImageMetadata imageMetadata = new ImageMetadata(null, Boolean.TRUE, 193 null, null, null, null, bih.biWidth, bih.biHeight, 194 null, null, null); 195 updateImageMetadata(imageMetadata); 196 197 int bmpStride = (bih.biBitCount*bih.biWidth/8 + 3) & ~3; 198 int rowLength = (bih.biBitCount/8)*bih.biWidth; 199 200 int hght = Math.abs(bih.biHeight); 201 202 byte image[] = new byte[rowLength * hght]; 203 204 for (int i = 0; i != hght; ++i) { 205 int line = bih.biHeight < 0 ? i : hght-i-1; 206 int nRead = data.in.read(image, line * rowLength, rowLength); 207 GBRtoRGB(image, line * rowLength, nRead); 208 209 if (nRead != rowLength) { 210 break; 211 } 212 213 if (nRead < bmpStride) { 214 data.skipBytes(bmpStride-nRead); 215 } 216 } 217 218 return new ImageFrame(ImageStorage.ImageType.RGB, ByteBuffer.wrap(image), 219 bih.biWidth, hght, rowLength, null, null); 220 } 221 } 222 223 public final class BMPImageLoaderFactory implements ImageLoaderFactory { 224 225 private static final BMPImageLoaderFactory theInstance = 226 new BMPImageLoaderFactory(); 227 228 public static ImageLoaderFactory getInstance() { 229 return theInstance; 230 } 231 232 public ImageFormatDescription getFormatDescription() { 233 return BMPDescriptor.theInstance; 234 } 235 236 public ImageLoader createImageLoader(InputStream input) throws IOException { 237 return new BMPImageLoader(input); 238 } 239 } 240 | 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 } |