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.AbstractBinaryOutputStream;
44 import org.apache.commons.imaging.common.Allocator;
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
232
233
234 public IcoImageParser() {
235 super(ByteOrder.LITTLE_ENDIAN);
236 }
237
238 @Override
239 public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) throws ImagingException, IOException {
240 final ImageContents contents = readImage(byteSource);
241 contents.fileHeader.dump(pw);
242 for (final IconData iconData : contents.iconDatas) {
243 iconData.dump(pw);
244 }
245 return true;
246 }
247
248 @Override
249 protected String[] getAcceptedExtensions() {
250 return ACCEPTED_EXTENSIONS;
251 }
252
253 @Override
254 protected ImageFormat[] getAcceptedTypes() {
255 return new ImageFormat[] { ImageFormats.ICO,
256 };
257 }
258
259 @Override
260 public List<BufferedImage> getAllBufferedImages(final ByteSource byteSource) throws ImagingException, IOException {
261 final ImageContents contents = readImage(byteSource);
262
263 final FileHeader fileHeader = contents.fileHeader;
264 final List<BufferedImage> result = Allocator.arrayList(fileHeader.iconCount);
265 for (int i = 0; i < fileHeader.iconCount; i++) {
266 result.add(contents.iconDatas[i].readBufferedImage());
267 }
268
269 return result;
270 }
271
272 @Override
273 public final BufferedImage getBufferedImage(final ByteSource byteSource, final IcoImagingParameters params) throws ImagingException, IOException {
274 final ImageContents contents = readImage(byteSource);
275 final FileHeader fileHeader = contents.fileHeader;
276 if (fileHeader.iconCount > 0) {
277 return contents.iconDatas[0].readBufferedImage();
278 }
279 throw new ImagingException("No icons in ICO file");
280 }
281
282 @Override
283 public String getDefaultExtension() {
284 return DEFAULT_EXTENSION;
285 }
286
287 @Override
288 public IcoImagingParameters getDefaultParameters() {
289 return new IcoImagingParameters();
290 }
291
292
293 @Override
294 public byte[] getIccProfileBytes(final ByteSource byteSource, final IcoImagingParameters params) throws ImagingException, IOException {
295 return null;
296 }
297
298
299 @Override
300 public ImageInfo getImageInfo(final ByteSource byteSource, final IcoImagingParameters params) throws ImagingException, IOException {
301 return null;
302 }
303
304
305 @Override
306 public Dimension getImageSize(final ByteSource byteSource, final IcoImagingParameters params) throws ImagingException, IOException {
307 return null;
308 }
309
310
311 @Override
312 public ImageMetadata getMetadata(final ByteSource byteSource, final IcoImagingParameters params) throws ImagingException, IOException {
313 return null;
314 }
315
316 @Override
317 public String getName() {
318 return "ico-Custom";
319 }
320
321 private IconData readBitmapIconData(final byte[] iconData, final IconInfo fIconInfo) throws ImagingException, IOException {
322 final ByteArrayInputStream is = new ByteArrayInputStream(iconData);
323 final int size = read4Bytes("size", is, "Not a Valid ICO File", getByteOrder());
324
325
326
327
328
329
330 final int width = read4Bytes("width", is, "Not a Valid ICO File", getByteOrder());
331
332
333
334
335
336
337 final int height = read4Bytes("height", is, "Not a Valid ICO File", getByteOrder());
338
339
340
341
342
343
344
345
346
347
348 final int planes = read2Bytes("planes", is, "Not a Valid ICO File", getByteOrder());
349
350
351
352
353 final int bitCount = read2Bytes("bitCount", is, "Not a Valid ICO File", getByteOrder());
354
355
356
357
358
359
360
361 int compression = read4Bytes("compression", is, "Not a Valid ICO File", getByteOrder());
362
363
364
365
366
367
368
369 final int sizeImage = read4Bytes("sizeImage", is, "Not a Valid ICO File", getByteOrder());
370
371
372
373
374
375
376
377 final int xPelsPerMeter = read4Bytes("xPelsPerMeter", is, "Not a Valid ICO File", getByteOrder());
378
379 final int yPelsPerMeter = read4Bytes("yPelsPerMeter", is, "Not a Valid ICO File", getByteOrder());
380
381 final int colorsUsed = read4Bytes("colorsUsed", is, "Not a Valid ICO File", getByteOrder());
382
383
384
385
386
387
388
389 final int colorsImportant = read4Bytes("ColorsImportant", is, "Not a Valid ICO File", getByteOrder());
390
391 int redMask = 0;
392 int greenMask = 0;
393 int blueMask = 0;
394 int alphaMask = 0;
395 if (compression == 3) {
396 redMask = read4Bytes("redMask", is, "Not a Valid ICO File", getByteOrder());
397 greenMask = read4Bytes("greenMask", is, "Not a Valid ICO File", getByteOrder());
398 blueMask = read4Bytes("blueMask", is, "Not a Valid ICO File", getByteOrder());
399 }
400 final byte[] restOfFile = readBytes("RestOfFile", is, is.available());
401
402 if (size != 40) {
403 throw new ImagingException("Not a Valid ICO File: Wrong bitmap header size " + size);
404 }
405 if (planes != 1) {
406 throw new ImagingException("Not a Valid ICO File: Planes can't be " + planes);
407 }
408
409 if (compression == 0 && bitCount == 32) {
410
411
412 compression = 3;
413 redMask = 0x00ff0000;
414 greenMask = 0x0000ff00;
415 blueMask = 0x000000ff;
416 alphaMask = 0xff000000;
417 }
418
419 final BitmapHeader header = new BitmapHeader(size, width, height, planes, bitCount, compression, sizeImage, xPelsPerMeter, yPelsPerMeter, colorsUsed,
420 colorsImportant);
421
422 final int bitmapPixelsOffset = 14 + 56 + 4 * (colorsUsed == 0 && bitCount <= 8 ? 1 << bitCount : colorsUsed);
423 final int bitmapSize = 14 + 56 + restOfFile.length;
424
425 final ByteArrayOutputStream baos = new ByteArrayOutputStream(Allocator.checkByteArray(bitmapSize));
426 try (AbstractBinaryOutputStream bos = AbstractBinaryOutputStream.littleEndian(baos)) {
427 bos.write('B');
428 bos.write('M');
429 bos.write4Bytes(bitmapSize);
430 bos.write4Bytes(0);
431 bos.write4Bytes(bitmapPixelsOffset);
432
433 bos.write4Bytes(56);
434 bos.write4Bytes(width);
435 bos.write4Bytes(height / 2);
436 bos.write2Bytes(planes);
437 bos.write2Bytes(bitCount);
438 bos.write4Bytes(compression);
439 bos.write4Bytes(sizeImage);
440 bos.write4Bytes(xPelsPerMeter);
441 bos.write4Bytes(yPelsPerMeter);
442 bos.write4Bytes(colorsUsed);
443 bos.write4Bytes(colorsImportant);
444 bos.write4Bytes(redMask);
445 bos.write4Bytes(greenMask);
446 bos.write4Bytes(blueMask);
447 bos.write4Bytes(alphaMask);
448 bos.write(restOfFile);
449 bos.flush();
450 }
451
452 final ByteArrayInputStream bmpInputStream = new ByteArrayInputStream(baos.toByteArray());
453 final BufferedImage bmpImage = new BmpImageParser().getBufferedImage(bmpInputStream, null);
454
455
456
457
458
459
460
461
462 int tScanlineSize = (width + 7) / 8;
463 if (tScanlineSize % 4 != 0) {
464 tScanlineSize += 4 - tScanlineSize % 4;
465
466 }
467 final int colorMapSizeBytes = tScanlineSize * (height / 2);
468 byte[] transparencyMap = null;
469 try {
470 transparencyMap = readBytes("transparencyMap", bmpInputStream, colorMapSizeBytes, "Not a Valid ICO File");
471 } catch (final IOException ioEx) {
472 if (bitCount != 32) {
473 throw ioEx;
474 }
475 }
476
477 boolean allAlphasZero = true;
478 if (bitCount == 32) {
479 for (int y = 0; allAlphasZero && y < bmpImage.getHeight(); y++) {
480 for (int x = 0; x < bmpImage.getWidth(); x++) {
481 if ((bmpImage.getRGB(x, y) & 0xff000000) != 0) {
482 allAlphasZero = false;
483 break;
484 }
485 }
486 }
487 }
488 final BufferedImage resultImage;
489 if (allAlphasZero) {
490 resultImage = new BufferedImage(bmpImage.getWidth(), bmpImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
491 for (int y = 0; y < resultImage.getHeight(); y++) {
492 for (int x = 0; x < resultImage.getWidth(); x++) {
493 int alpha = 0xff;
494 if (transparencyMap != null) {
495 final int alphaByte = 0xff & transparencyMap[tScanlineSize * (bmpImage.getHeight() - y - 1) + x / 8];
496 alpha = 0x01 & alphaByte >> 7 - x % 8;
497 alpha = alpha == 0 ? 0xff : 0x00;
498 }
499 resultImage.setRGB(x, y, alpha << 24 | 0xffffff & bmpImage.getRGB(x, y));
500 }
501 }
502 } else {
503 resultImage = bmpImage;
504 }
505 return new BitmapIconData(fIconInfo, header, resultImage);
506 }
507
508 private FileHeader readFileHeader(final InputStream is) throws ImagingException, IOException {
509 final int reserved = read2Bytes("Reserved", is, "Not a Valid ICO File", getByteOrder());
510 final int iconType = read2Bytes("IconType", is, "Not a Valid ICO File", getByteOrder());
511 final int iconCount = read2Bytes("IconCount", is, "Not a Valid ICO File", getByteOrder());
512
513 if (reserved != 0) {
514 throw new ImagingException("Not a Valid ICO File: reserved is " + reserved);
515 }
516 if (iconType != 1 && iconType != 2) {
517 throw new ImagingException("Not a Valid ICO File: icon type is " + iconType);
518 }
519
520 return new FileHeader(reserved, iconType, iconCount);
521
522 }
523
524 private IconData readIconData(final byte[] iconData, final IconInfo fIconInfo) throws ImagingException, IOException {
525 final ImageFormat imageFormat = Imaging.guessFormat(iconData);
526 if (imageFormat.equals(ImageFormats.PNG)) {
527 final BufferedImage bufferedImage = Imaging.getBufferedImage(iconData);
528 return new PngIconData(fIconInfo, bufferedImage);
529 }
530 return readBitmapIconData(iconData, fIconInfo);
531 }
532
533 private IconInfo readIconInfo(final InputStream is) throws IOException {
534
535 final byte width = readByte("Width", is, "Not a Valid ICO File");
536
537 final byte height = readByte("Height", is, "Not a Valid ICO File");
538
539
540
541 final byte colorCount = readByte("ColorCount", is, "Not a Valid ICO File");
542
543 final byte reserved = readByte("Reserved", is, "Not a Valid ICO File");
544
545 final int planes = read2Bytes("Planes", is, "Not a Valid ICO File", getByteOrder());
546
547
548
549 final int bitCount = read2Bytes("BitCount", is, "Not a Valid ICO File", getByteOrder());
550
551 final int imageSize = read4Bytes("ImageSize", is, "Not a Valid ICO File", getByteOrder());
552
553 final int imageOffset = read4Bytes("ImageOffset", is, "Not a Valid ICO File", getByteOrder());
554
555 return new IconInfo(width, height, colorCount, reserved, planes, bitCount, imageSize, imageOffset);
556 }
557
558 private ImageContents readImage(final ByteSource byteSource) throws ImagingException, IOException {
559 try (InputStream is = byteSource.getInputStream()) {
560 final FileHeader fileHeader = readFileHeader(is);
561
562 final IconInfo[] fIconInfos = Allocator.array(fileHeader.iconCount, IconInfo[]::new, IconInfo.SHALLOW_SIZE);
563 for (int i = 0; i < fileHeader.iconCount; i++) {
564 fIconInfos[i] = readIconInfo(is);
565 }
566
567 final IconData[] fIconDatas = Allocator.array(fileHeader.iconCount, IconData[]::new, IconData.SHALLOW_SIZE);
568 for (int i = 0; i < fileHeader.iconCount; i++) {
569 final byte[] iconData = byteSource.getByteArray(fIconInfos[i].imageOffset, fIconInfos[i].imageSize);
570 fIconDatas[i] = readIconData(iconData, fIconInfos[i]);
571 }
572
573 return new ImageContents(fileHeader, fIconDatas);
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
599
600
601 @Override
602 public void writeImage(final BufferedImage src, final OutputStream os, IcoImagingParameters params) throws ImagingException, IOException {
603 if (params == null) {
604 params = new IcoImagingParameters();
605 }
606 final PixelDensity pixelDensity = params.getPixelDensity();
607
608 final PaletteFactory paletteFactory = new PaletteFactory();
609 final SimplePalette palette = paletteFactory.makeExactRgbPaletteSimple(src, 256);
610 final int bitCount;
611
612
613 if (palette == null) {
614 final boolean hasTransparency = paletteFactory.hasTransparency(src);
615 if (hasTransparency) {
616 bitCount = 32;
617 } else {
618 bitCount = 24;
619 }
620 } else if (palette.length() <= 2) {
621 bitCount = 1;
622 } else if (palette.length() <= 16) {
623 bitCount = 4;
624 } else {
625 bitCount = 8;
626 }
627
628 try (AbstractBinaryOutputStream bos = AbstractBinaryOutputStream.littleEndian(os)) {
629
630 int scanlineSize = (bitCount * src.getWidth() + 7) / 8;
631 if (scanlineSize % 4 != 0) {
632 scanlineSize += 4 - scanlineSize % 4;
633
634 }
635 int tScanlineSize = (src.getWidth() + 7) / 8;
636 if (tScanlineSize % 4 != 0) {
637 tScanlineSize += 4 - tScanlineSize % 4;
638
639 }
640 final int imageSize = 40 + 4 * (bitCount <= 8 ? 1 << bitCount : 0) + src.getHeight() * scanlineSize + src.getHeight() * tScanlineSize;
641
642
643 bos.write2Bytes(0);
644 bos.write2Bytes(1);
645 bos.write2Bytes(1);
646
647
648 int iconDirEntryWidth = src.getWidth();
649 int iconDirEntryHeight = src.getHeight();
650 if (iconDirEntryWidth > 255 || iconDirEntryHeight > 255) {
651 iconDirEntryWidth = 0;
652 iconDirEntryHeight = 0;
653 }
654 bos.write(iconDirEntryWidth);
655 bos.write(iconDirEntryHeight);
656 bos.write(bitCount >= 8 ? 0 : 1 << bitCount);
657 bos.write(0);
658 bos.write2Bytes(1);
659 bos.write2Bytes(bitCount);
660 bos.write4Bytes(imageSize);
661 bos.write4Bytes(22);
662
663
664 bos.write4Bytes(40);
665 bos.write4Bytes(src.getWidth());
666 bos.write4Bytes(2 * src.getHeight());
667 bos.write2Bytes(1);
668 bos.write2Bytes(bitCount);
669 bos.write4Bytes(0);
670 bos.write4Bytes(0);
671 bos.write4Bytes(pixelDensity == null ? 0 : (int) Math.round(pixelDensity.horizontalDensityMetres()));
672
673
674
675 bos.write4Bytes(pixelDensity == null ? 0 : (int) Math.round(pixelDensity.horizontalDensityMetres()));
676
677
678
679 bos.write4Bytes(0);
680 bos.write4Bytes(0);
681
682 if (palette != null) {
683 for (int i = 0; i < 1 << bitCount; i++) {
684 if (i < palette.length()) {
685 final int argb = palette.getEntry(i);
686 bos.write3Bytes(argb);
687 bos.write(0);
688 } else {
689 bos.write4Bytes(0);
690 }
691 }
692 }
693
694 int bitCache = 0;
695 int bitsInCache = 0;
696 final int rowPadding = scanlineSize - (bitCount * src.getWidth() + 7) / 8;
697 for (int y = src.getHeight() - 1; y >= 0; y--) {
698 for (int x = 0; x < src.getWidth(); x++) {
699 final int argb = src.getRGB(x, y);
700
701 if (palette == null) {
702 if (bitCount == 24) {
703 bos.write3Bytes(argb);
704 } else if (bitCount == 32) {
705 bos.write4Bytes(argb);
706 }
707 } else if (bitCount < 8) {
708 final int rgb = 0xffffff & argb;
709 final int index = palette.getPaletteIndex(rgb);
710 bitCache <<= bitCount;
711 bitCache |= index;
712 bitsInCache += bitCount;
713 if (bitsInCache >= 8) {
714 bos.write(0xff & bitCache);
715 bitCache = 0;
716 bitsInCache = 0;
717 }
718 } else if (bitCount == 8) {
719 final int rgb = 0xffffff & argb;
720 final int index = palette.getPaletteIndex(rgb);
721 bos.write(0xff & index);
722 }
723 }
724
725 if (bitsInCache > 0) {
726 bitCache <<= 8 - bitsInCache;
727 bos.write(0xff & bitCache);
728 bitCache = 0;
729 bitsInCache = 0;
730 }
731
732 for (int x = 0; x < rowPadding; x++) {
733 bos.write(0);
734 }
735 }
736
737 final int tRowPadding = tScanlineSize - (src.getWidth() + 7) / 8;
738 for (int y = src.getHeight() - 1; y >= 0; y--) {
739 for (int x = 0; x < src.getWidth(); x++) {
740 final int argb = src.getRGB(x, y);
741 final int alpha = 0xff & argb >> 24;
742 bitCache <<= 1;
743 if (alpha == 0) {
744 bitCache |= 1;
745 }
746 bitsInCache++;
747 if (bitsInCache >= 8) {
748 bos.write(0xff & bitCache);
749 bitCache = 0;
750 bitsInCache = 0;
751 }
752 }
753
754 if (bitsInCache > 0) {
755 bitCache <<= 8 - bitsInCache;
756 bos.write(0xff & bitCache);
757 bitCache = 0;
758 bitsInCache = 0;
759 }
760
761 for (int x = 0; x < tRowPadding; x++) {
762 bos.write(0);
763 }
764 }
765 }
766 }
767 }