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