1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.imaging.formats.bmp;
18
19 import static org.apache.commons.imaging.common.BinaryFunctions.read2Bytes;
20 import static org.apache.commons.imaging.common.BinaryFunctions.read4Bytes;
21 import static org.apache.commons.imaging.common.BinaryFunctions.readByte;
22 import static org.apache.commons.imaging.common.BinaryFunctions.readBytes;
23
24 import java.awt.Dimension;
25 import java.awt.image.BufferedImage;
26 import java.io.ByteArrayOutputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.OutputStream;
30 import java.io.PrintWriter;
31 import java.nio.ByteOrder;
32 import java.util.ArrayList;
33 import java.util.List;
34 import java.util.logging.Level;
35 import java.util.logging.Logger;
36
37 import org.apache.commons.imaging.AbstractImageParser;
38 import org.apache.commons.imaging.FormatCompliance;
39 import org.apache.commons.imaging.ImageFormat;
40 import org.apache.commons.imaging.ImageFormats;
41 import org.apache.commons.imaging.ImageInfo;
42 import org.apache.commons.imaging.ImagingException;
43 import org.apache.commons.imaging.PixelDensity;
44 import org.apache.commons.imaging.bytesource.ByteSource;
45 import org.apache.commons.imaging.common.AbstractBinaryOutputStream;
46 import org.apache.commons.imaging.common.ImageBuilder;
47 import org.apache.commons.imaging.common.ImageMetadata;
48 import org.apache.commons.imaging.palette.PaletteFactory;
49 import org.apache.commons.imaging.palette.SimplePalette;
50
51 public class BmpImageParser extends AbstractImageParser<BmpImagingParameters> {
52
53 private static final Logger LOGGER = Logger.getLogger(BmpImageParser.class.getName());
54
55 private static final String DEFAULT_EXTENSION = ImageFormats.BMP.getDefaultExtension();
56 private static final String[] ACCEPTED_EXTENSIONS = ImageFormats.BMP.getExtensions();
57 private static final byte[] BMP_HEADER_SIGNATURE = { 0x42, 0x4d, };
58 private static final int BI_RGB = 0;
59 private static final int BI_RLE4 = 2;
60 private static final int BI_RLE8 = 1;
61 private static final int BI_BITFIELDS = 3;
62 private static final int BITMAP_FILE_HEADER_SIZE = 14;
63 private static final int BITMAP_INFO_HEADER_SIZE = 40;
64
65
66
67
68 public BmpImageParser() {
69 super(ByteOrder.LITTLE_ENDIAN);
70 }
71
72 @Override
73 public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) throws ImagingException, IOException {
74 pw.println("bmp.dumpImageFile");
75
76 final ImageInfo imageData = getImageInfo(byteSource, null);
77
78 imageData.toString(pw, "");
79
80 pw.println("");
81
82 return true;
83 }
84
85 @Override
86 protected String[] getAcceptedExtensions() {
87 return ACCEPTED_EXTENSIONS;
88 }
89
90 @Override
91 protected ImageFormat[] getAcceptedTypes() {
92 return new ImageFormat[] { ImageFormats.BMP };
93 }
94
95 private String getBmpTypeDescription(final int identifier1, final int identifier2) {
96 if (identifier1 == 'B' && identifier2 == 'M') {
97 return "Windows 3.1x, 95, NT,";
98 }
99 if (identifier1 == 'B' && identifier2 == 'A') {
100 return "OS/2 Bitmap Array";
101 }
102 if (identifier1 == 'C' && identifier2 == 'I') {
103 return "OS/2 Color Icon";
104 }
105 if (identifier1 == 'C' && identifier2 == 'P') {
106 return "OS/2 Color Pointer";
107 }
108 if (identifier1 == 'I' && identifier2 == 'C') {
109 return "OS/2 Icon";
110 }
111 if (identifier1 == 'P' && identifier2 == 'T') {
112 return "OS/2 Pointer";
113 }
114
115 return "Unknown";
116 }
117
118 @Override
119 public BufferedImage getBufferedImage(final ByteSource byteSource, final BmpImagingParameters params) throws ImagingException, IOException {
120 try (InputStream is = byteSource.getInputStream()) {
121 return getBufferedImage(is, params);
122 }
123 }
124
125 public BufferedImage getBufferedImage(final InputStream inputStream, final BmpImagingParameters params) throws ImagingException, IOException {
126 final BmpImageContents ic = readImageContents(inputStream, FormatCompliance.getDefault());
127
128 final BmpHeaderInfo bhi = ic.bhi;
129
130
131
132 final int width = bhi.width;
133 final int height = bhi.height;
134
135 if (LOGGER.isLoggable(Level.FINE)) {
136 LOGGER.fine("width: " + width);
137 LOGGER.fine("height: " + height);
138 LOGGER.fine("width*height: " + width * height);
139 LOGGER.fine("width*height*4: " + width * height * 4);
140 }
141
142 final AbstractPixelParser abstractPixelParser = ic.abstractPixelParser;
143 final ImageBuilder imageBuilder = new ImageBuilder(width, height, true);
144 abstractPixelParser.processImage(imageBuilder);
145
146 return imageBuilder.getBufferedImage();
147
148 }
149
150 @Override
151 public String getDefaultExtension() {
152 return DEFAULT_EXTENSION;
153 }
154
155 @Override
156 public BmpImagingParameters getDefaultParameters() {
157 return new BmpImagingParameters();
158 }
159
160 @Override
161 public FormatCompliance getFormatCompliance(final ByteSource byteSource) throws ImagingException, IOException {
162 final FormatCompliance result = new FormatCompliance(byteSource.toString());
163
164 try (InputStream is = byteSource.getInputStream()) {
165 readImageContents(is, result);
166 }
167
168 return result;
169 }
170
171 @Override
172 public byte[] getIccProfileBytes(final ByteSource byteSource, final BmpImagingParameters params) {
173 return null;
174 }
175
176 @Override
177 public ImageInfo getImageInfo(final ByteSource byteSource, final BmpImagingParameters params) throws ImagingException, IOException {
178 BmpImageContents ic = null;
179 try (InputStream is = byteSource.getInputStream()) {
180 ic = readImageContents(is, FormatCompliance.getDefault());
181 }
182
183 final BmpHeaderInfo bhi = ic.bhi;
184 final byte[] colorTable = ic.colorTable;
185
186 if (bhi == null) {
187 throw new ImagingException("BMP: couldn't read header");
188 }
189
190 final int height = bhi.height;
191 final int width = bhi.width;
192
193 final List<String> comments = new ArrayList<>();
194
195
196 final int bitsPerPixel = bhi.bitsPerPixel;
197 final ImageFormat format = ImageFormats.BMP;
198 final String name = "BMP Windows Bitmap";
199 final String mimeType = "image/x-ms-bmp";
200
201 final int numberOfImages = -1;
202
203 final boolean progressive = false;
204
205
206
207 final int physicalWidthDpi = (int) Math.round(bhi.hResolution * .0254);
208 final float physicalWidthInch = (float) ((double) width / (double) physicalWidthDpi);
209
210 final int physicalHeightDpi = (int) Math.round(bhi.vResolution * .0254);
211 final float physicalHeightInch = (float) ((double) height / (double) physicalHeightDpi);
212
213 final String formatDetails = "Bmp (" + (char) bhi.identifier1 + (char) bhi.identifier2 + ": " + getBmpTypeDescription(bhi.identifier1, bhi.identifier2)
214 + ")";
215
216 final boolean transparent = false;
217
218 final boolean usesPalette = colorTable != null;
219 final ImageInfo.ColorType colorType = ImageInfo.ColorType.RGB;
220 final ImageInfo.CompressionAlgorithm compressionAlgorithm = ImageInfo.CompressionAlgorithm.RLE;
221
222 return new ImageInfo(formatDetails, bitsPerPixel, comments, format, name, height, mimeType, numberOfImages, physicalHeightDpi, physicalHeightInch,
223 physicalWidthDpi, physicalWidthInch, width, progressive, transparent, usesPalette, colorType, compressionAlgorithm);
224 }
225
226 @Override
227 public Dimension getImageSize(final ByteSource byteSource, final BmpImagingParameters params) throws ImagingException, IOException {
228 final BmpHeaderInfo bhi = readBmpHeaderInfo(byteSource);
229
230 return new Dimension(bhi.width, bhi.height);
231
232 }
233
234 @Override
235 public ImageMetadata getMetadata(final ByteSource byteSource, final BmpImagingParameters params) {
236
237 return null;
238 }
239
240 @Override
241 public String getName() {
242 return "Bmp-Custom";
243 }
244
245 private byte[] getRleBytes(final InputStream is, final int rleSamplesPerByte) throws IOException {
246 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
247
248
249
250 boolean done = false;
251 while (!done) {
252 final int a = 0xff & readByte("RLE a", is, "BMP: Bad RLE");
253 baos.write(a);
254 final int b = 0xff & readByte("RLE b", is, "BMP: Bad RLE");
255 baos.write(b);
256
257 if (a == 0) {
258 switch (b) {
259 case 0:
260 break;
261 case 1:
262
263
264 done = true;
265 break;
266 case 2: {
267
268
269 final int c = 0xff & readByte("RLE c", is, "BMP: Bad RLE");
270 baos.write(c);
271 final int d = 0xff & readByte("RLE d", is, "BMP: Bad RLE");
272 baos.write(d);
273
274 }
275 break;
276 default: {
277 int size = b / rleSamplesPerByte;
278 if (b % rleSamplesPerByte > 0) {
279 size++;
280 }
281 if (size % 2 != 0) {
282 size++;
283 }
284
285
286
287
288
289
290
291 final byte[] bytes = readBytes("bytes", is, size, "RLE: Absolute Mode");
292 baos.write(bytes);
293 }
294 break;
295 }
296 }
297 }
298
299 return baos.toByteArray();
300 }
301
302 private BmpHeaderInfo readBmpHeaderInfo(final ByteSource byteSource) throws ImagingException, IOException {
303 try (InputStream is = byteSource.getInputStream()) {
304
305 return readBmpHeaderInfo(is, null);
306 }
307 }
308
309 private BmpHeaderInfo readBmpHeaderInfo(final InputStream is, final FormatCompliance formatCompliance) throws ImagingException, IOException {
310 final byte identifier1 = readByte("Identifier1", is, "Not a Valid BMP File");
311 final byte identifier2 = readByte("Identifier2", is, "Not a Valid BMP File");
312
313 if (formatCompliance != null) {
314 formatCompliance.compareBytes("Signature", BMP_HEADER_SIGNATURE, new byte[] { identifier1, identifier2 });
315 }
316
317 final int fileSize = read4Bytes("File Size", is, "Not a Valid BMP File", getByteOrder());
318 final int reserved = read4Bytes("Reserved", is, "Not a Valid BMP File", getByteOrder());
319 final int bitmapDataOffset = read4Bytes("Bitmap Data Offset", is, "Not a Valid BMP File", getByteOrder());
320
321 final int bitmapHeaderSize = read4Bytes("Bitmap Header Size", is, "Not a Valid BMP File", getByteOrder());
322 int width = 0;
323 int height = 0;
324 int planes = 0;
325 int bitsPerPixel = 0;
326 int compression = 0;
327 int bitmapDataSize = 0;
328 int hResolution = 0;
329 int vResolution = 0;
330 int colorsUsed = 0;
331 int colorsImportant = 0;
332 int redMask = 0;
333 int greenMask = 0;
334 int blueMask = 0;
335 int alphaMask = 0;
336 int colorSpaceType = 0;
337 final BmpHeaderInfo.ColorSpace colorSpace = new BmpHeaderInfo.ColorSpace();
338 colorSpace.red = new BmpHeaderInfo.ColorSpaceCoordinate();
339 colorSpace.green = new BmpHeaderInfo.ColorSpaceCoordinate();
340 colorSpace.blue = new BmpHeaderInfo.ColorSpaceCoordinate();
341 int gammaRed = 0;
342 int gammaGreen = 0;
343 int gammaBlue = 0;
344 int intent = 0;
345 int profileData = 0;
346 int profileSize = 0;
347 int reservedV5 = 0;
348
349 if (bitmapHeaderSize < 40) {
350 throw new ImagingException("Invalid/unsupported BMP file");
351 }
352
353 width = read4Bytes("Width", is, "Not a Valid BMP File", getByteOrder());
354 height = read4Bytes("Height", is, "Not a Valid BMP File", getByteOrder());
355 planes = read2Bytes("Planes", is, "Not a Valid BMP File", getByteOrder());
356 bitsPerPixel = read2Bytes("Bits Per Pixel", is, "Not a Valid BMP File", getByteOrder());
357 compression = read4Bytes("Compression", is, "Not a Valid BMP File", getByteOrder());
358 bitmapDataSize = read4Bytes("Bitmap Data Size", is, "Not a Valid BMP File", getByteOrder());
359 hResolution = read4Bytes("HResolution", is, "Not a Valid BMP File", getByteOrder());
360 vResolution = read4Bytes("VResolution", is, "Not a Valid BMP File", getByteOrder());
361 colorsUsed = read4Bytes("ColorsUsed", is, "Not a Valid BMP File", getByteOrder());
362 colorsImportant = read4Bytes("ColorsImportant", is, "Not a Valid BMP File", getByteOrder());
363 if (bitmapHeaderSize >= 52 || compression == BI_BITFIELDS) {
364
365
366 redMask = read4Bytes("RedMask", is, "Not a Valid BMP File", getByteOrder());
367 greenMask = read4Bytes("GreenMask", is, "Not a Valid BMP File", getByteOrder());
368 blueMask = read4Bytes("BlueMask", is, "Not a Valid BMP File", getByteOrder());
369 }
370 if (bitmapHeaderSize >= 56) {
371
372
373
374 alphaMask = read4Bytes("AlphaMask", is, "Not a Valid BMP File", getByteOrder());
375 }
376 if (bitmapHeaderSize >= 108) {
377
378 colorSpaceType = read4Bytes("ColorSpaceType", is, "Not a Valid BMP File", getByteOrder());
379 colorSpace.red.x = read4Bytes("ColorSpaceRedX", is, "Not a Valid BMP File", getByteOrder());
380 colorSpace.red.y = read4Bytes("ColorSpaceRedY", is, "Not a Valid BMP File", getByteOrder());
381 colorSpace.red.z = read4Bytes("ColorSpaceRedZ", is, "Not a Valid BMP File", getByteOrder());
382 colorSpace.green.x = read4Bytes("ColorSpaceGreenX", is, "Not a Valid BMP File", getByteOrder());
383 colorSpace.green.y = read4Bytes("ColorSpaceGreenY", is, "Not a Valid BMP File", getByteOrder());
384 colorSpace.green.z = read4Bytes("ColorSpaceGreenZ", is, "Not a Valid BMP File", getByteOrder());
385 colorSpace.blue.x = read4Bytes("ColorSpaceBlueX", is, "Not a Valid BMP File", getByteOrder());
386 colorSpace.blue.y = read4Bytes("ColorSpaceBlueY", is, "Not a Valid BMP File", getByteOrder());
387 colorSpace.blue.z = read4Bytes("ColorSpaceBlueZ", is, "Not a Valid BMP File", getByteOrder());
388 gammaRed = read4Bytes("GammaRed", is, "Not a Valid BMP File", getByteOrder());
389 gammaGreen = read4Bytes("GammaGreen", is, "Not a Valid BMP File", getByteOrder());
390 gammaBlue = read4Bytes("GammaBlue", is, "Not a Valid BMP File", getByteOrder());
391 }
392 if (bitmapHeaderSize >= 124) {
393
394 intent = read4Bytes("Intent", is, "Not a Valid BMP File", getByteOrder());
395 profileData = read4Bytes("ProfileData", is, "Not a Valid BMP File", getByteOrder());
396 profileSize = read4Bytes("ProfileSize", is, "Not a Valid BMP File", getByteOrder());
397 reservedV5 = read4Bytes("Reserved", is, "Not a Valid BMP File", getByteOrder());
398 }
399
400 if (LOGGER.isLoggable(Level.FINE)) {
401 debugNumber("identifier1", identifier1, 1);
402 debugNumber("identifier2", identifier2, 1);
403 debugNumber("fileSize", fileSize, 4);
404 debugNumber("reserved", reserved, 4);
405 debugNumber("bitmapDataOffset", bitmapDataOffset, 4);
406 debugNumber("bitmapHeaderSize", bitmapHeaderSize, 4);
407 debugNumber("width", width, 4);
408 debugNumber("height", height, 4);
409 debugNumber("planes", planes, 2);
410 debugNumber("bitsPerPixel", bitsPerPixel, 2);
411 debugNumber("compression", compression, 4);
412 debugNumber("bitmapDataSize", bitmapDataSize, 4);
413 debugNumber("hResolution", hResolution, 4);
414 debugNumber("vResolution", vResolution, 4);
415 debugNumber("colorsUsed", colorsUsed, 4);
416 debugNumber("colorsImportant", colorsImportant, 4);
417 if (bitmapHeaderSize >= 52 || compression == BI_BITFIELDS) {
418 debugNumber("redMask", redMask, 4);
419 debugNumber("greenMask", greenMask, 4);
420 debugNumber("blueMask", blueMask, 4);
421 }
422 if (bitmapHeaderSize >= 56) {
423 debugNumber("alphaMask", alphaMask, 4);
424 }
425 if (bitmapHeaderSize >= 108) {
426 debugNumber("colorSpaceType", colorSpaceType, 4);
427 debugNumber("colorSpace.red.x", colorSpace.red.x, 1);
428 debugNumber("colorSpace.red.y", colorSpace.red.y, 1);
429 debugNumber("colorSpace.red.z", colorSpace.red.z, 1);
430 debugNumber("colorSpace.green.x", colorSpace.green.x, 1);
431 debugNumber("colorSpace.green.y", colorSpace.green.y, 1);
432 debugNumber("colorSpace.green.z", colorSpace.green.z, 1);
433 debugNumber("colorSpace.blue.x", colorSpace.blue.x, 1);
434 debugNumber("colorSpace.blue.y", colorSpace.blue.y, 1);
435 debugNumber("colorSpace.blue.z", colorSpace.blue.z, 1);
436 debugNumber("gammaRed", gammaRed, 4);
437 debugNumber("gammaGreen", gammaGreen, 4);
438 debugNumber("gammaBlue", gammaBlue, 4);
439 }
440 if (bitmapHeaderSize >= 124) {
441 debugNumber("intent", intent, 4);
442 debugNumber("profileData", profileData, 4);
443 debugNumber("profileSize", profileSize, 4);
444 debugNumber("reservedV5", reservedV5, 4);
445 }
446 }
447
448 return new BmpHeaderInfo(identifier1, identifier2, fileSize, reserved, bitmapDataOffset, bitmapHeaderSize, width, height, planes, bitsPerPixel,
449 compression, bitmapDataSize, hResolution, vResolution, colorsUsed, colorsImportant, redMask, greenMask, blueMask, alphaMask, colorSpaceType,
450 colorSpace, gammaRed, gammaGreen, gammaBlue, intent, profileData, profileSize, reservedV5);
451 }
452
453 private BmpImageContents readImageContents(final InputStream is, final FormatCompliance formatCompliance) throws ImagingException, IOException {
454 final BmpHeaderInfo bhi = readBmpHeaderInfo(is, formatCompliance);
455
456 int colorTableSize = bhi.colorsUsed;
457 if (colorTableSize == 0) {
458 colorTableSize = 1 << bhi.bitsPerPixel;
459 }
460
461 if (LOGGER.isLoggable(Level.FINE)) {
462 debugNumber("ColorsUsed", bhi.colorsUsed, 4);
463 debugNumber("BitsPerPixel", bhi.bitsPerPixel, 4);
464 debugNumber("ColorTableSize", colorTableSize, 4);
465 debugNumber("bhi.colorsUsed", bhi.colorsUsed, 4);
466 debugNumber("Compression", bhi.compression, 4);
467 }
468
469
470
471
472 final int paletteLength;
473 int rleSamplesPerByte = 0;
474 boolean rle = false;
475
476 switch (bhi.compression) {
477 case BI_RGB:
478 if (LOGGER.isLoggable(Level.FINE)) {
479 LOGGER.fine("Compression: BI_RGB");
480 }
481 if (bhi.bitsPerPixel <= 8) {
482 paletteLength = 4 * colorTableSize;
483 } else {
484 paletteLength = 0;
485 }
486
487
488
489
490 break;
491
492 case BI_RLE4:
493 if (LOGGER.isLoggable(Level.FINE)) {
494 LOGGER.fine("Compression: BI_RLE4");
495 }
496 paletteLength = 4 * colorTableSize;
497 rleSamplesPerByte = 2;
498
499 rle = true;
500
501
502 break;
503
504 case BI_RLE8:
505 if (LOGGER.isLoggable(Level.FINE)) {
506 LOGGER.fine("Compression: BI_RLE8");
507 }
508 paletteLength = 4 * colorTableSize;
509 rleSamplesPerByte = 1;
510
511 rle = true;
512
513
514 break;
515
516 case BI_BITFIELDS:
517 if (LOGGER.isLoggable(Level.FINE)) {
518 LOGGER.fine("Compression: BI_BITFIELDS");
519 }
520 if (bhi.bitsPerPixel <= 8) {
521 paletteLength = 4 * colorTableSize;
522 } else {
523 paletteLength = 0;
524 }
525
526
527 break;
528
529 default:
530 throw new ImagingException("BMP: Unknown Compression: " + bhi.compression);
531 }
532
533 if (paletteLength < 0) {
534 throw new ImagingException("BMP: Invalid negative palette length: " + paletteLength);
535 }
536
537 byte[] colorTable = null;
538 if (paletteLength > 0) {
539 colorTable = readBytes("ColorTable", is, paletteLength, "Not a Valid BMP File");
540 }
541
542 if (LOGGER.isLoggable(Level.FINE)) {
543 debugNumber("paletteLength", paletteLength, 4);
544 LOGGER.fine("ColorTable: " + (colorTable == null ? "null" : Integer.toString(colorTable.length)));
545 }
546
547 int imageLineLength = (bhi.bitsPerPixel * bhi.width + 7) / 8;
548
549 if (LOGGER.isLoggable(Level.FINE)) {
550 final int pixelCount = bhi.width * bhi.height;
551
552
553
554
555
556 debugNumber("bhi.Width", bhi.width, 4);
557 debugNumber("bhi.Height", bhi.height, 4);
558 debugNumber("ImageLineLength", imageLineLength, 4);
559
560 debugNumber("PixelCount", pixelCount, 4);
561 }
562
563 while (imageLineLength % 4 != 0) {
564 imageLineLength++;
565 }
566
567 final int headerSize = BITMAP_FILE_HEADER_SIZE + bhi.bitmapHeaderSize + (bhi.bitmapHeaderSize == 40 && bhi.compression == BI_BITFIELDS ? 3 * 4 : 0);
568 final int expectedDataOffset = headerSize + paletteLength;
569
570 if (LOGGER.isLoggable(Level.FINE)) {
571 debugNumber("bhi.BitmapDataOffset", bhi.bitmapDataOffset, 4);
572 debugNumber("expectedDataOffset", expectedDataOffset, 4);
573 }
574 final int extraBytes = bhi.bitmapDataOffset - expectedDataOffset;
575 if (extraBytes < 0 || extraBytes > bhi.fileSize) {
576 throw new ImagingException("BMP has invalid image data offset: " + bhi.bitmapDataOffset + " (expected: " + expectedDataOffset + ", paletteLength: "
577 + paletteLength + ", headerSize: " + headerSize + ")");
578 }
579 if (extraBytes > 0) {
580 readBytes("BitmapDataOffset", is, extraBytes, "Not a Valid BMP File");
581 }
582
583 final int imageDataSize = bhi.height * imageLineLength;
584
585 if (LOGGER.isLoggable(Level.FINE)) {
586 debugNumber("imageDataSize", imageDataSize, 4);
587 }
588
589 final byte[] imageData;
590 if (rle) {
591 imageData = getRleBytes(is, rleSamplesPerByte);
592 } else {
593 imageData = readBytes("ImageData", is, imageDataSize, "Not a Valid BMP File");
594 }
595
596 if (LOGGER.isLoggable(Level.FINE)) {
597 debugNumber("ImageData.length", imageData.length, 4);
598 }
599
600 final AbstractPixelParser abstractPixelParser;
601 switch (bhi.compression) {
602 case BI_RLE4:
603 case BI_RLE8:
604 abstractPixelParser = new PixelParserRle(bhi, colorTable, imageData);
605 break;
606 case BI_RGB:
607 abstractPixelParser = new PixelParserRgb(bhi, colorTable, imageData);
608 break;
609 case BI_BITFIELDS:
610 abstractPixelParser = new PixelParserBitFields(bhi, colorTable, imageData);
611 break;
612 default:
613 throw new ImagingException("BMP: Unknown Compression: " + bhi.compression);
614 }
615
616 return new BmpImageContents(bhi, colorTable, imageData, abstractPixelParser);
617 }
618
619 @Override
620 public void writeImage(final BufferedImage src, final OutputStream os, BmpImagingParameters params) throws ImagingException, IOException {
621 if (params == null) {
622 params = new BmpImagingParameters();
623 }
624 final PixelDensity pixelDensity = params.getPixelDensity();
625
626 final SimplePalette palette = new PaletteFactory().makeExactRgbPaletteSimple(src, 256);
627
628 final BmpWriter writer;
629 if (palette == null) {
630 writer = new BmpWriterRgb();
631 } else {
632 writer = new BmpWriterPalette(palette);
633 }
634
635 final byte[] imageData = writer.getImageData(src);
636 @SuppressWarnings("resource")
637 final AbstractBinaryOutputStream bos = AbstractBinaryOutputStream.littleEndian(os);
638
639
640 os.write(0x42);
641 os.write(0x4d);
642
643 final int fileSize = BITMAP_FILE_HEADER_SIZE + BITMAP_INFO_HEADER_SIZE +
644 4 * writer.getPaletteSize() +
645 imageData.length;
646 bos.write4Bytes(fileSize);
647
648 bos.write4Bytes(0);
649 bos.write4Bytes(BITMAP_FILE_HEADER_SIZE + BITMAP_INFO_HEADER_SIZE + 4 * writer.getPaletteSize());
650
651 final int width = src.getWidth();
652 final int height = src.getHeight();
653
654
655 bos.write4Bytes(BITMAP_INFO_HEADER_SIZE);
656 bos.write4Bytes(width);
657 bos.write4Bytes(height);
658 bos.write2Bytes(1);
659 bos.write2Bytes(writer.getBitsPerPixel());
660
661 bos.write4Bytes(BI_RGB);
662 bos.write4Bytes(imageData.length);
663 bos.write4Bytes(pixelDensity != null ? (int) Math.round(pixelDensity.horizontalDensityMetres()) : 0);
664 bos.write4Bytes(pixelDensity != null ? (int) Math.round(pixelDensity.verticalDensityMetres()) : 0);
665 if (palette == null) {
666 bos.write4Bytes(0);
667 } else {
668 bos.write4Bytes(palette.length());
669 }
670 bos.write4Bytes(0);
671
672
673
674 writer.writePalette(bos);
675
676 bos.write(imageData);
677 }
678 }