Source code: non_com/media/jai/codec/BMPImageEncoder.java
1 /*
2 * Copyright (c) 2001 Sun Microsystems, Inc. All Rights Reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are met:
6 *
7 * -Redistributions of source code must retain the above copyright notice, this
8 * list of conditions and the following disclaimer.
9 *
10 * -Redistribution in binary form must reproduct the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 *
14 * Neither the name of Sun Microsystems, Inc. or the names of contributors may
15 * be used to endorse or promote products derived from this software without
16 * specific prior written permission.
17 *
18 * This software is provided "AS IS," without a warranty of any kind. ALL
19 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
20 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
21 * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
22 * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
23 * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
24 * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
25 * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
26 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
27 * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGES.
29 *
30 * You acknowledge that Software is not designed,licensed or intended for use in
31 * the design, construction, operation or maintenance of any nuclear facility.
32 */
33 package non_com.media.jai.codec;
34
35 import non_com.media.jai.codec.BMPEncodeParam;
36 import non_com.media.jai.codec.ImageEncodeParam;
37 import non_com.media.jai.codec.ImageEncoderImpl;
38 import non_com.media.jai.codec.SeekableOutputStream;
39 import java.awt.Rectangle;
40 import java.awt.image.ColorModel;
41 import java.awt.image.DataBuffer;
42 import java.awt.image.DataBufferByte;
43 import java.awt.image.IndexColorModel;
44 import java.awt.image.MultiPixelPackedSampleModel;
45 import java.awt.image.PixelInterleavedSampleModel;
46 import java.awt.image.Raster;
47 import java.awt.image.RenderedImage;
48 import java.awt.image.SampleModel;
49 import java.io.IOException;
50 import java.io.OutputStream;
51
52 /**
53 * An ImageEncoder for the various versions of the BMP image file format.
54 * Unless specified otherwise by the BMPDecodeParam object passed to the
55 * constructor, Version 3 will be the default version used. <p>
56 *
57 * If the image to be encoded has an IndexColorModel and can be encoded using
58 * upto 8 bits per pixel, the image will be written out as a Palette color
59 * image with an appropriate number of bits per pixel. For example an image
60 * having a 256 color IndexColorModel will be written out as a Palette image
61 * with 8 bits per pixel while one with a 16 color palette will be written out
62 * as a Palette image with 4 bits per pixel. For all other images, the 24 bit
63 * image format will be used.
64 */
65 public class BMPImageEncoder extends ImageEncoderImpl {
66
67 private OutputStream output;
68 private int version;
69 private boolean isCompressed, isTopDown;
70 private int w, h;
71 private int compImageSize = 0;
72
73
74 /**
75 * An ImageEncoder for the BMP file format.
76 *
77 * @param output The OutputStream to write to.
78 * @param param The BMPEncodeParam object.
79 */
80 public BMPImageEncoder(OutputStream output, ImageEncodeParam param) {
81
82 super(output, param);
83
84 this.output = output;
85
86 BMPEncodeParam bmpParam;
87 if (param == null) {
88 // Use default valued BMPEncodeParam
89 bmpParam = new BMPEncodeParam();
90 }
91 else {
92 bmpParam = (BMPEncodeParam) param;
93 }
94
95 this.version = bmpParam.getVersion();
96 this.isCompressed = bmpParam.isCompressed();
97 if (isCompressed && !(output instanceof SeekableOutputStream)) {
98 throw new
99 IllegalArgumentException(JaiI18N.getString("BMPImageEncoder6"));
100 }
101
102 this.isTopDown = bmpParam.isTopDown();
103 }
104
105
106 /**
107 * Encodes a RenderedImage and writes the output to the OutputStream
108 * associated with this ImageEncoder.
109 *
110 * @param im Description of Parameter
111 * @exception IOException Description of Exception
112 */
113 public void encode(RenderedImage im) throws IOException {
114
115 // Get image dimensions
116 int minX = im.getMinX();
117 int minY = im.getMinY();
118 w = im.getWidth();
119 h = im.getHeight();
120
121 // Default is using 24 bits per pixel.
122 int bitsPerPixel = 24;
123 boolean isPalette = false;
124 int paletteEntries = 0;
125 IndexColorModel icm = null;
126
127 SampleModel sm = im.getSampleModel();
128 int numBands = sm.getNumBands();
129
130 ColorModel cm = im.getColorModel();
131
132 if (numBands != 1 && numBands != 3) {
133 throw new
134 IllegalArgumentException(JaiI18N.getString("BMPImageEncoder1"));
135 }
136
137 int sampleSize[] = sm.getSampleSize();
138 if (sampleSize[0] > 8) {
139 throw new RuntimeException(JaiI18N.getString("BMPImageEncoder2"));
140 }
141
142 for (int i = 1; i < sampleSize.length; i++) {
143 if (sampleSize[i] != sampleSize[0]) {
144 throw
145 new RuntimeException(JaiI18N.getString("BMPImageEncoder3"));
146 }
147 }
148
149 // Float and Double data cannot be written in a BMP format.
150 int dataType = sm.getTransferType();
151 if ((dataType == DataBuffer.TYPE_USHORT) ||
152 (dataType == DataBuffer.TYPE_SHORT) ||
153 (dataType == DataBuffer.TYPE_INT) ||
154 (dataType == DataBuffer.TYPE_FLOAT) ||
155 (dataType == DataBuffer.TYPE_DOUBLE)) {
156 throw new RuntimeException(JaiI18N.getString("BMPImageEncoder0"));
157 }
158
159 // Number of bytes that a scanline for the image written out will have.
160 int destScanlineBytes = w * numBands;
161 int compression = 0;
162
163 byte r[] = null;
164
165 byte g[] = null;
166
167 byte b[] = null;
168
169 byte a[] = null;
170
171 if (cm instanceof IndexColorModel) {
172
173 isPalette = true;
174 icm = (IndexColorModel) cm;
175 paletteEntries = icm.getMapSize();
176
177 if (paletteEntries <= 2) {
178
179 bitsPerPixel = 1;
180 destScanlineBytes = (int) Math.ceil((double) w / 8.0);
181
182 }
183 else if (paletteEntries <= 16) {
184
185 bitsPerPixel = 4;
186 destScanlineBytes = (int) Math.ceil((double) w / 2.0);
187
188 }
189 else if (paletteEntries <= 256) {
190
191 bitsPerPixel = 8;
192
193 }
194 else {
195
196 // Cannot be written as a Palette image. So write out as
197 // 24 bit image.
198 bitsPerPixel = 24;
199 isPalette = false;
200 paletteEntries = 0;
201 destScanlineBytes = w * 3;
202 }
203
204 if (isPalette == true) {
205
206 r = new byte[paletteEntries];
207 g = new byte[paletteEntries];
208 b = new byte[paletteEntries];
209 a = new byte[paletteEntries];
210
211 icm.getAlphas(a);
212 icm.getReds(r);
213 icm.getGreens(g);
214 icm.getBlues(b);
215 }
216
217 }
218 else {
219
220 // Grey scale images
221 if (numBands == 1) {
222
223 isPalette = true;
224 paletteEntries = 256;
225 // int sampleSize[] = sm.getSampleSize();
226 bitsPerPixel = sampleSize[0];
227
228 destScanlineBytes = (int) Math.ceil((double) (w * bitsPerPixel) /
229 8.0);
230
231 r = new byte[256];
232 g = new byte[256];
233 b = new byte[256];
234 a = new byte[256];
235
236 for (int i = 0; i < 256; i++) {
237 r[i] = (byte) i;
238 g[i] = (byte) i;
239 b[i] = (byte) i;
240 a[i] = (byte) i;
241 }
242 }
243 }
244
245 // actual writing of image data
246 int fileSize = 0;
247 int offset = 0;
248 int headerSize = 0;
249 int imageSize = 0;
250 int xPelsPerMeter = 0;
251 int yPelsPerMeter = 0;
252 int colorsUsed = 0;
253 int colorsImportant = paletteEntries;
254 int padding = 0;
255
256 // Calculate padding for each scanline
257 int remainder = destScanlineBytes % 4;
258 if (remainder != 0) {
259 padding = 4 - remainder;
260 }
261
262 switch (version) {
263 case BMPEncodeParam.VERSION_2:
264 offset = 26 + paletteEntries * 3;
265 headerSize = 12;
266 imageSize = (destScanlineBytes + padding) * h;
267 fileSize = imageSize + offset;
268 throw new
269 RuntimeException(JaiI18N.getString("BMPImageEncoder5"));
270 //break;
271
272 case BMPEncodeParam.VERSION_3:
273 // FileHeader is 14 bytes, BitmapHeader is 40 bytes,
274 // add palette size and that is where the data will begin
275 if (isCompressed && bitsPerPixel == 8) {
276 compression = 1;
277 }
278 else if (isCompressed && bitsPerPixel == 4) {
279 compression = 2;
280 }
281 offset = 54 + paletteEntries * 4;
282
283 imageSize = (destScanlineBytes + padding) * h;
284 fileSize = imageSize + offset;
285 headerSize = 40;
286 break;
287 case BMPEncodeParam.VERSION_4:
288 headerSize = 108;
289 throw new
290 RuntimeException(JaiI18N.getString("BMPImageEncoder5"));
291 // break;
292 }
293
294 writeFileHeader(fileSize, offset);
295
296 writeInfoHeader(headerSize, bitsPerPixel);
297
298 // compression
299 writeDWord(compression);
300
301 // imageSize
302 writeDWord(imageSize);
303
304 // xPelsPerMeter
305 writeDWord(xPelsPerMeter);
306
307 // yPelsPerMeter
308 writeDWord(yPelsPerMeter);
309
310 // Colors Used
311 writeDWord(colorsUsed);
312
313 // Colors Important
314 writeDWord(colorsImportant);
315
316 // palette
317 if (isPalette == true) {
318
319 // write palette
320 switch (version) {
321
322 // has 3 field entries
323 case BMPEncodeParam.VERSION_2:
324
325 for (int i = 0; i < paletteEntries; i++) {
326 output.write(b[i]);
327 output.write(g[i]);
328 output.write(r[i]);
329 }
330 break;
331 // has 4 field entries
332 default:
333
334 for (int i = 0; i < paletteEntries; i++) {
335 output.write(b[i]);
336 output.write(g[i]);
337 output.write(r[i]);
338 output.write(a[i]);
339 }
340 break;
341 }
342
343 }
344 // else no palette
345
346 // Writing of actual image data
347
348 int scanlineBytes = w * numBands;
349
350 // Buffer for up to 8 rows of pixels
351 int[] pixels = new int[8 * scanlineBytes];
352
353 // Also create a buffer to hold one line of the data
354 // to be written to the file, so we can use array writes.
355 byte[] bpixels = new byte[destScanlineBytes];
356
357 int l;
358
359 if (!isTopDown) {
360 // Process 8 rows at a time so all but the first will have a
361 // multiple of 8 rows.
362 int lastRow = minY + h;
363
364 for (int row = (lastRow - 1); row >= minY; row -= 8) {
365 // Number of rows being read
366 int rows = Math.min(8, row - minY + 1);
367
368 // Get the pixels
369 Raster src = im.getData(new Rectangle(minX, row - rows + 1,
370 w, rows));
371
372 src.getPixels(minX, row - rows + 1, w, rows, pixels);
373
374 l = 0;
375
376 // Last possible position in the pixels array
377 int max = scanlineBytes * rows - 1;
378
379 for (int i = 0; i < rows; i++) {
380
381 // Beginning of each scanline in the pixels array
382 l = max - (i + 1) * scanlineBytes + 1;
383
384 writePixels(l, scanlineBytes, bitsPerPixel, pixels,
385 bpixels, padding, numBands, icm);
386 }
387
388 }
389
390 }
391 else {
392 // Process 8 rows at a time so all but the last will have a
393 // multiple of 8 rows.
394 int lastRow = minY + h;
395
396 for (int row = minY; row < lastRow; row += 8) {
397 int rows = Math.min(8, lastRow - row);
398
399 // Get the pixels
400 Raster src = im.getData(new Rectangle(minX, row,
401 w, rows));
402 src.getPixels(minX, row, w, rows, pixels);
403
404 l = 0;
405 for (int i = 0; i < rows; i++) {
406
407 writePixels(l, scanlineBytes, bitsPerPixel, pixels,
408 bpixels, padding, numBands, icm);
409 }
410
411 }
412
413 }
414
415 if (isCompressed && (bitsPerPixel == 4 || bitsPerPixel == 8)) {
416 // Write the RLE EOF marker and
417 output.write(0);
418 output.write(1);
419 incCompImageSize(2);
420 // update the file/image Size
421 imageSize = compImageSize;
422 fileSize = compImageSize + offset;
423 writeSize(fileSize, 2);
424 writeSize(imageSize, 34);
425 }
426
427 }
428
429
430 // Methods for little-endian writing
431 /**
432 * Description of the Method
433 *
434 * @param word Description of Parameter
435 * @exception IOException Description of Exception
436 */
437 public void writeWord(int word) throws IOException {
438 output.write(word & 0xff);
439 output.write((word & 0xff00) >> 8);
440 }
441
442
443 /**
444 * Description of the Method
445 *
446 * @param dword Description of Parameter
447 * @exception IOException Description of Exception
448 */
449 public void writeDWord(int dword) throws IOException {
450 output.write(dword & 0xff);
451 output.write((dword & 0xff00) >> 8);
452 output.write((dword & 0xff0000) >> 16);
453 output.write((dword & 0xff000000) >> 24);
454 }
455
456
457 private boolean isEven(int number) {
458 return (number % 2 == 0 ? true : false);
459 }
460
461
462 private void writePixels(int l, int scanlineBytes, int bitsPerPixel,
463 int pixels[], byte bpixels[],
464 int padding, int numBands,
465 IndexColorModel icm) throws IOException {
466
467 int pixel = 0;
468 int k = 0;
469 switch (bitsPerPixel) {
470
471 case 1:
472
473 for (int j = 0; j < scanlineBytes / 8; j++) {
474 bpixels[k++] = (byte) ((pixels[l++] << 7) |
475 (pixels[l++] << 6) |
476 (pixels[l++] << 5) |
477 (pixels[l++] << 4) |
478 (pixels[l++] << 3) |
479 (pixels[l++] << 2) |
480 (pixels[l++] << 1) |
481 pixels[l++]);
482 }
483
484 // Partially filled last byte, if any
485 if (scanlineBytes % 8 > 0) {
486 pixel = 0;
487 for (int j = 0; j < scanlineBytes % 8; j++) {
488 pixel |= (pixels[l++] << (7 - j));
489 }
490 bpixels[k++] = (byte) pixel;
491 }
492 output.write(bpixels, 0, (scanlineBytes + 7) / 8);
493
494 break;
495 case 4:
496 if (isCompressed) {
497 byte[] bipixels = new byte[scanlineBytes];
498 for (int h = 0; h < scanlineBytes; h++) {
499 bipixels[h] = (byte) pixels[l++];
500 }
501 encodeRLE4(bipixels, scanlineBytes);
502 }
503 else {
504 for (int j = 0; j < scanlineBytes / 2; j++) {
505 pixel = (pixels[l++] << 4) | pixels[l++];
506 bpixels[k++] = (byte) pixel;
507 }
508 // Put the last pixel of odd-length lines in the 4 MSBs
509 if ((scanlineBytes % 2) == 1) {
510 pixel = pixels[l] << 4;
511 bpixels[k++] = (byte) pixel;
512 }
513 output.write(bpixels, 0, (scanlineBytes + 1) / 2);
514 }
515 break;
516 case 8:
517 if (isCompressed) {
518 for (int h = 0; h < scanlineBytes; h++) {
519 bpixels[h] = (byte) pixels[l++];
520 }
521 encodeRLE8(bpixels, scanlineBytes);
522 }
523 else {
524 for (int j = 0; j < scanlineBytes; j++) {
525 bpixels[j] = (byte) pixels[l++];
526 }
527 output.write(bpixels, 0, scanlineBytes);
528 }
529 break;
530 case 24:
531 if (numBands == 3) {
532 for (int j = 0; j < scanlineBytes; j += 3) {
533 // Since BMP needs BGR format
534 bpixels[k++] = (byte) (pixels[l + 2]);
535 bpixels[k++] = (byte) (pixels[l + 1]);
536 bpixels[k++] = (byte) (pixels[l]);
537 l += 3;
538 }
539 output.write(bpixels, 0, scanlineBytes);
540 }
541 else {
542 // Case where IndexColorModel had > 256 colors.
543 int entries = icm.getMapSize();
544
545 byte r[] = new byte[entries];
546 byte g[] = new byte[entries];
547 byte b[] = new byte[entries];
548
549 icm.getReds(r);
550 icm.getGreens(g);
551 icm.getBlues(b);
552 int index;
553
554 for (int j = 0; j < scanlineBytes; j++) {
555 index = pixels[l];
556 bpixels[k++] = b[index];
557 bpixels[k++] = g[index];
558 bpixels[k++] = b[index];
559 l++;
560 }
561 output.write(bpixels, 0, scanlineBytes * 3);
562 }
563 break;
564 }
565
566 // Write out the padding
567 if (!(isCompressed && (bitsPerPixel == 8 || bitsPerPixel == 4))) {
568 for (k = 0; k < padding; k++) {
569 output.write(0);
570 }
571 }
572 }
573
574
575 private void encodeRLE8(byte[] bpixels, int scanlineBytes)
576 throws IOException {
577
578 int runCount = 1;
579
580 int absVal = -1;
581
582 int j = -1;
583 byte runVal = 0;
584 byte nextVal = 0;
585
586 runVal = bpixels[++j];
587 byte[] absBuf = new byte[256];
588
589 while (j < scanlineBytes - 1) {
590 nextVal = bpixels[++j];
591 if (nextVal == runVal) {
592 if (absVal >= 3) {
593 /// Check if there was an existing Absolute Run
594 output.write(0);
595 output.write(absVal);
596 incCompImageSize(2);
597 for (int a = 0; a < absVal; a++) {
598 output.write(absBuf[a]);
599 incCompImageSize(1);
600 }
601 if (!isEven(absVal)) {
602 //Padding
603 output.write(0);
604 incCompImageSize(1);
605 }
606 }
607 else if (absVal > -1) {
608 /// Absolute Encoding for less than 3
609 /// treated as regular encoding
610 /// Do not include the last element since it will
611 /// be inclued in the next encoding/run
612 for (int b = 0; b < absVal; b++) {
613 output.write(1);
614 output.write(absBuf[b]);
615 incCompImageSize(2);
616 }
617 }
618 absVal = -1;
619 runCount++;
620 if (runCount == 256) {
621 /// Only 255 values permitted
622 output.write(runCount - 1);
623 output.write(runVal);
624 incCompImageSize(2);
625 runCount = 1;
626 }
627 }
628 else {
629 if (runCount > 1) {
630 /// If there was an existing run
631 output.write(runCount);
632 output.write(runVal);
633 incCompImageSize(2);
634 }
635 else if (absVal < 0) {
636 // First time..
637 absBuf[++absVal] = runVal;
638 absBuf[++absVal] = nextVal;
639 }
640 else if (absVal < 254) {
641 // 0-254 only
642 absBuf[++absVal] = nextVal;
643 }
644 else {
645 output.write(0);
646 output.write(absVal + 1);
647 incCompImageSize(2);
648 for (int a = 0; a <= absVal; a++) {
649 output.write(absBuf[a]);
650 incCompImageSize(1);
651 }
652 // padding since 255 elts is not even
653 output.write(0);
654 incCompImageSize(1);
655 absVal = -1;
656 }
657 runVal = nextVal;
658 runCount = 1;
659 }
660
661 if (j == scanlineBytes - 1) {
662 // EOF scanline
663 // Write the run
664 if (absVal == -1) {
665 output.write(runCount);
666 output.write(runVal);
667 incCompImageSize(2);
668 runCount = 1;
669 }
670 else {
671 // write the Absolute Run
672 if (absVal >= 2) {
673 output.write(0);
674 output.write(absVal + 1);
675 incCompImageSize(2);
676 for (int a = 0; a <= absVal; a++) {
677 output.write(absBuf[a]);
678 incCompImageSize(1);
679 }
680 if (!isEven(absVal + 1)) {
681 //Padding
682 output.write(0);
683 incCompImageSize(1);
684 }
685
686 }
687 else if (absVal > -1) {
688 for (int b = 0; b <= absVal; b++) {
689 output.write(1);
690 output.write(absBuf[b]);
691 incCompImageSize(2);
692 }
693 }
694 }
695 /// EOF scanline
696
697 output.write(0);
698 output.write(0);
699 incCompImageSize(2);
700 }
701 }
702 }
703
704
705 private void encodeRLE4(byte[] bipixels, int scanlineBytes)
706 throws IOException {
707
708 int runCount = 2;
709
710 int absVal = -1;
711
712 int j = -1;
713
714 int pixel = 0;
715
716 int q = 0;
717 byte runVal1 = 0;
718 byte runVal2 = 0;
719 byte nextVal1 = 0;
720 byte nextVal2 = 0;
721 byte[] absBuf = new byte[256];
722
723 runVal1 = bipixels[++j];
724 runVal2 = bipixels[++j];
725
726 while (j < scanlineBytes - 2) {
727 nextVal1 = bipixels[++j];
728 nextVal2 = bipixels[++j];
729
730 if (nextVal1 == runVal1) {
731
732 //Check if there was an existing Absolute Run
733 if (absVal >= 4) {
734 output.write(0);
735 output.write(absVal - 1);
736 incCompImageSize(2);
737 // we need to exclude last 2 elts, similarity of
738 // which caused to enter this part of the code
739 for (int a = 0; a < absVal - 2; a += 2) {
740 pixel = (absBuf[a] << 4) | absBuf[a + 1];
741 output.write((byte) pixel);
742 incCompImageSize(1);
743 }
744 // if # of elts is odd - read the last element
745 if (!(isEven(absVal - 1))) {
746 q = absBuf[absVal - 2] << 4 | 0;
747 output.write(q);
748 incCompImageSize(1);
749 }
750 // Padding to word align absolute encoding
751 if (!isEven((int) Math.ceil((absVal - 1) / 2))) {
752 output.write(0);
753 incCompImageSize(1);
754 }
755 }
756 else if (absVal > -1) {
757 output.write(2);
758 pixel = (absBuf[0] << 4) | absBuf[1];
759 output.write(pixel);
760 incCompImageSize(2);
761 }
762 absVal = -1;
763
764 if (nextVal2 == runVal2) {
765 // Even runlength
766 runCount += 2;
767 if (runCount == 256) {
768 output.write(runCount - 1);
769 pixel = (runVal1 << 4) | runVal2;
770 output.write(pixel);
771 incCompImageSize(2);
772 runCount = 2;
773 if (j < scanlineBytes - 1) {
774 runVal1 = runVal2;
775 runVal2 = bipixels[++j];
776 }
777 else {
778 output.write(01);
779 int r = runVal2 << 4 | 0;
780 output.write(r);
781 incCompImageSize(2);
782 runCount = -1;
783 /// Only EOF required now
784 }
785 }
786 }
787 else {
788 // odd runlength and the run ends here
789 // runCount wont be > 254 since 256/255 case will
790 // be taken care of in above code.
791 runCount++;
792 pixel = (runVal1 << 4) | runVal2;
793 output.write(runCount);
794 output.write(pixel);
795 incCompImageSize(2);
796 runCount = 2;
797 runVal1 = nextVal2;
798 // If end of scanline
799 if (j < scanlineBytes - 1) {
800 runVal2 = bipixels[++j];
801 }
802 else {
803 output.write(01);
804 int r = nextVal2 << 4 | 0;
805 output.write(r);
806 incCompImageSize(2);
807 runCount = -1;
808 /// Only EOF required now
809 }
810
811 }
812 }
813 else {
814 // Check for existing run
815 if (runCount > 2) {
816 pixel = (runVal1 << 4) | runVal2;
817 output.write(runCount);
818 output.write(pixel);
819 incCompImageSize(2);
820 }
821 else if (absVal < 0) {
822 // first time
823 absBuf[++absVal] = runVal1;
824 absBuf[++absVal] = runVal2;
825 absBuf[++absVal] = nextVal1;
826 absBuf[++absVal] = nextVal2;
827 }
828 else if (absVal < 253) {
829 // only 255 elements
830 absBuf[++absVal] = nextVal1;
831 absBuf[++absVal] = nextVal2;
832 }
833 else {
834 output.write(0);
835 output.write(absVal + 1);
836 incCompImageSize(2);
837 for (int a = 0; a < absVal; a += 2) {
838 pixel = (absBuf[a] << 4) | absBuf[a + 1];
839 output.write((byte) pixel);
840 incCompImageSize(1);
841 }
842 // Padding for word align
843 // since it will fit into 127 bytes
844 output.write(0);
845 incCompImageSize(1);
846 absVal = -1;
847 }
848
849 runVal1 = nextVal1;
850 runVal2 = nextVal2;
851 runCount = 2;
852 }
853 // Handle the End of scanline for the last 2 4bits
854 if (j >= scanlineBytes - 2) {
855 if (absVal == -1 && runCount >= 2) {
856 if (j == scanlineBytes - 2) {
857 if (bipixels[++j] == runVal1) {
858 runCount++;
859 pixel = (runVal1 << 4) | runVal2;
860 output.write(runCount);
861 output.write(pixel);
862 incCompImageSize(2);
863 }
864 else {
865 pixel = (runVal1 << 4) | runVal2;
866 output.write(runCount);
867 output.write(pixel);
868 output.write(01);
869 pixel = bipixels[j] << 4 | 0;
870 output.write(pixel);
871 int n = bipixels[j] << 4 | 0;
872 incCompImageSize(4);
873 }
874 }
875 else {
876 output.write(runCount);
877 pixel = (runVal1 << 4) | runVal2;
878 output.write(pixel);
879 incCompImageSize(2);
880 }
881 }
882 else if (absVal > -1) {
883 if (j == scanlineBytes - 2) {
884 absBuf[++absVal] = bipixels[++j];
885 }
886 if (absVal >= 2) {
887 output.write(0);
888 output.write(absVal + 1);
889 incCompImageSize(2);
890 for (int a = 0; a < absVal; a += 2) {
891 pixel = (absBuf[a] << 4) | absBuf[a + 1];
892 output.write((byte) pixel);
893 incCompImageSize(1);
894 }
895 if (!(isEven(absVal + 1))) {
896 q = absBuf[absVal] << 4 | 0;
897 output.write(q);
898 incCompImageSize(1);
899 }
900
901 // Padding
902 if (!isEven((int) Math.ceil((absVal + 1) / 2))) {
903 output.write(0);
904 incCompImageSize(1);
905 }
906
907 }
908 else {
909 switch (absVal) {
910 case 0:
911 output.write(1);
912 int n = absBuf[0] << 4 | 0;
913 output.write(n);
914 incCompImageSize(2);
915 break;
916 case 1:
917 output.write(2);
918 pixel = (absBuf[0] << 4) | absBuf[1];
919 output.write(pixel);
920 incCompImageSize(2);
921 break;
922 }
923 }
924
925 }
926 output.write(0);
927 output.write(0);
928 incCompImageSize(2);
929 }
930 }
931 }
932
933
934 private synchronized void incCompImageSize(int value) {
935 compImageSize = compImageSize + value;
936 }
937
938
939 private void writeFileHeader(int fileSize, int offset) throws IOException {
940 // magic value
941 output.write('B');
942 output.write('M');
943
944 // File size
945 writeDWord(fileSize);
946
947 // reserved1 and reserved2
948 output.write(0);
949 output.write(0);
950 output.write(0);
951 output.write(0);
952
953 // offset to image data
954 writeDWord(offset);
955 }
956
957
958 private void writeInfoHeader(int headerSize, int bitsPerPixel)
959 throws IOException {
960
961 // size of header
962 writeDWord(headerSize);
963
964 // width
965 writeDWord(w);
966
967 // height
968 writeDWord(h);
969
970 // number of planes
971 writeWord(1);
972
973 // Bits Per Pixel
974 writeWord(bitsPerPixel);
975 }
976
977
978 private void writeSize(int dword, int offset) throws IOException {
979 ((SeekableOutputStream) output).seek(offset);
980 writeDWord(dword);
981 }
982 }
983