001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.imaging.formats.bmp; 018 019import static org.apache.commons.imaging.common.BinaryFunctions.read2Bytes; 020import static org.apache.commons.imaging.common.BinaryFunctions.read4Bytes; 021import static org.apache.commons.imaging.common.BinaryFunctions.readByte; 022import static org.apache.commons.imaging.common.BinaryFunctions.readBytes; 023 024import java.awt.Dimension; 025import java.awt.image.BufferedImage; 026import java.io.ByteArrayOutputStream; 027import java.io.IOException; 028import java.io.InputStream; 029import java.io.OutputStream; 030import java.io.PrintWriter; 031import java.nio.ByteOrder; 032import java.util.ArrayList; 033import java.util.List; 034import java.util.logging.Level; 035import java.util.logging.Logger; 036 037import org.apache.commons.imaging.FormatCompliance; 038import org.apache.commons.imaging.ImageFormat; 039import org.apache.commons.imaging.ImageFormats; 040import org.apache.commons.imaging.ImageInfo; 041import org.apache.commons.imaging.ImageParser; 042import org.apache.commons.imaging.ImageReadException; 043import org.apache.commons.imaging.ImageWriteException; 044import org.apache.commons.imaging.PixelDensity; 045import org.apache.commons.imaging.common.BinaryOutputStream; 046import org.apache.commons.imaging.common.ImageBuilder; 047import org.apache.commons.imaging.common.ImageMetadata; 048import org.apache.commons.imaging.common.bytesource.ByteSource; 049import org.apache.commons.imaging.palette.PaletteFactory; 050import org.apache.commons.imaging.palette.SimplePalette; 051 052public class BmpImageParser extends ImageParser<BmpImagingParameters> { 053 054 private static final Logger LOGGER = Logger.getLogger(BmpImageParser.class.getName()); 055 056 private static final String DEFAULT_EXTENSION = ImageFormats.BMP.getDefaultExtension(); 057 private static final String[] ACCEPTED_EXTENSIONS = ImageFormats.BMP.getExtensions(); 058 private static final byte[] BMP_HEADER_SIGNATURE = { 0x42, 0x4d, }; 059 private static final int BI_RGB = 0; 060 private static final int BI_RLE4 = 2; 061 private static final int BI_RLE8 = 1; 062 private static final int BI_BITFIELDS = 3; 063 private static final int BITMAP_FILE_HEADER_SIZE = 14; 064 private static final int BITMAP_INFO_HEADER_SIZE = 40; 065 066 public BmpImageParser() { 067 super.setByteOrder(ByteOrder.LITTLE_ENDIAN); 068 } 069 070 @Override 071 public BmpImagingParameters getDefaultParameters() { 072 return new BmpImagingParameters(); 073 } 074 075 @Override 076 public String getName() { 077 return "Bmp-Custom"; 078 } 079 080 @Override 081 public String getDefaultExtension() { 082 return DEFAULT_EXTENSION; 083 } 084 085 @Override 086 protected String[] getAcceptedExtensions() { 087 return ACCEPTED_EXTENSIONS; 088 } 089 090 @Override 091 protected ImageFormat[] getAcceptedTypes() { 092 return new ImageFormat[] { ImageFormats.BMP, // 093 }; 094 } 095 096 private BmpHeaderInfo readBmpHeaderInfo(final InputStream is, 097 final FormatCompliance formatCompliance) 098 throws ImageReadException, IOException { 099 final byte identifier1 = readByte("Identifier1", is, "Not a Valid BMP File"); 100 final byte identifier2 = readByte("Identifier2", is, "Not a Valid BMP File"); 101 102 if (formatCompliance != null) { 103 formatCompliance.compareBytes("Signature", BMP_HEADER_SIGNATURE, 104 new byte[]{identifier1, identifier2,}); 105 } 106 107 final int fileSize = read4Bytes("File Size", is, "Not a Valid BMP File", getByteOrder()); 108 final int reserved = read4Bytes("Reserved", is, "Not a Valid BMP File", getByteOrder()); 109 final int bitmapDataOffset = read4Bytes("Bitmap Data Offset", is, "Not a Valid BMP File", getByteOrder()); 110 111 final int bitmapHeaderSize = read4Bytes("Bitmap Header Size", is, "Not a Valid BMP File", getByteOrder()); 112 int width = 0; 113 int height = 0; 114 int planes = 0; 115 int bitsPerPixel = 0; 116 int compression = 0; 117 int bitmapDataSize = 0; 118 int hResolution = 0; 119 int vResolution = 0; 120 int colorsUsed = 0; 121 int colorsImportant = 0; 122 int redMask = 0; 123 int greenMask = 0; 124 int blueMask = 0; 125 int alphaMask = 0; 126 int colorSpaceType = 0; 127 final BmpHeaderInfo.ColorSpace colorSpace = new BmpHeaderInfo.ColorSpace(); 128 colorSpace.red = new BmpHeaderInfo.ColorSpaceCoordinate(); 129 colorSpace.green = new BmpHeaderInfo.ColorSpaceCoordinate(); 130 colorSpace.blue = new BmpHeaderInfo.ColorSpaceCoordinate(); 131 int gammaRed = 0; 132 int gammaGreen = 0; 133 int gammaBlue = 0; 134 int intent = 0; 135 int profileData = 0; 136 int profileSize = 0; 137 int reservedV5 = 0; 138 139 if (bitmapHeaderSize < 40) { 140 throw new ImageReadException("Invalid/unsupported BMP file"); 141 } 142 // BITMAPINFOHEADER 143 width = read4Bytes("Width", is, "Not a Valid BMP File", getByteOrder()); 144 height = read4Bytes("Height", is, "Not a Valid BMP File", getByteOrder()); 145 planes = read2Bytes("Planes", is, "Not a Valid BMP File", getByteOrder()); 146 bitsPerPixel = read2Bytes("Bits Per Pixel", is, "Not a Valid BMP File", getByteOrder()); 147 compression = read4Bytes("Compression", is, "Not a Valid BMP File", getByteOrder()); 148 bitmapDataSize = read4Bytes("Bitmap Data Size", is, "Not a Valid BMP File", getByteOrder()); 149 hResolution = read4Bytes("HResolution", is, "Not a Valid BMP File", getByteOrder()); 150 vResolution = read4Bytes("VResolution", is, "Not a Valid BMP File", getByteOrder()); 151 colorsUsed = read4Bytes("ColorsUsed", is, "Not a Valid BMP File", getByteOrder()); 152 colorsImportant = read4Bytes("ColorsImportant", is, "Not a Valid BMP File", getByteOrder()); 153 if (bitmapHeaderSize >= 52 || compression == BI_BITFIELDS) { 154 // 52 = BITMAPV2INFOHEADER, now undocumented 155 // see http://en.wikipedia.org/wiki/BMP_file_format 156 redMask = read4Bytes("RedMask", is, "Not a Valid BMP File", getByteOrder()); 157 greenMask = read4Bytes("GreenMask", is, "Not a Valid BMP File", getByteOrder()); 158 blueMask = read4Bytes("BlueMask", is, "Not a Valid BMP File", getByteOrder()); 159 } 160 if (bitmapHeaderSize >= 56) { 161 // 56 = the now undocumented BITMAPV3HEADER sometimes used by 162 // Photoshop 163 // see http://forums.adobe.com/thread/751592?tstart=1 164 alphaMask = read4Bytes("AlphaMask", is, "Not a Valid BMP File", getByteOrder()); 165 } 166 if (bitmapHeaderSize >= 108) { 167 // BITMAPV4HEADER 168 colorSpaceType = read4Bytes("ColorSpaceType", is, "Not a Valid BMP File", getByteOrder()); 169 colorSpace.red.x = read4Bytes("ColorSpaceRedX", is, "Not a Valid BMP File", getByteOrder()); 170 colorSpace.red.y = read4Bytes("ColorSpaceRedY", is, "Not a Valid BMP File", getByteOrder()); 171 colorSpace.red.z = read4Bytes("ColorSpaceRedZ", is, "Not a Valid BMP File", getByteOrder()); 172 colorSpace.green.x = read4Bytes("ColorSpaceGreenX", is, "Not a Valid BMP File", getByteOrder()); 173 colorSpace.green.y = read4Bytes("ColorSpaceGreenY", is, "Not a Valid BMP File", getByteOrder()); 174 colorSpace.green.z = read4Bytes("ColorSpaceGreenZ", is, "Not a Valid BMP File", getByteOrder()); 175 colorSpace.blue.x = read4Bytes("ColorSpaceBlueX", is, "Not a Valid BMP File", getByteOrder()); 176 colorSpace.blue.y = read4Bytes("ColorSpaceBlueY", is, "Not a Valid BMP File", getByteOrder()); 177 colorSpace.blue.z = read4Bytes("ColorSpaceBlueZ", is, "Not a Valid BMP File", getByteOrder()); 178 gammaRed = read4Bytes("GammaRed", is, "Not a Valid BMP File", getByteOrder()); 179 gammaGreen = read4Bytes("GammaGreen", is, "Not a Valid BMP File", getByteOrder()); 180 gammaBlue = read4Bytes("GammaBlue", is, "Not a Valid BMP File", getByteOrder()); 181 } 182 if (bitmapHeaderSize >= 124) { 183 // BITMAPV5HEADER 184 intent = read4Bytes("Intent", is, "Not a Valid BMP File", getByteOrder()); 185 profileData = read4Bytes("ProfileData", is, "Not a Valid BMP File", getByteOrder()); 186 profileSize = read4Bytes("ProfileSize", is, "Not a Valid BMP File", getByteOrder()); 187 reservedV5 = read4Bytes("Reserved", is, "Not a Valid BMP File", getByteOrder()); 188 } 189 190 if (LOGGER.isLoggable(Level.FINE)) { 191 debugNumber("identifier1", identifier1, 1); 192 debugNumber("identifier2", identifier2, 1); 193 debugNumber("fileSize", fileSize, 4); 194 debugNumber("reserved", reserved, 4); 195 debugNumber("bitmapDataOffset", bitmapDataOffset, 4); 196 debugNumber("bitmapHeaderSize", bitmapHeaderSize, 4); 197 debugNumber("width", width, 4); 198 debugNumber("height", height, 4); 199 debugNumber("planes", planes, 2); 200 debugNumber("bitsPerPixel", bitsPerPixel, 2); 201 debugNumber("compression", compression, 4); 202 debugNumber("bitmapDataSize", bitmapDataSize, 4); 203 debugNumber("hResolution", hResolution, 4); 204 debugNumber("vResolution", vResolution, 4); 205 debugNumber("colorsUsed", colorsUsed, 4); 206 debugNumber("colorsImportant", colorsImportant, 4); 207 if (bitmapHeaderSize >= 52 || compression == BI_BITFIELDS) { 208 debugNumber("redMask", redMask, 4); 209 debugNumber("greenMask", greenMask, 4); 210 debugNumber("blueMask", blueMask, 4); 211 } 212 if (bitmapHeaderSize >= 56) { 213 debugNumber("alphaMask", alphaMask, 4); 214 } 215 if (bitmapHeaderSize >= 108) { 216 debugNumber("colorSpaceType", colorSpaceType, 4); 217 debugNumber("colorSpace.red.x", colorSpace.red.x, 1); 218 debugNumber("colorSpace.red.y", colorSpace.red.y, 1); 219 debugNumber("colorSpace.red.z", colorSpace.red.z, 1); 220 debugNumber("colorSpace.green.x", colorSpace.green.x, 1); 221 debugNumber("colorSpace.green.y", colorSpace.green.y, 1); 222 debugNumber("colorSpace.green.z", colorSpace.green.z, 1); 223 debugNumber("colorSpace.blue.x", colorSpace.blue.x, 1); 224 debugNumber("colorSpace.blue.y", colorSpace.blue.y, 1); 225 debugNumber("colorSpace.blue.z", colorSpace.blue.z, 1); 226 debugNumber("gammaRed", gammaRed, 4); 227 debugNumber("gammaGreen", gammaGreen, 4); 228 debugNumber("gammaBlue", gammaBlue, 4); 229 } 230 if (bitmapHeaderSize >= 124) { 231 debugNumber("intent", intent, 4); 232 debugNumber("profileData", profileData, 4); 233 debugNumber("profileSize", profileSize, 4); 234 debugNumber("reservedV5", reservedV5, 4); 235 } 236 } 237 238 return new BmpHeaderInfo(identifier1, identifier2, 239 fileSize, reserved, bitmapDataOffset, bitmapHeaderSize, width, 240 height, planes, bitsPerPixel, compression, bitmapDataSize, 241 hResolution, vResolution, colorsUsed, colorsImportant, redMask, 242 greenMask, blueMask, alphaMask, colorSpaceType, colorSpace, 243 gammaRed, gammaGreen, gammaBlue, intent, profileData, 244 profileSize, reservedV5); 245 } 246 247 private byte[] getRLEBytes(final InputStream is, final int rleSamplesPerByte) throws IOException { 248 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 249 250 // this.setDebug(true); 251 252 boolean done = false; 253 while (!done) { 254 final int a = 0xff & readByte("RLE a", is, "BMP: Bad RLE"); 255 baos.write(a); 256 final int b = 0xff & readByte("RLE b", is, "BMP: Bad RLE"); 257 baos.write(b); 258 259 if (a == 0) { 260 switch (b) { 261 case 0: // EOL 262 break; 263 case 1: // EOF 264 // System.out.println("xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" 265 // ); 266 done = true; 267 break; 268 case 2: { 269 // System.out.println("xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" 270 // ); 271 final int c = 0xff & readByte("RLE c", is, "BMP: Bad RLE"); 272 baos.write(c); 273 final int d = 0xff & readByte("RLE d", is, "BMP: Bad RLE"); 274 baos.write(d); 275 276 } 277 break; 278 default: { 279 int size = b / rleSamplesPerByte; 280 if ((b % rleSamplesPerByte) > 0) { 281 size++; 282 } 283 if ((size % 2) != 0) { 284 size++; 285 } 286 287 // System.out.println("b: " + b); 288 // System.out.println("size: " + size); 289 // System.out.println("RLESamplesPerByte: " + 290 // RLESamplesPerByte); 291 // System.out.println("xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" 292 // ); 293 final byte[] bytes = readBytes("bytes", is, size, 294 "RLE: Absolute Mode"); 295 baos.write(bytes); 296 } 297 break; 298 } 299 } 300 } 301 302 return baos.toByteArray(); 303 } 304 305 private BmpImageContents readImageContents(final InputStream is, 306 final FormatCompliance formatCompliance) 307 throws ImageReadException, IOException { 308 final BmpHeaderInfo bhi = readBmpHeaderInfo(is, formatCompliance); 309 310 int colorTableSize = bhi.colorsUsed; 311 if (colorTableSize == 0) { 312 colorTableSize = (1 << bhi.bitsPerPixel); 313 } 314 315 if (LOGGER.isLoggable(Level.FINE)) { 316 debugNumber("ColorsUsed", bhi.colorsUsed, 4); 317 debugNumber("BitsPerPixel", bhi.bitsPerPixel, 4); 318 debugNumber("ColorTableSize", colorTableSize, 4); 319 debugNumber("bhi.colorsUsed", bhi.colorsUsed, 4); 320 debugNumber("Compression", bhi.compression, 4); 321 } 322 323 // A palette is always valid, even for images that don't need it 324 // (like 32 bpp), it specifies the "optimal color palette" for 325 // when the image is displayed on a <= 256 color graphics card. 326 int paletteLength; 327 int rleSamplesPerByte = 0; 328 boolean rle = false; 329 330 switch (bhi.compression) { 331 case BI_RGB: 332 if (LOGGER.isLoggable(Level.FINE)) { 333 LOGGER.fine("Compression: BI_RGB"); 334 } 335 if (bhi.bitsPerPixel <= 8) { 336 paletteLength = 4 * colorTableSize; 337 } else { 338 paletteLength = 0; 339 } 340 // BytesPerPaletteEntry = 0; 341 // System.out.println("Compression: BI_RGBx2: " + bhi.BitsPerPixel); 342 // System.out.println("Compression: BI_RGBx2: " + (bhi.BitsPerPixel 343 // <= 16)); 344 break; 345 346 case BI_RLE4: 347 if (LOGGER.isLoggable(Level.FINE)) { 348 LOGGER.fine("Compression: BI_RLE4"); 349 } 350 paletteLength = 4 * colorTableSize; 351 rleSamplesPerByte = 2; 352 // ExtraBitsPerPixel = 4; 353 rle = true; 354 // // BytesPerPixel = 2; 355 // // BytesPerPaletteEntry = 0; 356 break; 357 // 358 case BI_RLE8: 359 if (LOGGER.isLoggable(Level.FINE)) { 360 LOGGER.fine("Compression: BI_RLE8"); 361 } 362 paletteLength = 4 * colorTableSize; 363 rleSamplesPerByte = 1; 364 // ExtraBitsPerPixel = 8; 365 rle = true; 366 // BytesPerPixel = 2; 367 // BytesPerPaletteEntry = 0; 368 break; 369 // 370 case BI_BITFIELDS: 371 if (LOGGER.isLoggable(Level.FINE)) { 372 LOGGER.fine("Compression: BI_BITFIELDS"); 373 } 374 if (bhi.bitsPerPixel <= 8) { 375 paletteLength = 4 * colorTableSize; 376 } else { 377 paletteLength = 0; 378 } 379 // BytesPerPixel = 2; 380 // BytesPerPaletteEntry = 4; 381 break; 382 383 default: 384 throw new ImageReadException("BMP: Unknown Compression: " 385 + bhi.compression); 386 } 387 388 if (paletteLength < 0) { 389 throw new ImageReadException("BMP: Invalid negative palette length: " + paletteLength); 390 } 391 392 byte[] colorTable = null; 393 if (paletteLength > 0) { 394 colorTable = readBytes("ColorTable", is, paletteLength, 395 "Not a Valid BMP File"); 396 } 397 398 if (LOGGER.isLoggable(Level.FINE)) { 399 debugNumber("paletteLength", paletteLength, 4); 400 LOGGER.fine("ColorTable: " 401 + ((colorTable == null) ? "null" : Integer.toString(colorTable.length))); 402 } 403 404 int imageLineLength = (((bhi.bitsPerPixel) * bhi.width) + 7) / 8; 405 406 if (LOGGER.isLoggable(Level.FINE)) { 407 final int pixelCount = bhi.width * bhi.height; 408 // this.debugNumber("Total BitsPerPixel", 409 // (ExtraBitsPerPixel + bhi.BitsPerPixel), 4); 410 // this.debugNumber("Total Bit Per Line", 411 // ((ExtraBitsPerPixel + bhi.BitsPerPixel) * bhi.Width), 4); 412 // this.debugNumber("ExtraBitsPerPixel", ExtraBitsPerPixel, 4); 413 debugNumber("bhi.Width", bhi.width, 4); 414 debugNumber("bhi.Height", bhi.height, 4); 415 debugNumber("ImageLineLength", imageLineLength, 4); 416 // this.debugNumber("imageDataSize", imageDataSize, 4); 417 debugNumber("PixelCount", pixelCount, 4); 418 } 419 // int ImageLineLength = BytesPerPixel * bhi.Width; 420 while ((imageLineLength % 4) != 0) { 421 imageLineLength++; 422 } 423 424 final int headerSize = BITMAP_FILE_HEADER_SIZE 425 + bhi.bitmapHeaderSize 426 + (bhi.bitmapHeaderSize == 40 427 && bhi.compression == BI_BITFIELDS ? 3 * 4 : 0); 428 final int expectedDataOffset = headerSize + paletteLength; 429 430 if (LOGGER.isLoggable(Level.FINE)) { 431 debugNumber("bhi.BitmapDataOffset", bhi.bitmapDataOffset, 4); 432 debugNumber("expectedDataOffset", expectedDataOffset, 4); 433 } 434 final int extraBytes = bhi.bitmapDataOffset - expectedDataOffset; 435 if (extraBytes < 0) { 436 throw new ImageReadException("BMP has invalid image data offset: " 437 + bhi.bitmapDataOffset + " (expected: " 438 + expectedDataOffset + ", paletteLength: " + paletteLength 439 + ", headerSize: " + headerSize + ")"); 440 } 441 if (extraBytes > 0) { 442 readBytes("BitmapDataOffset", is, extraBytes, "Not a Valid BMP File"); 443 } 444 445 final int imageDataSize = bhi.height * imageLineLength; 446 447 if (LOGGER.isLoggable(Level.FINE)) { 448 debugNumber("imageDataSize", imageDataSize, 4); 449 } 450 451 byte[] imageData; 452 if (rle) { 453 imageData = getRLEBytes(is, rleSamplesPerByte); 454 } else { 455 imageData = readBytes("ImageData", is, imageDataSize, 456 "Not a Valid BMP File"); 457 } 458 459 if (LOGGER.isLoggable(Level.FINE)) { 460 debugNumber("ImageData.length", imageData.length, 4); 461 } 462 463 PixelParser pixelParser; 464 465 switch (bhi.compression) { 466 case BI_RLE4: 467 case BI_RLE8: 468 pixelParser = new PixelParserRle(bhi, colorTable, imageData); 469 break; 470 case BI_RGB: 471 pixelParser = new PixelParserRgb(bhi, colorTable, imageData); 472 break; 473 case BI_BITFIELDS: 474 pixelParser = new PixelParserBitFields(bhi, colorTable, imageData); 475 break; 476 default: 477 throw new ImageReadException("BMP: Unknown Compression: " 478 + bhi.compression); 479 } 480 481 return new BmpImageContents(bhi, colorTable, imageData, pixelParser); 482 } 483 484 private BmpHeaderInfo readBmpHeaderInfo(final ByteSource byteSource) throws ImageReadException, IOException { 485 try (InputStream is = byteSource.getInputStream()) { 486 // readSignature(is); 487 return readBmpHeaderInfo(is, null); 488 } 489 } 490 491 @Override 492 public byte[] getICCProfileBytes(final ByteSource byteSource, final BmpImagingParameters params) 493 throws ImageReadException, IOException { 494 return null; 495 } 496 497 @Override 498 public Dimension getImageSize(final ByteSource byteSource, final BmpImagingParameters params) 499 throws ImageReadException, IOException { 500 final BmpHeaderInfo bhi = readBmpHeaderInfo(byteSource); 501 502 return new Dimension(bhi.width, bhi.height); 503 504 } 505 506 @Override 507 public ImageMetadata getMetadata(final ByteSource byteSource, final BmpImagingParameters params) 508 throws ImageReadException, IOException { 509 // TODO this should throw UnsupportedOperationException, but RoundtripTest has to be refactored completely before this can be changed 510 return null; 511 } 512 513 private String getBmpTypeDescription(final int identifier1, final int identifier2) { 514 if ((identifier1 == 'B') && (identifier2 == 'M')) { 515 return "Windows 3.1x, 95, NT,"; 516 } 517 if ((identifier1 == 'B') && (identifier2 == 'A')) { 518 return "OS/2 Bitmap Array"; 519 } 520 if ((identifier1 == 'C') && (identifier2 == 'I')) { 521 return "OS/2 Color Icon"; 522 } 523 if ((identifier1 == 'C') && (identifier2 == 'P')) { 524 return "OS/2 Color Pointer"; 525 } 526 if ((identifier1 == 'I') && (identifier2 == 'C')) { 527 return "OS/2 Icon"; 528 } 529 if ((identifier1 == 'P') && (identifier2 == 'T')) { 530 return "OS/2 Pointer"; 531 } 532 533 return "Unknown"; 534 } 535 536 @Override 537 public ImageInfo getImageInfo(final ByteSource byteSource, final BmpImagingParameters params) 538 throws ImageReadException, IOException { 539 BmpImageContents ic = null; 540 try (InputStream is = byteSource.getInputStream()) { 541 ic = readImageContents(is, FormatCompliance.getDefault()); 542 } 543 544 final BmpHeaderInfo bhi = ic.bhi; 545 final byte[] colorTable = ic.colorTable; 546 547 if (bhi == null) { 548 throw new ImageReadException("BMP: couldn't read header"); 549 } 550 551 final int height = bhi.height; 552 final int width = bhi.width; 553 554 final List<String> comments = new ArrayList<>(); 555 // TODO: comments... 556 557 final int bitsPerPixel = bhi.bitsPerPixel; 558 final ImageFormat format = ImageFormats.BMP; 559 final String name = "BMP Windows Bitmap"; 560 final String mimeType = "image/x-ms-bmp"; 561 // we ought to count images, but don't yet. 562 final int numberOfImages = -1; 563 // not accurate ... only reflects first 564 final boolean progressive = false; 565 // boolean progressive = (fPNGChunkIHDR.InterlaceMethod != 0); 566 // 567 // pixels per meter 568 final int physicalWidthDpi = (int) Math.round(bhi.hResolution * .0254); 569 final float physicalWidthInch = (float) ((double) width / (double) physicalWidthDpi); 570 // int physicalHeightDpi = 72; 571 final int physicalHeightDpi = (int) Math.round(bhi.vResolution * .0254); 572 final float physicalHeightInch = (float) ((double) height / (double) physicalHeightDpi); 573 574 final String formatDetails = "Bmp (" + (char) bhi.identifier1 575 + (char) bhi.identifier2 + ": " 576 + getBmpTypeDescription(bhi.identifier1, bhi.identifier2) + ")"; 577 578 final boolean transparent = false; 579 580 final boolean usesPalette = colorTable != null; 581 final ImageInfo.ColorType colorType = ImageInfo.ColorType.RGB; 582 final ImageInfo.CompressionAlgorithm compressionAlgorithm = ImageInfo.CompressionAlgorithm.RLE; 583 584 return new ImageInfo(formatDetails, bitsPerPixel, comments, 585 format, name, height, mimeType, numberOfImages, 586 physicalHeightDpi, physicalHeightInch, physicalWidthDpi, 587 physicalWidthInch, width, progressive, transparent, 588 usesPalette, colorType, compressionAlgorithm); 589 } 590 591 @Override 592 public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) 593 throws ImageReadException, IOException { 594 pw.println("bmp.dumpImageFile"); 595 596 final ImageInfo imageData = getImageInfo(byteSource, null); 597 598 imageData.toString(pw, ""); 599 600 pw.println(""); 601 602 return true; 603 } 604 605 @Override 606 public FormatCompliance getFormatCompliance(final ByteSource byteSource) 607 throws ImageReadException, IOException { 608 final FormatCompliance result = new FormatCompliance( 609 byteSource.getDescription()); 610 611 try (InputStream is = byteSource.getInputStream()) { 612 readImageContents(is, result); 613 } 614 615 return result; 616 } 617 618 @Override 619 public BufferedImage getBufferedImage(final ByteSource byteSource, final BmpImagingParameters params) 620 throws ImageReadException, IOException { 621 try (InputStream is = byteSource.getInputStream()) { 622 return getBufferedImage(is, params); 623 } 624 } 625 626 public BufferedImage getBufferedImage(final InputStream inputStream, final BmpImagingParameters params) 627 throws ImageReadException, IOException { 628 final BmpImageContents ic = readImageContents(inputStream, FormatCompliance.getDefault()); 629 630 final BmpHeaderInfo bhi = ic.bhi; 631 // byte colorTable[] = ic.colorTable; 632 // byte imageData[] = ic.imageData; 633 634 final int width = bhi.width; 635 final int height = bhi.height; 636 637 if (LOGGER.isLoggable(Level.FINE)) { 638 LOGGER.fine("width: " + width); 639 LOGGER.fine("height: " + height); 640 LOGGER.fine("width*height: " + width * height); 641 LOGGER.fine("width*height*4: " + width * height * 4); 642 } 643 644 final PixelParser pixelParser = ic.pixelParser; 645 final ImageBuilder imageBuilder = new ImageBuilder(width, height, true); 646 pixelParser.processImage(imageBuilder); 647 648 return imageBuilder.getBufferedImage(); 649 650 } 651 652 @Override 653 public void writeImage(final BufferedImage src, final OutputStream os, BmpImagingParameters params) throws ImageWriteException, IOException { 654 if (params == null) { 655 params = new BmpImagingParameters(); 656 } 657 PixelDensity pixelDensity = params.getPixelDensity(); 658 659 final SimplePalette palette = new PaletteFactory().makeExactRgbPaletteSimple( 660 src, 256); 661 662 BmpWriter writer; 663 if (palette == null) { 664 writer = new BmpWriterRgb(); 665 } else { 666 writer = new BmpWriterPalette(palette); 667 } 668 669 final byte[] imageData = writer.getImageData(src); 670 final BinaryOutputStream bos = new BinaryOutputStream(os, ByteOrder.LITTLE_ENDIAN); 671 672 // write BitmapFileHeader 673 os.write(0x42); // B, Windows 3.1x, 95, NT, Bitmap 674 os.write(0x4d); // M 675 676 final int filesize = BITMAP_FILE_HEADER_SIZE + BITMAP_INFO_HEADER_SIZE + // header size 677 4 * writer.getPaletteSize() + // palette size in bytes 678 imageData.length; 679 bos.write4Bytes(filesize); 680 681 bos.write4Bytes(0); // reserved 682 bos.write4Bytes(BITMAP_FILE_HEADER_SIZE + BITMAP_INFO_HEADER_SIZE 683 + 4 * writer.getPaletteSize()); // Bitmap Data Offset 684 685 final int width = src.getWidth(); 686 final int height = src.getHeight(); 687 688 // write BitmapInfoHeader 689 bos.write4Bytes(BITMAP_INFO_HEADER_SIZE); // Bitmap Info Header Size 690 bos.write4Bytes(width); // width 691 bos.write4Bytes(height); // height 692 bos.write2Bytes(1); // Number of Planes 693 bos.write2Bytes(writer.getBitsPerPixel()); // Bits Per Pixel 694 695 bos.write4Bytes(BI_RGB); // Compression 696 bos.write4Bytes(imageData.length); // Bitmap Data Size 697 bos.write4Bytes(pixelDensity != null ? (int) Math.round(pixelDensity.horizontalDensityMetres()) : 0); // HResolution 698 bos.write4Bytes(pixelDensity != null ? (int) Math.round(pixelDensity.verticalDensityMetres()) : 0); // VResolution 699 if (palette == null) { 700 bos.write4Bytes(0); // Colors 701 } else { 702 bos.write4Bytes(palette.length()); // Colors 703 } 704 bos.write4Bytes(0); // Important Colors 705 // bos.write_4_bytes(0); // Compression 706 707 // write Palette 708 writer.writePalette(bos); 709 // write Image Data 710 bos.write(imageData); 711 } 712}