001: /*
002: * $RCSfile: BMPImageEncoder.java,v $
003: *
004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Use is subject to license terms.
007: *
008: * $Revision: 1.1 $
009: * $Date: 2005/02/11 04:55:35 $
010: * $State: Exp $
011: */
012: package com.sun.media.jai.codecimpl;
013:
014: import java.io.OutputStream;
015: import java.io.IOException;
016: import java.awt.image.Raster;
017: import java.awt.image.DataBuffer;
018: import java.awt.image.DataBufferByte;
019: import java.awt.image.DataBufferShort;
020: import java.awt.image.DataBufferUShort;
021: import java.awt.image.DataBufferInt;
022: import java.awt.image.RenderedImage;
023: import java.awt.image.SampleModel;
024: import java.awt.Rectangle;
025: import java.awt.image.PixelInterleavedSampleModel;
026: import java.awt.image.SinglePixelPackedSampleModel;
027: import java.awt.image.MultiPixelPackedSampleModel;
028: import java.awt.image.ColorModel;
029: import java.awt.image.DirectColorModel;
030: import java.awt.image.IndexColorModel;
031: import com.sun.media.jai.codec.ImageEncoderImpl;
032: import com.sun.media.jai.codec.ImageEncodeParam;
033: import com.sun.media.jai.codec.BMPEncodeParam;
034: import com.sun.media.jai.codec.SeekableOutputStream;
035:
036: /**
037: * An ImageEncoder for the various versions of the BMP image file format.
038: *
039: * Unless specified otherwise by the BMPDecodeParam object passed to the
040: * constructor, Version 3 will be the default version used.
041: *
042: * <p>If the image to be encoded has an IndexColorModel and can be encoded
043: * using upto 8 bits per pixel, the image will be written out as a Palette
044: * color image with an appropriate number of bits per pixel. For example an
045: * image having a 256 color IndexColorModel will be written out as a Palette
046: * image with 8 bits per pixel while one with a 16 color palette will be
047: * written out as a Palette image with 4 bits per pixel. For all other images,
048: * the 24 bit image format will be used.
049: *
050: *
051: * @since EA2
052: */
053: public class BMPImageEncoder extends ImageEncoderImpl {
054:
055: private OutputStream output;
056: private int version;
057: private boolean isCompressed, isTopDown;
058: private int w, h;
059: private int compImageSize = 0;
060:
061: /**
062: * An ImageEncoder for the BMP file format.
063: *
064: * @param output The OutputStream to write to.
065: * @param param The BMPEncodeParam object.
066: */
067: public BMPImageEncoder(OutputStream output, ImageEncodeParam param) {
068:
069: super (output, param);
070:
071: this .output = output;
072:
073: BMPEncodeParam bmpParam;
074: if (param == null) {
075: // Use default valued BMPEncodeParam
076: bmpParam = new BMPEncodeParam();
077: } else {
078: bmpParam = (BMPEncodeParam) param;
079: }
080:
081: this .version = bmpParam.getVersion();
082: this .isCompressed = bmpParam.isCompressed();
083: if (isCompressed && !(output instanceof SeekableOutputStream)) {
084: throw new IllegalArgumentException(JaiI18N
085: .getString("BMPImageEncoder6"));
086: }
087:
088: this .isTopDown = bmpParam.isTopDown();
089: }
090:
091: /**
092: * Encodes a RenderedImage and writes the output to the
093: * OutputStream associated with this ImageEncoder.
094: */
095: public void encode(RenderedImage im) throws IOException {
096:
097: // Get image dimensions
098: int minX = im.getMinX();
099: int minY = im.getMinY();
100: w = im.getWidth();
101: h = im.getHeight();
102:
103: // Default is using 24 bits per pixel.
104: int bitsPerPixel = 24;
105: boolean isPalette = false;
106: int paletteEntries = 0;
107: IndexColorModel icm = null;
108:
109: SampleModel sm = im.getSampleModel();
110: int numBands = sm.getNumBands();
111:
112: ColorModel cm = im.getColorModel();
113:
114: if (numBands != 1 && numBands != 3) {
115: throw new IllegalArgumentException(JaiI18N
116: .getString("BMPImageEncoder1"));
117: }
118:
119: int sampleSize[] = sm.getSampleSize();
120: if (sampleSize[0] > 8) {
121: throw new RuntimeException(JaiI18N
122: .getString("BMPImageEncoder2"));
123: }
124:
125: for (int i = 1; i < sampleSize.length; i++) {
126: if (sampleSize[i] != sampleSize[0]) {
127: throw new RuntimeException(JaiI18N
128: .getString("BMPImageEncoder3"));
129: }
130: }
131:
132: // Float and Double data cannot be written in a BMP format.
133: int dataType = sm.getTransferType();
134: if (dataType != DataBuffer.TYPE_BYTE
135: && !CodecUtils.isPackedByteImage(im)) {
136: throw new RuntimeException(JaiI18N
137: .getString("BMPImageEncoder0"));
138: }
139:
140: // Number of bytes that a scanline for the image written out will have.
141: int destScanlineBytes = w * numBands;
142: int compression = 0;
143:
144: byte r[] = null, g[] = null, b[] = null, a[] = null;
145:
146: if (cm instanceof IndexColorModel) {
147:
148: isPalette = true;
149: icm = (IndexColorModel) cm;
150: paletteEntries = icm.getMapSize();
151:
152: if (paletteEntries <= 2) {
153:
154: bitsPerPixel = 1;
155: destScanlineBytes = (int) Math.ceil((double) w / 8.0);
156:
157: } else if (paletteEntries <= 16) {
158:
159: bitsPerPixel = 4;
160: destScanlineBytes = (int) Math.ceil((double) w / 2.0);
161:
162: } else if (paletteEntries <= 256) {
163:
164: bitsPerPixel = 8;
165:
166: } else {
167:
168: // Cannot be written as a Palette image. So write out as
169: // 24 bit image.
170: bitsPerPixel = 24;
171: isPalette = false;
172: paletteEntries = 0;
173: destScanlineBytes = w * 3;
174: }
175:
176: if (isPalette == true) {
177:
178: r = new byte[paletteEntries];
179: g = new byte[paletteEntries];
180: b = new byte[paletteEntries];
181: a = new byte[paletteEntries];
182:
183: icm.getAlphas(a);
184: icm.getReds(r);
185: icm.getGreens(g);
186: icm.getBlues(b);
187: }
188:
189: } else {
190:
191: // Grey scale images
192: if (numBands == 1) {
193:
194: isPalette = true;
195: paletteEntries = 256;
196: // int sampleSize[] = sm.getSampleSize();
197: bitsPerPixel = sampleSize[0];
198:
199: destScanlineBytes = (int) Math
200: .ceil((double) (w * bitsPerPixel) / 8.0);
201:
202: r = new byte[256];
203: g = new byte[256];
204: b = new byte[256];
205: a = new byte[256];
206:
207: for (int i = 0; i < 256; i++) {
208: r[i] = (byte) i;
209: g[i] = (byte) i;
210: b[i] = (byte) i;
211: //Fix 4672486: BMPEncoder writes wrong alpha lut into
212: // stream for gray-scale image
213: a[i] = (byte) 255;
214: }
215: } else if (sm instanceof SinglePixelPackedSampleModel) {
216: bitsPerPixel = DataBuffer.getDataTypeSize(sm
217: .getDataType());
218: destScanlineBytes = w * bitsPerPixel + 7 >> 3;
219: }
220: }
221:
222: // actual writing of image data
223: int fileSize = 0;
224: int offset = 0;
225: int headerSize = 0;
226: int imageSize = 0;
227: int xPelsPerMeter = 0;
228: int yPelsPerMeter = 0;
229: int colorsUsed = 0;
230: int colorsImportant = paletteEntries;
231: int padding = 0;
232:
233: // Calculate padding for each scanline
234: int remainder = destScanlineBytes % 4;
235: if (remainder != 0) {
236: padding = 4 - remainder;
237: }
238:
239: switch (version) {
240: case BMPEncodeParam.VERSION_2:
241: offset = 26 + paletteEntries * 3;
242: headerSize = 12;
243: imageSize = (destScanlineBytes + padding) * h;
244: fileSize = imageSize + offset;
245: throw new RuntimeException(JaiI18N
246: .getString("BMPImageEncoder5"));
247: //break;
248:
249: case BMPEncodeParam.VERSION_3:
250: // FileHeader is 14 bytes, BitmapHeader is 40 bytes,
251: // add palette size and that is where the data will begin
252: if (isCompressed && bitsPerPixel == 8) {
253: compression = 1;
254: } else if (isCompressed && bitsPerPixel == 4) {
255: compression = 2;
256: }
257: offset = 54 + paletteEntries * 4;
258:
259: imageSize = (destScanlineBytes + padding) * h;
260: fileSize = imageSize + offset;
261: headerSize = 40;
262: break;
263:
264: case BMPEncodeParam.VERSION_4:
265: headerSize = 108;
266: throw new RuntimeException(JaiI18N
267: .getString("BMPImageEncoder5"));
268: // break;
269: }
270:
271: int redMask = 0, blueMask = 0, greenMask = 0;
272: if (cm instanceof DirectColorModel) {
273: redMask = ((DirectColorModel) cm).getRedMask();
274: greenMask = ((DirectColorModel) cm).getGreenMask();
275: blueMask = ((DirectColorModel) cm).getBlueMask();
276: destScanlineBytes = w;
277: compression = 3;
278: fileSize += 12;
279: offset += 12;
280: }
281:
282: writeFileHeader(fileSize, offset);
283:
284: writeInfoHeader(headerSize, bitsPerPixel);
285:
286: // compression
287: writeDWord(compression);
288:
289: // imageSize
290: writeDWord(imageSize);
291:
292: // xPelsPerMeter
293: writeDWord(xPelsPerMeter);
294:
295: // yPelsPerMeter
296: writeDWord(yPelsPerMeter);
297:
298: // Colors Used
299: writeDWord(colorsUsed);
300:
301: // Colors Important
302: writeDWord(colorsImportant);
303:
304: if (compression == 3) {
305: writeDWord(redMask);
306: writeDWord(greenMask);
307: writeDWord(blueMask);
308: }
309:
310: if (compression == 3) {
311: for (int i = 0; i < h; i++) {
312: int row = minY + i;
313:
314: if (!isTopDown)
315: row = minY + h - i - 1;
316:
317: // Get the pixels
318: Rectangle srcRect = new Rectangle(minX, row, w, 1);
319: Raster src = im.getData(srcRect);
320:
321: SampleModel sm1 = src.getSampleModel();
322: int pos = 0;
323: int startX = srcRect.x - src.getSampleModelTranslateX();
324: int startY = srcRect.y - src.getSampleModelTranslateY();
325: if (sm1 instanceof SinglePixelPackedSampleModel) {
326: SinglePixelPackedSampleModel sppsm = (SinglePixelPackedSampleModel) sm1;
327: pos = sppsm.getOffset(startX, startY);
328: }
329:
330: switch (dataType) {
331: case DataBuffer.TYPE_SHORT:
332: short[] sdata = ((DataBufferShort) src
333: .getDataBuffer()).getData();
334: for (int m = 0; m < sdata.length; m++)
335: writeWord(sdata[m]);
336: break;
337:
338: case DataBuffer.TYPE_USHORT:
339: short[] usdata = ((DataBufferUShort) src
340: .getDataBuffer()).getData();
341: for (int m = 0; m < usdata.length; m++)
342: writeWord(usdata[m]);
343: break;
344:
345: case DataBuffer.TYPE_INT:
346: int[] idata = ((DataBufferInt) src.getDataBuffer())
347: .getData();
348: for (int m = 0; m < idata.length; m++)
349: writeDWord(idata[m]);
350: break;
351: }
352: }
353: return;
354: }
355:
356: // palette
357: if (isPalette == true) {
358:
359: // write palette
360: switch (version) {
361:
362: // has 3 field entries
363: case BMPEncodeParam.VERSION_2:
364:
365: for (int i = 0; i < paletteEntries; i++) {
366: output.write(b[i]);
367: output.write(g[i]);
368: output.write(r[i]);
369: }
370: break;
371:
372: // has 4 field entries
373: default:
374:
375: for (int i = 0; i < paletteEntries; i++) {
376: output.write(b[i]);
377: output.write(g[i]);
378: output.write(r[i]);
379: output.write(a[i]);
380: }
381: break;
382: }
383:
384: } // else no palette
385:
386: // Writing of actual image data
387:
388: int scanlineBytes = w * numBands;
389:
390: // Buffer for up to 8 rows of pixels
391: int[] pixels = new int[8 * scanlineBytes];
392:
393: // Also create a buffer to hold one line of the data
394: // to be written to the file, so we can use array writes.
395: byte[] bpixels = new byte[destScanlineBytes];
396:
397: int l;
398:
399: if (!isTopDown) {
400: // Process 8 rows at a time so all but the first will have a
401: // multiple of 8 rows.
402: int lastRow = minY + h;
403:
404: for (int row = (lastRow - 1); row >= minY; row -= 8) {
405: // Number of rows being read
406: int rows = Math.min(8, row - minY + 1);
407:
408: // Get the pixels
409: Raster src = im.getData(new Rectangle(minX, row - rows
410: + 1, w, rows));
411:
412: src.getPixels(minX, row - rows + 1, w, rows, pixels);
413:
414: l = 0;
415:
416: // Last possible position in the pixels array
417: int max = scanlineBytes * rows - 1;
418:
419: for (int i = 0; i < rows; i++) {
420:
421: // Beginning of each scanline in the pixels array
422: l = max - (i + 1) * scanlineBytes + 1;
423:
424: writePixels(l, scanlineBytes, bitsPerPixel, pixels,
425: bpixels, padding, numBands, icm);
426: }
427:
428: }
429:
430: } else {
431: // Process 8 rows at a time so all but the last will have a
432: // multiple of 8 rows.
433: int lastRow = minY + h;
434:
435: for (int row = minY; row < lastRow; row += 8) {
436: int rows = Math.min(8, lastRow - row);
437:
438: // Get the pixels
439: Raster src = im.getData(new Rectangle(minX, row, w,
440: rows));
441: src.getPixels(minX, row, w, rows, pixels);
442:
443: l = 0;
444: for (int i = 0; i < rows; i++) {
445:
446: writePixels(l, scanlineBytes, bitsPerPixel, pixels,
447: bpixels, padding, numBands, icm);
448: }
449:
450: }
451:
452: }
453:
454: if (isCompressed && (bitsPerPixel == 4 || bitsPerPixel == 8)) {
455: // Write the RLE EOF marker and
456: output.write(0);
457: output.write(1);
458: incCompImageSize(2);
459: // update the file/image Size
460: imageSize = compImageSize;
461: fileSize = compImageSize + offset;
462: writeSize(fileSize, 2);
463: writeSize(imageSize, 34);
464: }
465:
466: }
467:
468: private void writePixels(int l, int scanlineBytes,
469: int bitsPerPixel, int pixels[], byte bpixels[],
470: int padding, int numBands, IndexColorModel icm)
471: throws IOException {
472:
473: int pixel = 0;
474: int k = 0;
475: switch (bitsPerPixel) {
476:
477: case 1:
478:
479: for (int j = 0; j < scanlineBytes / 8; j++) {
480: bpixels[k++] = (byte) ((pixels[l++] << 7)
481: | (pixels[l++] << 6) | (pixels[l++] << 5)
482: | (pixels[l++] << 4) | (pixels[l++] << 3)
483: | (pixels[l++] << 2) | (pixels[l++] << 1) | pixels[l++]);
484: }
485:
486: // Partially filled last byte, if any
487: if (scanlineBytes % 8 > 0) {
488: pixel = 0;
489: for (int j = 0; j < scanlineBytes % 8; j++) {
490: pixel |= (pixels[l++] << (7 - j));
491: }
492: bpixels[k++] = (byte) pixel;
493: }
494: output.write(bpixels, 0, (scanlineBytes + 7) / 8);
495:
496: break;
497:
498: case 4:
499: if (isCompressed) {
500: byte[] bipixels = new byte[scanlineBytes];
501: for (int h = 0; h < scanlineBytes; h++) {
502: bipixels[h] = (byte) pixels[l++];
503: }
504: encodeRLE4(bipixels, scanlineBytes);
505: } else {
506: for (int j = 0; j < scanlineBytes / 2; j++) {
507: pixel = (pixels[l++] << 4) | pixels[l++];
508: bpixels[k++] = (byte) pixel;
509: }
510: // Put the last pixel of odd-length lines in the 4 MSBs
511: if ((scanlineBytes % 2) == 1) {
512: pixel = pixels[l] << 4;
513: bpixels[k++] = (byte) pixel;
514: }
515: output.write(bpixels, 0, (scanlineBytes + 1) / 2);
516: }
517: break;
518:
519: case 8:
520: if (isCompressed) {
521: for (int h = 0; h < scanlineBytes; h++) {
522: bpixels[h] = (byte) pixels[l++];
523: }
524: encodeRLE8(bpixels, scanlineBytes);
525: } else {
526: for (int j = 0; j < scanlineBytes; j++) {
527: bpixels[j] = (byte) pixels[l++];
528: }
529: output.write(bpixels, 0, scanlineBytes);
530: }
531: break;
532:
533: case 24:
534: if (numBands == 3) {
535: for (int j = 0; j < scanlineBytes; j += 3) {
536: // Since BMP needs BGR format
537: bpixels[k++] = (byte) (pixels[l + 2]);
538: bpixels[k++] = (byte) (pixels[l + 1]);
539: bpixels[k++] = (byte) (pixels[l]);
540: l += 3;
541: }
542: output.write(bpixels, 0, scanlineBytes);
543: } else {
544: // Case where IndexColorModel had > 256 colors.
545: int entries = icm.getMapSize();
546:
547: byte r[] = new byte[entries];
548: byte g[] = new byte[entries];
549: byte b[] = new byte[entries];
550:
551: icm.getReds(r);
552: icm.getGreens(g);
553: icm.getBlues(b);
554: int index;
555:
556: for (int j = 0; j < scanlineBytes; j++) {
557: index = pixels[l];
558: bpixels[k++] = b[index];
559: bpixels[k++] = g[index];
560: bpixels[k++] = b[index];
561: l++;
562: }
563: output.write(bpixels, 0, scanlineBytes * 3);
564: }
565: break;
566:
567: }
568:
569: // Write out the padding
570: if (!(isCompressed && (bitsPerPixel == 8 || bitsPerPixel == 4))) {
571: for (k = 0; k < padding; k++) {
572: output.write(0);
573: }
574: }
575: }
576:
577: private void encodeRLE8(byte[] bpixels, int scanlineBytes)
578: throws IOException {
579:
580: int runCount = 1, absVal = -1, j = -1;
581: byte runVal = 0, nextVal = 0;
582:
583: runVal = bpixels[++j];
584: byte[] absBuf = new byte[256];
585:
586: while (j < scanlineBytes - 1) {
587: nextVal = bpixels[++j];
588: if (nextVal == runVal) {
589: if (absVal >= 3) {
590: /// Check if there was an existing Absolute Run
591: output.write(0);
592: output.write(absVal);
593: incCompImageSize(2);
594: for (int a = 0; a < absVal; a++) {
595: output.write(absBuf[a]);
596: incCompImageSize(1);
597: }
598: if (!isEven(absVal)) {
599: //Padding
600: output.write(0);
601: incCompImageSize(1);
602: }
603: } else if (absVal > -1) {
604: /// Absolute Encoding for less than 3
605: /// treated as regular encoding
606: /// Do not include the last element since it will
607: /// be inclued in the next encoding/run
608: for (int b = 0; b < absVal; b++) {
609: output.write(1);
610: output.write(absBuf[b]);
611: incCompImageSize(2);
612: }
613: }
614: absVal = -1;
615: runCount++;
616: if (runCount == 256) {
617: /// Only 255 values permitted
618: output.write(runCount - 1);
619: output.write(runVal);
620: incCompImageSize(2);
621: runCount = 1;
622: }
623: } else {
624: if (runCount > 1) {
625: /// If there was an existing run
626: output.write(runCount);
627: output.write(runVal);
628: incCompImageSize(2);
629: } else if (absVal < 0) {
630: // First time..
631: absBuf[++absVal] = runVal;
632: absBuf[++absVal] = nextVal;
633: } else if (absVal < 254) {
634: // 0-254 only
635: absBuf[++absVal] = nextVal;
636: } else {
637: output.write(0);
638: output.write(absVal + 1);
639: incCompImageSize(2);
640: for (int a = 0; a <= absVal; a++) {
641: output.write(absBuf[a]);
642: incCompImageSize(1);
643: }
644: // padding since 255 elts is not even
645: output.write(0);
646: incCompImageSize(1);
647: absVal = -1;
648: }
649: runVal = nextVal;
650: runCount = 1;
651: }
652:
653: if (j == scanlineBytes - 1) { // EOF scanline
654: // Write the run
655: if (absVal == -1) {
656: output.write(runCount);
657: output.write(runVal);
658: incCompImageSize(2);
659: runCount = 1;
660: } else {
661: // write the Absolute Run
662: if (absVal >= 2) {
663: output.write(0);
664: output.write(absVal + 1);
665: incCompImageSize(2);
666: for (int a = 0; a <= absVal; a++) {
667: output.write(absBuf[a]);
668: incCompImageSize(1);
669: }
670: if (!isEven(absVal + 1)) {
671: //Padding
672: output.write(0);
673: incCompImageSize(1);
674: }
675:
676: } else if (absVal > -1) {
677: for (int b = 0; b <= absVal; b++) {
678: output.write(1);
679: output.write(absBuf[b]);
680: incCompImageSize(2);
681: }
682: }
683: }
684: /// EOF scanline
685:
686: output.write(0);
687: output.write(0);
688: incCompImageSize(2);
689: }
690: }
691: }
692:
693: private void encodeRLE4(byte[] bipixels, int scanlineBytes)
694: throws IOException {
695:
696: int runCount = 2, absVal = -1, j = -1, pixel = 0, q = 0;
697: byte runVal1 = 0, runVal2 = 0, nextVal1 = 0, nextVal2 = 0;
698: byte[] absBuf = new byte[256];
699:
700: runVal1 = bipixels[++j];
701: runVal2 = bipixels[++j];
702:
703: while (j < scanlineBytes - 2) {
704: nextVal1 = bipixels[++j];
705: nextVal2 = bipixels[++j];
706:
707: if (nextVal1 == runVal1) {
708:
709: //Check if there was an existing Absolute Run
710: if (absVal >= 4) {
711: output.write(0);
712: output.write(absVal - 1);
713: incCompImageSize(2);
714: // we need to exclude last 2 elts, similarity of
715: // which caused to enter this part of the code
716: for (int a = 0; a < absVal - 2; a += 2) {
717: pixel = (absBuf[a] << 4) | absBuf[a + 1];
718: output.write((byte) pixel);
719: incCompImageSize(1);
720: }
721: // if # of elts is odd - read the last element
722: if (!(isEven(absVal - 1))) {
723: q = absBuf[absVal - 2] << 4 | 0;
724: output.write(q);
725: incCompImageSize(1);
726: }
727: // Padding to word align absolute encoding
728: if (!isEven((int) Math.ceil((absVal - 1) / 2))) {
729: output.write(0);
730: incCompImageSize(1);
731: }
732: } else if (absVal > -1) {
733: output.write(2);
734: pixel = (absBuf[0] << 4) | absBuf[1];
735: output.write(pixel);
736: incCompImageSize(2);
737: }
738: absVal = -1;
739:
740: if (nextVal2 == runVal2) {
741: // Even runlength
742: runCount += 2;
743: if (runCount == 256) {
744: output.write(runCount - 1);
745: pixel = (runVal1 << 4) | runVal2;
746: output.write(pixel);
747: incCompImageSize(2);
748: runCount = 2;
749: if (j < scanlineBytes - 1) {
750: runVal1 = runVal2;
751: runVal2 = bipixels[++j];
752: } else {
753: output.write(01);
754: int r = runVal2 << 4 | 0;
755: output.write(r);
756: incCompImageSize(2);
757: runCount = -1;/// Only EOF required now
758: }
759: }
760: } else {
761: // odd runlength and the run ends here
762: // runCount wont be > 254 since 256/255 case will
763: // be taken care of in above code.
764: runCount++;
765: pixel = (runVal1 << 4) | runVal2;
766: output.write(runCount);
767: output.write(pixel);
768: incCompImageSize(2);
769: runCount = 2;
770: runVal1 = nextVal2;
771: // If end of scanline
772: if (j < scanlineBytes - 1) {
773: runVal2 = bipixels[++j];
774: } else {
775: output.write(01);
776: int r = nextVal2 << 4 | 0;
777: output.write(r);
778: incCompImageSize(2);
779: runCount = -1;/// Only EOF required now
780: }
781:
782: }
783: } else {
784: // Check for existing run
785: if (runCount > 2) {
786: pixel = (runVal1 << 4) | runVal2;
787: output.write(runCount);
788: output.write(pixel);
789: incCompImageSize(2);
790: } else if (absVal < 0) { // first time
791: absBuf[++absVal] = runVal1;
792: absBuf[++absVal] = runVal2;
793: absBuf[++absVal] = nextVal1;
794: absBuf[++absVal] = nextVal2;
795: } else if (absVal < 253) { // only 255 elements
796: absBuf[++absVal] = nextVal1;
797: absBuf[++absVal] = nextVal2;
798: } else {
799: output.write(0);
800: output.write(absVal + 1);
801: incCompImageSize(2);
802: for (int a = 0; a < absVal; a += 2) {
803: pixel = (absBuf[a] << 4) | absBuf[a + 1];
804: output.write((byte) pixel);
805: incCompImageSize(1);
806: }
807: // Padding for word align
808: // since it will fit into 127 bytes
809: output.write(0);
810: incCompImageSize(1);
811: absVal = -1;
812: }
813:
814: runVal1 = nextVal1;
815: runVal2 = nextVal2;
816: runCount = 2;
817: }
818: // Handle the End of scanline for the last 2 4bits
819: if (j >= scanlineBytes - 2) {
820: if (absVal == -1 && runCount >= 2) {
821: if (j == scanlineBytes - 2) {
822: if (bipixels[++j] == runVal1) {
823: runCount++;
824: pixel = (runVal1 << 4) | runVal2;
825: output.write(runCount);
826: output.write(pixel);
827: incCompImageSize(2);
828: } else {
829: pixel = (runVal1 << 4) | runVal2;
830: output.write(runCount);
831: output.write(pixel);
832: output.write(01);
833: pixel = bipixels[j] << 4 | 0;
834: output.write(pixel);
835: int n = bipixels[j] << 4 | 0;
836: incCompImageSize(4);
837: }
838: } else {
839: output.write(runCount);
840: pixel = (runVal1 << 4) | runVal2;
841: output.write(pixel);
842: incCompImageSize(2);
843: }
844: } else if (absVal > -1) {
845: if (j == scanlineBytes - 2) {
846: absBuf[++absVal] = bipixels[++j];
847: }
848: if (absVal >= 2) {
849: output.write(0);
850: output.write(absVal + 1);
851: incCompImageSize(2);
852: for (int a = 0; a < absVal; a += 2) {
853: pixel = (absBuf[a] << 4) | absBuf[a + 1];
854: output.write((byte) pixel);
855: incCompImageSize(1);
856: }
857: if (!(isEven(absVal + 1))) {
858: q = absBuf[absVal] << 4 | 0;
859: output.write(q);
860: incCompImageSize(1);
861: }
862:
863: // Padding
864: if (!isEven((int) Math.ceil((absVal + 1) / 2))) {
865: output.write(0);
866: incCompImageSize(1);
867: }
868:
869: } else {
870: switch (absVal) {
871: case 0:
872: output.write(1);
873: int n = absBuf[0] << 4 | 0;
874: output.write(n);
875: incCompImageSize(2);
876: break;
877: case 1:
878: output.write(2);
879: pixel = (absBuf[0] << 4) | absBuf[1];
880: output.write(pixel);
881: incCompImageSize(2);
882: break;
883: }
884: }
885:
886: }
887: output.write(0);
888: output.write(0);
889: incCompImageSize(2);
890: }
891: }
892: }
893:
894: private synchronized void incCompImageSize(int value) {
895: compImageSize = compImageSize + value;
896: }
897:
898: private boolean isEven(int number) {
899: return (number % 2 == 0 ? true : false);
900: }
901:
902: private void writeFileHeader(int fileSize, int offset)
903: throws IOException {
904: // magic value
905: output.write('B');
906: output.write('M');
907:
908: // File size
909: writeDWord(fileSize);
910:
911: // reserved1 and reserved2
912: output.write(0);
913: output.write(0);
914: output.write(0);
915: output.write(0);
916:
917: // offset to image data
918: writeDWord(offset);
919: }
920:
921: private void writeInfoHeader(int headerSize, int bitsPerPixel)
922: throws IOException {
923:
924: // size of header
925: writeDWord(headerSize);
926:
927: // width
928: writeDWord(w);
929:
930: // height
931: writeDWord(h);
932:
933: // number of planes
934: writeWord(1);
935:
936: // Bits Per Pixel
937: writeWord(bitsPerPixel);
938: }
939:
940: // Methods for little-endian writing
941: public void writeWord(int word) throws IOException {
942: output.write(word & 0xff);
943: output.write((word & 0xff00) >> 8);
944: }
945:
946: public void writeDWord(int dword) throws IOException {
947: output.write(dword & 0xff);
948: output.write((dword & 0xff00) >> 8);
949: output.write((dword & 0xff0000) >> 16);
950: output.write((dword & 0xff000000) >> 24);
951: }
952:
953: private void writeSize(int dword, int offset) throws IOException {
954: ((SeekableOutputStream) output).seek(offset);
955: writeDWord(dword);
956: }
957: }
|