Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

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