View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.imaging.formats.ico;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertNotNull;
22  import static org.junit.jupiter.api.Assertions.assertThrows;
23  
24  import java.awt.image.BufferedImage;
25  import java.io.ByteArrayOutputStream;
26  import java.io.File;
27  import java.io.IOException;
28  import java.nio.file.Files;
29  import java.util.HashMap;
30  import java.util.Map;
31  
32  import org.apache.commons.imaging.Imaging;
33  import org.apache.commons.imaging.ImagingException;
34  import org.apache.commons.imaging.common.BinaryOutputStream;
35  import org.apache.commons.imaging.internal.Debug;
36  import org.apache.commons.io.FileUtils;
37  import org.junit.jupiter.api.Test;
38  
39  public class IcoRoundtripTest extends AbstractIcoTest {
40      private interface BitmapGenerator {
41          byte[] generateBitmap(int foreground, int background, int paletteSize) throws IOException, ImagingException;
42      }
43  
44      private static final class GeneratorFor16BitBitmaps implements BitmapGenerator {
45          @Override
46          public byte[] generateBitmap(final int foreground, final int background, final int paletteSize) throws IOException, ImagingException {
47              try (final ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
48                      final BinaryOutputStream bos = BinaryOutputStream.littleEndian(byteArrayStream)) {
49                  // Palette
50                  for (int i = 0; i < paletteSize; i++) {
51                      bos.write4Bytes(0);
52                  }
53                  // Image
54                  for (int y = 15; y >= 0; y--) {
55                      for (int x = 0; x < 16; x++) {
56                          if (IMAGE[y][x] == 1) {
57                              bos.write2Bytes(0x1f & foreground >> 3 | (0x1f & foreground >> 11) << 5 | (0x1f & foreground >> 19) << 10);
58                          } else {
59                              bos.write2Bytes(0x1f & background >> 3 | (0x1f & background >> 11) << 5 | (0x1f & background >> 19) << 10);
60                          }
61                      }
62                  }
63                  // Mask
64                  for (int y = IMAGE.length - 1; y >= 0; y--) {
65                      bos.write(0);
66                      bos.write(0);
67                      // Pad to 4 bytes:
68                      bos.write(0);
69                      bos.write(0);
70                  }
71                  bos.flush();
72                  return byteArrayStream.toByteArray();
73              }
74          }
75      }
76  
77      private static final class GeneratorFor1BitBitmaps implements BitmapGenerator {
78          @Override
79          public byte[] generateBitmap(final int foreground, final int background, final int paletteSize) throws IOException, ImagingException {
80              try (final ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
81                      final BinaryOutputStream bos = BinaryOutputStream.littleEndian(byteArrayStream)) {
82                  // Palette
83                  bos.write3Bytes(background);
84                  bos.write(0);
85                  bos.write3Bytes(foreground);
86                  bos.write(0);
87                  for (int i = 2; i < paletteSize; i++) {
88                      bos.write4Bytes(0);
89                  }
90                  // Image
91                  for (int y = 15; y >= 0; y--) {
92                      for (int x = 0; x < 16; x += 8) {
93                          bos.write((0x1 & IMAGE[y][x]) << 7 | (0x1 & IMAGE[y][x + 1]) << 6 | (0x1 & IMAGE[y][x + 2]) << 5 | (0x1 & IMAGE[y][x + 3]) << 4
94                                  | (0x1 & IMAGE[y][x + 4]) << 3 | (0x1 & IMAGE[y][x + 5]) << 2 | (0x1 & IMAGE[y][x + 6]) << 1 | (0x1 & IMAGE[y][x + 7]) << 0);
95                      }
96                      // Pad to multiple of 32 bytes
97                      bos.write(0);
98                      bos.write(0);
99                  }
100                 // Mask
101                 for (int y = IMAGE.length - 1; y >= 0; y--) {
102                     bos.write(0);
103                     bos.write(0);
104                     // Pad to 4 bytes:
105                     bos.write(0);
106                     bos.write(0);
107                 }
108                 bos.flush();
109                 return byteArrayStream.toByteArray();
110             }
111         }
112     }
113 
114     private static final class GeneratorFor24BitBitmaps implements BitmapGenerator {
115         @Override
116         public byte[] generateBitmap(final int foreground, final int background, final int paletteSize) throws IOException, ImagingException {
117             try (final ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
118                     final BinaryOutputStream bos = BinaryOutputStream.littleEndian(byteArrayStream)) {
119                 // Palette
120                 for (int i = 0; i < paletteSize; i++) {
121                     bos.write4Bytes(0);
122                 }
123                 // Image
124                 for (int y = 15; y >= 0; y--) {
125                     for (int x = 0; x < 16; x++) {
126                         if (IMAGE[y][x] == 1) {
127                             bos.write3Bytes(0xffffff & foreground);
128                         } else {
129                             bos.write3Bytes(0xffffff & background);
130                         }
131                     }
132                 }
133                 // Mask
134                 for (int y = IMAGE.length - 1; y >= 0; y--) {
135                     bos.write(0);
136                     bos.write(0);
137                     // Pad to 4 bytes:
138                     bos.write(0);
139                     bos.write(0);
140                 }
141                 bos.flush();
142                 return byteArrayStream.toByteArray();
143             }
144         }
145     }
146 
147     private static final class GeneratorFor32BitBitmaps implements BitmapGenerator {
148         public byte[] generate32bitRGBABitmap(final int foreground, final int background, final int paletteSize, final boolean writeMask) throws IOException {
149             try (final ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
150                     final BinaryOutputStream bos = BinaryOutputStream.littleEndian(byteArrayStream)) {
151                 // Palette
152                 for (int i = 0; i < paletteSize; i++) {
153                     bos.write4Bytes(0);
154                 }
155                 // Image
156                 for (int y = 15; y >= 0; y--) {
157                     for (int x = 0; x < 16; x++) {
158                         if (IMAGE[y][x] == 1) {
159                             bos.write4Bytes(foreground);
160                         } else {
161                             bos.write4Bytes(background);
162                         }
163                     }
164                 }
165                 // Mask
166                 if (writeMask) {
167                     for (int y = IMAGE.length - 1; y >= 0; y--) {
168                         bos.write(0);
169                         bos.write(0);
170                         // Pad to 4 bytes:
171                         bos.write(0);
172                         bos.write(0);
173                     }
174                 }
175                 bos.flush();
176                 return byteArrayStream.toByteArray();
177             }
178         }
179 
180         @Override
181         public byte[] generateBitmap(final int foreground, final int background, final int paletteSize) throws IOException, ImagingException {
182             return generate32bitRGBABitmap(foreground, background, paletteSize, true);
183         }
184     }
185 
186     private static final class GeneratorFor4BitBitmaps implements BitmapGenerator {
187         @Override
188         public byte[] generateBitmap(final int foreground, final int background, final int paletteSize) throws IOException, ImagingException {
189             try (final ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
190                     final BinaryOutputStream bos = BinaryOutputStream.littleEndian(byteArrayStream)) {
191                 // Palette
192                 bos.write3Bytes(background);
193                 bos.write(0);
194                 bos.write3Bytes(foreground);
195                 bos.write(0);
196                 for (int i = 2; i < paletteSize; i++) {
197                     bos.write4Bytes(0);
198                 }
199                 // Image
200                 for (int y = 15; y >= 0; y--) {
201                     for (int x = 0; x < 16; x += 2) {
202                         bos.write((0xf & IMAGE[y][x]) << 4 | 0xf & IMAGE[y][x + 1]);
203                     }
204                 }
205                 // Mask
206                 for (int y = IMAGE.length - 1; y >= 0; y--) {
207                     bos.write(0);
208                     bos.write(0);
209                     // Pad to 4 bytes:
210                     bos.write(0);
211                     bos.write(0);
212                 }
213                 bos.flush();
214                 return byteArrayStream.toByteArray();
215             }
216         }
217     }
218 
219     private static final class GeneratorFor8BitBitmaps implements BitmapGenerator {
220         @Override
221         public byte[] generateBitmap(final int foreground, final int background, final int paletteSize) throws IOException, ImagingException {
222             try (final ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
223                     final BinaryOutputStream bos = BinaryOutputStream.littleEndian(byteArrayStream)) {
224                 // Palette
225                 bos.write3Bytes(background);
226                 bos.write(0);
227                 bos.write3Bytes(foreground);
228                 bos.write(0);
229                 for (int i = 2; i < paletteSize; i++) {
230                     bos.write4Bytes(0);
231                 }
232                 // Image
233                 for (int y = 15; y >= 0; y--) {
234                     for (int x = 0; x < 16; x++) {
235                         bos.write(IMAGE[y][x]);
236                     }
237                 }
238                 // Mask
239                 for (int y = IMAGE.length - 1; y >= 0; y--) {
240                     bos.write(0);
241                     bos.write(0);
242                     // Pad to 4 bytes:
243                     bos.write(0);
244                     bos.write(0);
245                 }
246                 bos.flush();
247                 return byteArrayStream.toByteArray();
248             }
249         }
250     }
251 
252     // 16x16 test image
253     private static final int[][] IMAGE = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
254             { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0 },
255             { 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0 }, { 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 },
256             { 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 },
257             { 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0 },
258             { 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0 }, { 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0 },
259             { 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0 }, { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0 },
260             { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } };
261 
262     private final Map<Integer, BitmapGenerator> generatorMap = new HashMap<>();
263 
264     public IcoRoundtripTest() {
265         generatorMap.put(1, new GeneratorFor1BitBitmaps());
266         generatorMap.put(4, new GeneratorFor4BitBitmaps());
267         generatorMap.put(8, new GeneratorFor8BitBitmaps());
268         generatorMap.put(16, new GeneratorFor16BitBitmaps());
269         generatorMap.put(24, new GeneratorFor24BitBitmaps());
270         generatorMap.put(32, new GeneratorFor32BitBitmaps());
271     }
272 
273     @Test
274     public void test32bitMask() throws Exception {
275         final int foreground = 0xFFF000E0;
276         final int background = 0xFF102030;
277         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
278         try (final BinaryOutputStream bos = BinaryOutputStream.littleEndian(baos)) {
279             // For 32 bit RGBA, the AND mask can be missing:
280             final byte[] bitmap = new GeneratorFor32BitBitmaps().generate32bitRGBABitmap(foreground, background, 0, false);
281             writeICONDIR(bos, 0, 1, 1);
282             writeICONDIRENTRY(bos, 16, 16, 0, 0, 1, 32, 40 + bitmap.length);
283             writeBITMAPINFOHEADER(bos, 16, 2 * 16, 1, 32, 0, 0, 0);
284             bos.write(bitmap);
285             bos.flush();
286         }
287         writeAndReadImageData("16x16x32-no-mask", baos.toByteArray(), foreground, background);
288     }
289 
290     @Test
291     public void testAlphaVersusANDMask() throws Exception {
292         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
293         try (final BinaryOutputStream bos = BinaryOutputStream.littleEndian(baos)) {
294             final byte[] bitmap = new GeneratorFor32BitBitmaps().generate32bitRGBABitmap(0xFF000000, 0x00000000, 0, true);
295             writeICONDIR(bos, 0, 1, 1);
296             writeICONDIRENTRY(bos, 16, 16, 0, 0, 1, 32, 40 + bitmap.length);
297             writeBITMAPINFOHEADER(bos, 16, 2 * 16, 1, 32, 0, 0, 0);
298             bos.write(bitmap);
299             bos.flush();
300         }
301         // The AND mask is fully opaque, yet the fully transparent alpha should
302         // win:
303         writeAndReadImageData("16x16x32-alpha-vs-mask", baos.toByteArray(), 0xFF000000, 0x00000000);
304     }
305 
306     @Test
307     public void testBadICONDIRENTRYIcons() throws Exception {
308         final int foreground = 0xFFF000E0;
309         final int background = 0xFF102030;
310         // Windows ignores the ICONDIRENTRY values when parsing the ICO file.
311         for (final Map.Entry<Integer, BitmapGenerator> entry : generatorMap.entrySet()) {
312             final int bitDepth = entry.getKey();
313             final BitmapGenerator bitmapGenerator = entry.getValue();
314 
315             final ByteArrayOutputStream baos = new ByteArrayOutputStream();
316             try (final BinaryOutputStream bos = BinaryOutputStream.littleEndian(baos)) {
317                 final byte[] bitmap = bitmapGenerator.generateBitmap(foreground, background, bitDepth <= 8 ? 1 << bitDepth : 0);
318                 writeICONDIR(bos, 0, 1, 1);
319                 writeICONDIRENTRY(bos, 3 /* width, should be 16 */, 4 /*
320                                                                        * height, should be 16
321                                                                        */, 7 /*
322                                                                               * colorCount , should be 2 or 0
323                                                                               */, 20 /* reserved, should be 0 */, 11 /*
324                                                                                                                       * planes, should be 1 or 0
325                                                                                                                       */, 19 /*
326                                                                                                                               * bitCount, should be bitDepth
327                                                                                                                               */, 40 + bitmap.length);
328                 writeBITMAPINFOHEADER(bos, 16, 2 * 16, 1, bitDepth, 0, 0, 0);
329                 bos.write(bitmap);
330                 bos.flush();
331             }
332             writeAndReadImageData("16x16x" + bitDepth + "-corrupt-icondirentry", baos.toByteArray(), foreground, background);
333         }
334     }
335 
336     @Test
337     public void testBitfieldCompression() throws Exception {
338         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
339         try (final BinaryOutputStream bos = BinaryOutputStream.littleEndian(baos)) {
340             final byte[] bitmap = new GeneratorFor32BitBitmaps().generate32bitRGBABitmap(0xFFFF0000, 0xFFFFFFFF, 0, true);
341             writeICONDIR(bos, 0, 1, 1);
342             writeICONDIRENTRY(bos, 16, 16, 0, 0, 1, 32, 40 + bitmap.length);
343             writeBITMAPINFOHEADER(bos, 16, 2 * 16, 1, 32, 3 /* BI_BITFIELDS */, 0, 0);
344             bos.write4Bytes(0x000000FF); // red mask
345             bos.write4Bytes(0x0000FF00); // green mask
346             bos.write4Bytes(0x00FF0000); // blue mask
347             bos.write(bitmap);
348             bos.flush();
349         }
350         writeAndReadImageData("16x16x32-bitfield-compressed", baos.toByteArray(), 0xFF0000FF, 0xFFFFFFFF);
351     }
352 
353     @Test
354     public void testColorsUsed() throws Exception {
355         final int foreground = 0xFFF000E0;
356         final int background = 0xFF102030;
357         for (final Map.Entry<Integer, BitmapGenerator> entry : generatorMap.entrySet()) {
358             final int bitDepth = entry.getKey();
359             final BitmapGenerator bitmapGenerator = entry.getValue();
360 
361             final ByteArrayOutputStream baos = new ByteArrayOutputStream();
362             try (final BinaryOutputStream bos = BinaryOutputStream.littleEndian(baos)) {
363                 final byte[] bitmap = bitmapGenerator.generateBitmap(foreground, background, 2);
364                 writeICONDIR(bos, 0, 1, 1);
365                 writeICONDIRENTRY(bos, 3, 4, 7, 20, 11, 19, 40 + bitmap.length);
366                 writeBITMAPINFOHEADER(bos, 16, 2 * 16, 1, bitDepth, 0, 2, 0);
367                 bos.write(bitmap);
368                 bos.flush();
369                 writeAndReadImageData("16x16x" + bitDepth + "-custom-palette", baos.toByteArray(), foreground, background);
370             }
371         }
372     }
373 
374     @Test
375     public void testFullyTransparent32bitRGBA() throws Exception {
376         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
377         try (final BinaryOutputStream bos = BinaryOutputStream.littleEndian(baos)) {
378             final byte[] bitmap = new GeneratorFor32BitBitmaps().generate32bitRGBABitmap(0x00000000, 0x00FFFFFF, 0, true);
379             writeICONDIR(bos, 0, 1, 1);
380             writeICONDIRENTRY(bos, 16, 16, 0, 0, 1, 32, 40 + bitmap.length);
381             writeBITMAPINFOHEADER(bos, 16, 2 * 16, 1, 32, 0, 0, 0);
382             bos.write(bitmap);
383             bos.flush();
384         }
385         // Because every pixel is fully transparent, ***ALPHA GETS IGNORED***:
386         writeAndReadImageData("16x16x32-fully-transparent", baos.toByteArray(), 0xFF000000, 0xFFFFFFFF);
387     }
388 
389     @Test
390     public void testNormalIcons() throws Exception {
391         final int foreground = 0xFFF000E0;
392         final int background = 0xFF102030;
393         for (final Map.Entry<Integer, BitmapGenerator> entry : generatorMap.entrySet()) {
394             final int bitDepth = entry.getKey();
395             final BitmapGenerator bitmapGenerator = entry.getValue();
396 
397             final ByteArrayOutputStream baos = new ByteArrayOutputStream();
398             try (final BinaryOutputStream bos = BinaryOutputStream.littleEndian(baos)) {
399                 final byte[] bitmap = bitmapGenerator.generateBitmap(foreground, background, bitDepth <= 8 ? 1 << bitDepth : 0);
400                 writeICONDIR(bos, 0, 1, 1);
401                 writeICONDIRENTRY(bos, 16, 16, 0, 0, 1, bitDepth, 40 + bitmap.length);
402                 writeBITMAPINFOHEADER(bos, 16, 2 * 16, 1, bitDepth, 0, 0, 0);
403                 bos.write(bitmap);
404                 bos.flush();
405                 writeAndReadImageData("16x16x" + bitDepth, baos.toByteArray(), foreground, background);
406             }
407         }
408     }
409 
410     @Test
411     public void testZeroColorPlanes() throws Exception {
412         final int foreground = 0xFFF000E0;
413         final int background = 0xFF102030;
414         for (final Map.Entry<Integer, BitmapGenerator> entry : generatorMap.entrySet()) {
415             final int bitDepth = entry.getKey();
416             final BitmapGenerator bitmapGenerator = entry.getValue();
417 
418             final ByteArrayOutputStream baos = new ByteArrayOutputStream();
419             try (final BinaryOutputStream bos = BinaryOutputStream.littleEndian(baos)) {
420                 final byte[] bitmap = bitmapGenerator.generateBitmap(foreground, background, bitDepth <= 8 ? 1 << bitDepth : 0);
421                 writeICONDIR(bos, 0, 1, 1);
422                 writeICONDIRENTRY(bos, 16, 16, 0, 0, 1, bitDepth, 40 + bitmap.length);
423                 writeBITMAPINFOHEADER(bos, 16, 2 * 16, 0 /* should be 1 */, bitDepth, 0, 0, 0);
424                 bos.write(bitmap);
425                 bos.flush();
426             }
427 
428             assertThrows(ImagingException.class,
429                     () -> writeAndReadImageData("16x16x" + bitDepth + "-zero-colorPlanes", baos.toByteArray(), foreground, background));
430         }
431     }
432 
433     private void verify(final BufferedImage data, final int foreground, final int background) {
434         assertNotNull(data);
435         assertEquals(data.getHeight(), IMAGE.length);
436 
437         for (int y = 0; y < data.getHeight(); y++) {
438             assertEquals(data.getWidth(), IMAGE[y].length);
439             for (int x = 0; x < data.getWidth(); x++) {
440                 final int imageARGB = IMAGE[y][x] == 1 ? foreground : background;
441                 final int dataARGB = data.getRGB(x, y);
442 
443                 if (imageARGB != dataARGB) {
444                     Debug.debug("x: " + x + ", y: " + y + ", image: " + imageARGB + " (0x" + Integer.toHexString(imageARGB) + ")" + ", data: " + dataARGB
445                             + " (0x" + Integer.toHexString(dataARGB) + ")");
446                 }
447                 assertEquals(imageARGB, dataARGB);
448             }
449         }
450     }
451 
452     private void writeAndReadImageData(final String description, final byte[] rawData, final int foreground, final int background)
453             throws IOException, ImagingException {
454         // Uncomment to generate ICO files that can be tested with Windows:
455         // File exportFile = new File("/tmp/" + description + ".ico");
456         // IoUtils.writeToFile(rawData, exportFile);
457 
458         final File tempFile = Files.createTempFile("temp", ".ico").toFile();
459         FileUtils.writeByteArrayToFile(tempFile, rawData);
460 
461         final BufferedImage dstImage = Imaging.getBufferedImage(tempFile);
462 
463         assertNotNull(dstImage);
464         assertEquals(dstImage.getWidth(), IMAGE[0].length);
465         assertEquals(dstImage.getHeight(), IMAGE.length);
466 
467         verify(dstImage, foreground, background);
468     }
469 
470     private void writeBITMAPINFOHEADER(final BinaryOutputStream bos, final int width, final int height, final int colorPlanes, final int bitCount,
471             final int compression, final int colorsUsed, final int colorsImportant) throws IOException {
472         // BITMAPINFOHEADER
473         bos.write4Bytes(40); // biSize, always 40 for BITMAPINFOHEADER
474         bos.write4Bytes(width); // biWidth
475         bos.write4Bytes(height); // biHeight
476         bos.write2Bytes(colorPlanes); // biPlanes
477         bos.write2Bytes(bitCount); // bitCount
478         bos.write4Bytes(compression); // biCompression
479         bos.write4Bytes(0); // biSizeImage, can be 0 for uncompressed
480         bos.write4Bytes(0); // X pixels per metre
481         bos.write4Bytes(0); // Y pixels per metre
482         bos.write4Bytes(colorsUsed); // colors used, ignored
483         bos.write4Bytes(colorsImportant); // colors important
484     }
485 
486     private void writeICONDIR(final BinaryOutputStream bos, final int reserved, final int type, final int count) throws IOException {
487         bos.write2Bytes(reserved);
488         bos.write2Bytes(type);
489         bos.write2Bytes(count);
490     }
491 
492     private void writeICONDIRENTRY(final BinaryOutputStream bos, final int width, final int height, final int colorCount, final int reserved, final int planes,
493             final int bitCount, final int bytesInRes) throws IOException {
494         bos.write(width);
495         bos.write(height);
496         bos.write(colorCount);
497         bos.write(reserved);
498         bos.write2Bytes(planes);
499         bos.write2Bytes(bitCount);
500         bos.write4Bytes(bytesInRes);
501         bos.write4Bytes(22); // image comes immediately after this
502     }
503 
504 }