1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.imaging.formats.ico;
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.ByteArrayInputStream;
27 import java.io.ByteArrayOutputStream;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.io.OutputStream;
31 import java.io.PrintWriter;
32 import java.nio.ByteOrder;
33 import java.util.List;
34
35 import org.apache.commons.imaging.AbstractImageParser;
36 import org.apache.commons.imaging.ImageFormat;
37 import org.apache.commons.imaging.ImageFormats;
38 import org.apache.commons.imaging.ImageInfo;
39 import org.apache.commons.imaging.Imaging;
40 import org.apache.commons.imaging.ImagingException;
41 import org.apache.commons.imaging.PixelDensity;
42 import org.apache.commons.imaging.bytesource.ByteSource;
43 import org.apache.commons.imaging.common.Allocator;
44 import org.apache.commons.imaging.common.BinaryOutputStream;
45 import org.apache.commons.imaging.common.ImageMetadata;
46 import org.apache.commons.imaging.formats.bmp.BmpImageParser;
47 import org.apache.commons.imaging.palette.PaletteFactory;
48 import org.apache.commons.imaging.palette.SimplePalette;
49
50 public class IcoImageParser extends AbstractImageParser<IcoImagingParameters> {
51 private static final class BitmapHeader {
52 public final int size;
53 public final int width;
54 public final int height;
55 public final int planes;
56 public final int bitCount;
57 public final int compression;
58 public final int sizeImage;
59 public final int xPelsPerMeter;
60 public final int yPelsPerMeter;
61 public final int colorsUsed;
62 public final int colorsImportant;
63
64 BitmapHeader(final int size, final int width, final int height, final int planes, final int bitCount, final int compression, final int sizeImage,
65 final int pelsPerMeter, final int pelsPerMeter2, final int colorsUsed, final int colorsImportant) {
66 this.size = size;
67 this.width = width;
68 this.height = height;
69 this.planes = planes;
70 this.bitCount = bitCount;
71 this.compression = compression;
72 this.sizeImage = sizeImage;
73 xPelsPerMeter = pelsPerMeter;
74 yPelsPerMeter = pelsPerMeter2;
75 this.colorsUsed = colorsUsed;
76 this.colorsImportant = colorsImportant;
77 }
78
79 public void dump(final PrintWriter pw) {
80 pw.println("BitmapHeader");
81
82 pw.println("Size: " + size);
83 pw.println("Width: " + width);
84 pw.println("Height: " + height);
85 pw.println("Planes: " + planes);
86 pw.println("BitCount: " + bitCount);
87 pw.println("Compression: " + compression);
88 pw.println("SizeImage: " + sizeImage);
89 pw.println("XPelsPerMeter: " + xPelsPerMeter);
90 pw.println("YPelsPerMeter: " + yPelsPerMeter);
91 pw.println("ColorsUsed: " + colorsUsed);
92 pw.println("ColorsImportant: " + colorsImportant);
93 }
94 }
95
96 private static final class BitmapIconData extends IconData {
97 public final BitmapHeader header;
98 public final BufferedImage bufferedImage;
99
100 BitmapIconData(final IconInfo iconInfo, final BitmapHeader header, final BufferedImage bufferedImage) {
101 super(iconInfo);
102 this.header = header;
103 this.bufferedImage = bufferedImage;
104 }
105
106 @Override
107 protected void dumpSubclass(final PrintWriter pw) {
108 pw.println("BitmapIconData");
109 header.dump(pw);
110 pw.println();
111 }
112
113 @Override
114 public BufferedImage readBufferedImage() throws ImagingException {
115 return bufferedImage;
116 }
117 }
118
119 private static final class FileHeader {
120 public final int reserved;
121 public final int iconType;
122
123 public final int iconCount;
124
125
126 FileHeader(final int reserved, final int iconType, final int iconCount) {
127 this.reserved = reserved;
128 this.iconType = iconType;
129 this.iconCount = iconCount;
130 }
131
132 public void dump(final PrintWriter pw) {
133 pw.println("FileHeader");
134 pw.println("Reserved: " + reserved);
135 pw.println("IconType: " + iconType);
136 pw.println("IconCount: " + iconCount);
137 pw.println();
138 }
139 }
140
141 abstract static class IconData {
142 static final int SHALLOW_SIZE = 16;
143
144 public final IconInfo iconInfo;
145
146 IconData(final IconInfo iconInfo) {
147 this.iconInfo = iconInfo;
148 }
149
150 public void dump(final PrintWriter pw) {
151 iconInfo.dump(pw);
152 pw.println();
153 dumpSubclass(pw);
154 }
155
156 protected abstract void dumpSubclass(PrintWriter pw);
157
158 public abstract BufferedImage readBufferedImage() throws ImagingException;
159 }
160
161 static class IconInfo {
162 static final int SHALLOW_SIZE = 32;
163 public final byte width;
164 public final byte height;
165 public final byte colorCount;
166 public final byte reserved;
167 public final int planes;
168 public final int bitCount;
169 public final int imageSize;
170 public final int imageOffset;
171
172 IconInfo(final byte width, final byte height, final byte colorCount, final byte reserved, final int planes, final int bitCount, final int imageSize,
173 final int imageOffset) {
174 this.width = width;
175 this.height = height;
176 this.colorCount = colorCount;
177 this.reserved = reserved;
178 this.planes = planes;
179 this.bitCount = bitCount;
180 this.imageSize = imageSize;
181 this.imageOffset = imageOffset;
182 }
183
184 public void dump(final PrintWriter pw) {
185 pw.println("IconInfo");
186 pw.println("Width: " + width);
187 pw.println("Height: " + height);
188 pw.println("ColorCount: " + colorCount);
189 pw.println("Reserved: " + reserved);
190 pw.println("Planes: " + planes);
191 pw.println("BitCount: " + bitCount);
192 pw.println("ImageSize: " + imageSize);
193 pw.println("ImageOffset: " + imageOffset);
194 }
195 }
196
197 private static final class ImageContents {
198 public final FileHeader fileHeader;
199 public final IconData[] iconDatas;
200
201 ImageContents(final FileHeader fileHeader, final IconData[] iconDatas) {
202 this.fileHeader = fileHeader;
203 this.iconDatas = iconDatas;
204 }
205 }
206
207 private static final class PngIconData extends IconData {
208 public final BufferedImage bufferedImage;
209
210 PngIconData(final IconInfo iconInfo, final BufferedImage bufferedImage) {
211 super(iconInfo);
212 this.bufferedImage = bufferedImage;
213 }
214
215 @Override
216 protected void dumpSubclass(final PrintWriter pw) {
217 pw.println("PNGIconData");
218 pw.println();
219 }
220
221 @Override
222 public BufferedImage readBufferedImage() {
223 return bufferedImage;
224 }
225 }
226
227 private static final String DEFAULT_EXTENSION = ImageFormats.ICO.getDefaultExtension();
228
229 private static final String[] ACCEPTED_EXTENSIONS = ImageFormats.ICO.getExtensions();
230
231 public IcoImageParser() {
232 super(ByteOrder.LITTLE_ENDIAN);
233 }
234
235 @Override
236 public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) throws ImagingException, IOException {
237 final ImageContents contents = readImage(byteSource);
238 contents.fileHeader.dump(pw);
239 for (final IconData iconData : contents.iconDatas) {
240 iconData.dump(pw);
241 }
242 return true;
243 }
244
245 @Override
246 protected String[] getAcceptedExtensions() {
247 return ACCEPTED_EXTENSIONS;
248 }
249
250 @Override
251 protected ImageFormat[] getAcceptedTypes() {
252 return new ImageFormat[] { ImageFormats.ICO,
253 };
254 }
255
256 @Override
257 public List<BufferedImage> getAllBufferedImages(final ByteSource byteSource) throws ImagingException, IOException {
258 final ImageContents contents = readImage(byteSource);
259
260 final FileHeader fileHeader = contents.fileHeader;
261 final List<BufferedImage> result = Allocator.arrayList(fileHeader.iconCount);
262 for (int i = 0; i < fileHeader.iconCount; i++) {
263 result.add(contents.iconDatas[i].readBufferedImage());
264 }
265
266 return result;
267 }
268
269 @Override
270 public final BufferedImage getBufferedImage(final ByteSource byteSource, final IcoImagingParameters params) throws ImagingException, IOException {
271 final ImageContents contents = readImage(byteSource);
272 final FileHeader fileHeader = contents.fileHeader;
273 if (fileHeader.iconCount > 0) {
274 return contents.iconDatas[0].readBufferedImage();
275 }
276 throw new ImagingException("No icons in ICO file");
277 }
278
279 @Override
280 public String getDefaultExtension() {
281 return DEFAULT_EXTENSION;
282 }
283
284 @Override
285 public IcoImagingParameters getDefaultParameters() {
286 return new IcoImagingParameters();
287 }
288
289
290 @Override
291 public byte[] getIccProfileBytes(final ByteSource byteSource, final IcoImagingParameters params) throws ImagingException, IOException {
292 return null;
293 }
294
295
296 @Override
297 public ImageInfo getImageInfo(final ByteSource byteSource, final IcoImagingParameters params) throws ImagingException, IOException {
298 return null;
299 }
300
301
302 @Override
303 public Dimension getImageSize(final ByteSource byteSource, final IcoImagingParameters params) throws ImagingException, IOException {
304 return null;
305 }
306
307
308 @Override
309 public ImageMetadata getMetadata(final ByteSource byteSource, final IcoImagingParameters params) throws ImagingException, IOException {
310 return null;
311 }
312
313 @Override
314 public String getName() {
315 return "ico-Custom";
316 }
317
318 private IconData readBitmapIconData(final byte[] iconData, final IconInfo fIconInfo) throws ImagingException, IOException {
319 final ByteArrayInputStream is = new ByteArrayInputStream(iconData);
320 final int size = read4Bytes("size", is, "Not a Valid ICO File", getByteOrder());
321
322
323
324
325
326
327 final int width = read4Bytes("width", is, "Not a Valid ICO File", getByteOrder());
328
329
330
331
332
333
334 final int height = read4Bytes("height", is, "Not a Valid ICO File", getByteOrder());
335
336
337
338
339
340
341
342
343
344
345 final int planes = read2Bytes("planes", is, "Not a Valid ICO File", getByteOrder());
346
347
348
349
350 final int bitCount = read2Bytes("bitCount", is, "Not a Valid ICO File", getByteOrder());
351
352
353
354
355
356
357
358 int compression = read4Bytes("compression", is, "Not a Valid ICO File", getByteOrder());
359
360
361
362
363
364
365
366 final int sizeImage = read4Bytes("sizeImage", is, "Not a Valid ICO File", getByteOrder());
367
368
369
370
371
372
373
374 final int xPelsPerMeter = read4Bytes("xPelsPerMeter", is, "Not a Valid ICO File", getByteOrder());
375
376 final int yPelsPerMeter = read4Bytes("yPelsPerMeter", is, "Not a Valid ICO File", getByteOrder());
377
378 final int colorsUsed = read4Bytes("colorsUsed", is, "Not a Valid ICO File", getByteOrder());
379
380
381
382
383
384
385
386 final int colorsImportant = read4Bytes("ColorsImportant", is, "Not a Valid ICO File", getByteOrder());
387
388 int redMask = 0;
389 int greenMask = 0;
390 int blueMask = 0;
391 int alphaMask = 0;
392 if (compression == 3) {
393 redMask = read4Bytes("redMask", is, "Not a Valid ICO File", getByteOrder());
394 greenMask = read4Bytes("greenMask", is, "Not a Valid ICO File", getByteOrder());
395 blueMask = read4Bytes("blueMask", is, "Not a Valid ICO File", getByteOrder());
396 }
397 final byte[] restOfFile = readBytes("RestOfFile", is, is.available());
398
399 if (size != 40) {
400 throw new ImagingException("Not a Valid ICO File: Wrong bitmap header size " + size);
401 }
402 if (planes != 1) {
403 throw new ImagingException("Not a Valid ICO File: Planes can't be " + planes);
404 }
405
406 if (compression == 0 && bitCount == 32) {
407
408
409 compression = 3;
410 redMask = 0x00ff0000;
411 greenMask = 0x0000ff00;
412 blueMask = 0x000000ff;
413 alphaMask = 0xff000000;
414 }
415
416 final BitmapHeader header = new BitmapHeader(size, width, height, planes, bitCount, compression, sizeImage, xPelsPerMeter, yPelsPerMeter, colorsUsed,
417 colorsImportant);
418
419 final int bitmapPixelsOffset = 14 + 56 + 4 * (colorsUsed == 0 && bitCount <= 8 ? 1 << bitCount : colorsUsed);
420 final int bitmapSize = 14 + 56 + restOfFile.length;
421
422 final ByteArrayOutputStream baos = new ByteArrayOutputStream(Allocator.checkByteArray(bitmapSize));
423 try (BinaryOutputStream bos = BinaryOutputStream.littleEndian(baos)) {
424 bos.write('B');
425 bos.write('M');
426 bos.write4Bytes(bitmapSize);
427 bos.write4Bytes(0);
428 bos.write4Bytes(bitmapPixelsOffset);
429
430 bos.write4Bytes(56);
431 bos.write4Bytes(width);
432 bos.write4Bytes(height / 2);
433 bos.write2Bytes(planes);
434 bos.write2Bytes(bitCount);
435 bos.write4Bytes(compression);
436 bos.write4Bytes(sizeImage);
437 bos.write4Bytes(xPelsPerMeter);
438 bos.write4Bytes(yPelsPerMeter);
439 bos.write4Bytes(colorsUsed);
440 bos.write4Bytes(colorsImportant);
441 bos.write4Bytes(redMask);
442 bos.write4Bytes(greenMask);
443 bos.write4Bytes(blueMask);
444 bos.write4Bytes(alphaMask);
445 bos.write(restOfFile);
446 bos.flush();
447 }
448
449 final ByteArrayInputStream bmpInputStream = new ByteArrayInputStream(baos.toByteArray());
450 final BufferedImage bmpImage = new BmpImageParser().getBufferedImage(bmpInputStream, null);
451
452
453
454
455
456
457
458
459 int tScanlineSize = (width + 7) / 8;
460 if (tScanlineSize % 4 != 0) {
461 tScanlineSize += 4 - tScanlineSize % 4;
462
463 }
464 final int colorMapSizeBytes = tScanlineSize * (height / 2);
465 byte[] transparencyMap = null;
466 try {
467 transparencyMap = readBytes("transparencyMap", bmpInputStream, colorMapSizeBytes, "Not a Valid ICO File");
468 } catch (final IOException ioEx) {
469 if (bitCount != 32) {
470 throw ioEx;
471 }
472 }
473
474 boolean allAlphasZero = true;
475 if (bitCount == 32) {
476 for (int y = 0; allAlphasZero && y < bmpImage.getHeight(); y++) {
477 for (int x = 0; x < bmpImage.getWidth(); x++) {
478 if ((bmpImage.getRGB(x, y) & 0xff000000) != 0) {
479 allAlphasZero = false;
480 break;
481 }
482 }
483 }
484 }
485 BufferedImage resultImage;
486 if (allAlphasZero) {
487 resultImage = new BufferedImage(bmpImage.getWidth(), bmpImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
488 for (int y = 0; y < resultImage.getHeight(); y++) {
489 for (int x = 0; x < resultImage.getWidth(); x++) {
490 int alpha = 0xff;
491 if (transparencyMap != null) {
492 final int alphaByte = 0xff & transparencyMap[tScanlineSize * (bmpImage.getHeight() - y - 1) + x / 8];
493 alpha = 0x01 & alphaByte >> 7 - x % 8;
494 alpha = alpha == 0 ? 0xff : 0x00;
495 }
496 resultImage.setRGB(x, y, alpha << 24 | 0xffffff & bmpImage.getRGB(x, y));
497 }
498 }
499 } else {
500 resultImage = bmpImage;
501 }
502 return new BitmapIconData(fIconInfo, header, resultImage);
503 }
504
505 private FileHeader readFileHeader(final InputStream is) throws ImagingException, IOException {
506 final int reserved = read2Bytes("Reserved", is, "Not a Valid ICO File", getByteOrder());
507 final int iconType = read2Bytes("IconType", is, "Not a Valid ICO File", getByteOrder());
508 final int iconCount = read2Bytes("IconCount", is, "Not a Valid ICO File", getByteOrder());
509
510 if (reserved != 0) {
511 throw new ImagingException("Not a Valid ICO File: reserved is " + reserved);
512 }
513 if (iconType != 1 && iconType != 2) {
514 throw new ImagingException("Not a Valid ICO File: icon type is " + iconType);
515 }
516
517 return new FileHeader(reserved, iconType, iconCount);
518
519 }
520
521 private IconData readIconData(final byte[] iconData, final IconInfo fIconInfo) throws ImagingException, IOException {
522 final ImageFormat imageFormat = Imaging.guessFormat(iconData);
523 if (imageFormat.equals(ImageFormats.PNG)) {
524 final BufferedImage bufferedImage = Imaging.getBufferedImage(iconData);
525 return new PngIconData(fIconInfo, bufferedImage);
526 }
527 return readBitmapIconData(iconData, fIconInfo);
528 }
529
530 private IconInfo readIconInfo(final InputStream is) throws IOException {
531
532 final byte width = readByte("Width", is, "Not a Valid ICO File");
533
534 final byte height = readByte("Height", is, "Not a Valid ICO File");
535
536
537
538 final byte colorCount = readByte("ColorCount", is, "Not a Valid ICO File");
539
540 final byte reserved = readByte("Reserved", is, "Not a Valid ICO File");
541
542 final int planes = read2Bytes("Planes", is, "Not a Valid ICO File", getByteOrder());
543
544
545
546 final int bitCount = read2Bytes("BitCount", is, "Not a Valid ICO File", getByteOrder());
547
548 final int imageSize = read4Bytes("ImageSize", is, "Not a Valid ICO File", getByteOrder());
549
550 final int imageOffset = read4Bytes("ImageOffset", is, "Not a Valid ICO File", getByteOrder());
551
552 return new IconInfo(width, height, colorCount, reserved, planes, bitCount, imageSize, imageOffset);
553 }
554
555 private ImageContents readImage(final ByteSource byteSource) throws ImagingException, IOException {
556 try (InputStream is = byteSource.getInputStream()) {
557 final FileHeader fileHeader = readFileHeader(is);
558
559 final IconInfo[] fIconInfos = Allocator.array(fileHeader.iconCount, IconInfo[]::new, IconInfo.SHALLOW_SIZE);
560 for (int i = 0; i < fileHeader.iconCount; i++) {
561 fIconInfos[i] = readIconInfo(is);
562 }
563
564 final IconData[] fIconDatas = Allocator.array(fileHeader.iconCount, IconData[]::new, IconData.SHALLOW_SIZE);
565 for (int i = 0; i < fileHeader.iconCount; i++) {
566 final byte[] iconData = byteSource.getByteArray(fIconInfos[i].imageOffset, fIconInfos[i].imageSize);
567 fIconDatas[i] = readIconData(iconData, fIconInfos[i]);
568 }
569
570 return new ImageContents(fileHeader, fIconDatas);
571 }
572 }
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598 @Override
599 public void writeImage(final BufferedImage src, final OutputStream os, IcoImagingParameters params) throws ImagingException, IOException {
600 if (params == null) {
601 params = new IcoImagingParameters();
602 }
603 final PixelDensity pixelDensity = params.getPixelDensity();
604
605 final PaletteFactory paletteFactory = new PaletteFactory();
606 final SimplePalette palette = paletteFactory.makeExactRgbPaletteSimple(src, 256);
607 final int bitCount;
608
609
610 if (palette == null) {
611 final boolean hasTransparency = paletteFactory.hasTransparency(src);
612 if (hasTransparency) {
613 bitCount = 32;
614 } else {
615 bitCount = 24;
616 }
617 } else if (palette.length() <= 2) {
618 bitCount = 1;
619 } else if (palette.length() <= 16) {
620 bitCount = 4;
621 } else {
622 bitCount = 8;
623 }
624
625 try (BinaryOutputStream bos = BinaryOutputStream.littleEndian(os)) {
626
627 int scanlineSize = (bitCount * src.getWidth() + 7) / 8;
628 if (scanlineSize % 4 != 0) {
629 scanlineSize += 4 - scanlineSize % 4;
630
631 }
632 int tScanlineSize = (src.getWidth() + 7) / 8;
633 if (tScanlineSize % 4 != 0) {
634 tScanlineSize += 4 - tScanlineSize % 4;
635
636 }
637 final int imageSize = 40 + 4 * (bitCount <= 8 ? 1 << bitCount : 0) + src.getHeight() * scanlineSize + src.getHeight() * tScanlineSize;
638
639
640 bos.write2Bytes(0);
641 bos.write2Bytes(1);
642 bos.write2Bytes(1);
643
644
645 int iconDirEntryWidth = src.getWidth();
646 int iconDirEntryHeight = src.getHeight();
647 if (iconDirEntryWidth > 255 || iconDirEntryHeight > 255) {
648 iconDirEntryWidth = 0;
649 iconDirEntryHeight = 0;
650 }
651 bos.write(iconDirEntryWidth);
652 bos.write(iconDirEntryHeight);
653 bos.write(bitCount >= 8 ? 0 : 1 << bitCount);
654 bos.write(0);
655 bos.write2Bytes(1);
656 bos.write2Bytes(bitCount);
657 bos.write4Bytes(imageSize);
658 bos.write4Bytes(22);
659
660
661 bos.write4Bytes(40);
662 bos.write4Bytes(src.getWidth());
663 bos.write4Bytes(2 * src.getHeight());
664 bos.write2Bytes(1);
665 bos.write2Bytes(bitCount);
666 bos.write4Bytes(0);
667 bos.write4Bytes(0);
668 bos.write4Bytes(pixelDensity == null ? 0 : (int) Math.round(pixelDensity.horizontalDensityMetres()));
669
670
671
672 bos.write4Bytes(pixelDensity == null ? 0 : (int) Math.round(pixelDensity.horizontalDensityMetres()));
673
674
675
676 bos.write4Bytes(0);
677 bos.write4Bytes(0);
678
679 if (palette != null) {
680 for (int i = 0; i < 1 << bitCount; i++) {
681 if (i < palette.length()) {
682 final int argb = palette.getEntry(i);
683 bos.write3Bytes(argb);
684 bos.write(0);
685 } else {
686 bos.write4Bytes(0);
687 }
688 }
689 }
690
691 int bitCache = 0;
692 int bitsInCache = 0;
693 final int rowPadding = scanlineSize - (bitCount * src.getWidth() + 7) / 8;
694 for (int y = src.getHeight() - 1; y >= 0; y--) {
695 for (int x = 0; x < src.getWidth(); x++) {
696 final int argb = src.getRGB(x, y);
697
698 if (palette == null) {
699 if (bitCount == 24) {
700 bos.write3Bytes(argb);
701 } else if (bitCount == 32) {
702 bos.write4Bytes(argb);
703 }
704 } else if (bitCount < 8) {
705 final int rgb = 0xffffff & argb;
706 final int index = palette.getPaletteIndex(rgb);
707 bitCache <<= bitCount;
708 bitCache |= index;
709 bitsInCache += bitCount;
710 if (bitsInCache >= 8) {
711 bos.write(0xff & bitCache);
712 bitCache = 0;
713 bitsInCache = 0;
714 }
715 } else if (bitCount == 8) {
716 final int rgb = 0xffffff & argb;
717 final int index = palette.getPaletteIndex(rgb);
718 bos.write(0xff & index);
719 }
720 }
721
722 if (bitsInCache > 0) {
723 bitCache <<= 8 - bitsInCache;
724 bos.write(0xff & bitCache);
725 bitCache = 0;
726 bitsInCache = 0;
727 }
728
729 for (int x = 0; x < rowPadding; x++) {
730 bos.write(0);
731 }
732 }
733
734 final int tRowPadding = tScanlineSize - (src.getWidth() + 7) / 8;
735 for (int y = src.getHeight() - 1; y >= 0; y--) {
736 for (int x = 0; x < src.getWidth(); x++) {
737 final int argb = src.getRGB(x, y);
738 final int alpha = 0xff & argb >> 24;
739 bitCache <<= 1;
740 if (alpha == 0) {
741 bitCache |= 1;
742 }
743 bitsInCache++;
744 if (bitsInCache >= 8) {
745 bos.write(0xff & bitCache);
746 bitCache = 0;
747 bitsInCache = 0;
748 }
749 }
750
751 if (bitsInCache > 0) {
752 bitCache <<= 8 - bitsInCache;
753 bos.write(0xff & bitCache);
754 bitCache = 0;
755 bitsInCache = 0;
756 }
757
758 for (int x = 0; x < tRowPadding; x++) {
759 bos.write(0);
760 }
761 }
762 }
763 }
764 }