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.icns;
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.ByteArrayInputStream;
26  import java.io.ByteArrayOutputStream;
27  import java.io.IOException;
28  
29  import org.apache.commons.imaging.Imaging;
30  import org.apache.commons.imaging.ImagingException;
31  import org.apache.commons.imaging.common.BinaryOutputStream;
32  import org.apache.commons.imaging.internal.Debug;
33  import org.junit.jupiter.api.Test;
34  
35  public class IcnsRoundTripTest extends IcnsBaseTest {
36      // 16x16 test image
37      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 },
38              { 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 },
39              { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 },
40              { 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 },
41              { 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0 },
42              { 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 },
43              { 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 },
44              { 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 } };
45  
46      @Test
47      public void test1BPPIconMaskVersus8BPPMask() throws Exception {
48          final int foreground = 0xff000000;
49          final int background = 0xff000000;
50          try (final ByteArrayOutputStream baos = new ByteArrayOutputStream();
51                  final BinaryOutputStream bos = BinaryOutputStream.bigEndian(baos)) {
52              bos.write4Bytes(IcnsImageParser.ICNS_MAGIC);
53              bos.write4Bytes(4 + 4 + 4 + 4 + 2 * 16 * 16 / 8 + 4 + 4 + 16 * 16);
54              bos.write4Bytes(IcnsType.ICNS_16x16_1BIT_IMAGE_AND_MASK.getType());
55              bos.write4Bytes(4 + 4 + 2 * 16 * 16 / 8);
56              // 1 BPP image - all black
57              for (int y = 0; y < 16; y++) {
58                  bos.write(0xff);
59                  bos.write(0xff);
60              }
61              // 1 BPP mask - all opaque
62              for (int y = 0; y < 16; y++) {
63                  bos.write(0xff);
64                  bos.write(0xff);
65              }
66              // 8 BPP alpha mask - partially transparent
67              bos.write4Bytes(IcnsType.ICNS_16x16_8BIT_MASK.getType());
68              bos.write4Bytes(4 + 4 + 16 * 16);
69              for (int y = 0; y < 16; y++) {
70                  for (int x = 0; x < 16; x++) {
71                      if (IMAGE[y][x] != 0) {
72                          bos.write(0xff);
73                      } else {
74                          bos.write(0x00);
75                      }
76                  }
77              }
78              bos.flush();
79              writeAndReadImageData("1bpp-image-mask-versus-8bpp-mask", baos.toByteArray(), foreground, background);
80          }
81      }
82  
83      @Test
84      public void test32BPPHalfMaskedIcon() throws Exception {
85          final int foreground = 0xff000000;
86          final int background = 0xff0000ff;
87          try (final ByteArrayOutputStream baos = new ByteArrayOutputStream();
88                  final BinaryOutputStream bos = BinaryOutputStream.bigEndian(baos)) {
89              bos.write4Bytes(IcnsImageParser.ICNS_MAGIC);
90              bos.write4Bytes(4 + 4 + 4 + 4 + 4 * 16 * 16 + 4 + 4 + 16 * 16 / 8);
91              bos.write4Bytes(IcnsType.ICNS_16x16_32BIT_IMAGE.getType());
92              bos.write4Bytes(4 + 4 + 4 * 16 * 16);
93              for (int y = 0; y < 16; y++) {
94                  for (int x = 0; x < 16; x++) {
95                      // argb, a ignored
96                      bos.write(0);
97                      final int pixel;
98                      if (IMAGE[y][x] != 0) {
99                          pixel = foreground;
100                     } else {
101                         pixel = background;
102                     }
103                     bos.write(0xff & pixel >> 16);
104                     bos.write(0xff & pixel >> 8);
105                     bos.write(0xff & pixel);
106                 }
107             }
108             bos.write4Bytes(IcnsType.ICNS_16x16_1BIT_IMAGE_AND_MASK.getType());
109             bos.write4Bytes(4 + 4 + 16 * 16 / 8);
110             // 1 bit image
111             for (int y = 0; y < 16; y++) {
112                 for (int x = 0; x < 16; x += 8) {
113                     int eightBits = 0;
114                     for (int pos = 0; pos < 8; pos++) {
115                         if (IMAGE[y][x + pos] != 0) {
116                             eightBits |= 1 << 7 - pos;
117                         }
118                     }
119                     bos.write(eightBits);
120                 }
121             }
122             // Missing 1 bit mask!!!
123             bos.flush();
124 
125             assertThrows(ImagingException.class, () -> writeAndReadImageData("32bpp-half-masked-CORRUPT", baos.toByteArray(), foreground, background));
126         }
127     }
128 
129     @Test
130     public void test32BPPMaskedIcon() throws Exception {
131         final int foreground = 0xff000000;
132         final int background = 0x000000ff;
133         try (final ByteArrayOutputStream baos = new ByteArrayOutputStream();
134                 final BinaryOutputStream bos = BinaryOutputStream.bigEndian(baos)) {
135             bos.write4Bytes(IcnsImageParser.ICNS_MAGIC);
136             bos.write4Bytes(4 + 4 + 4 + 4 + 4 * 16 * 16 + 4 + 4 + 2 * 16 * 16 / 8);
137             bos.write4Bytes(IcnsType.ICNS_16x16_32BIT_IMAGE.getType());
138             bos.write4Bytes(4 + 4 + 4 * 16 * 16);
139             for (int y = 0; y < 16; y++) {
140                 for (int x = 0; x < 16; x++) {
141                     // argb, a ignored
142                     bos.write(0);
143                     final int pixel;
144                     if (IMAGE[y][x] != 0) {
145                         pixel = foreground;
146                     } else {
147                         pixel = background;
148                     }
149                     bos.write(0xff & pixel >> 16);
150                     bos.write(0xff & pixel >> 8);
151                     bos.write(0xff & pixel);
152                 }
153             }
154             bos.write4Bytes(IcnsType.ICNS_16x16_1BIT_IMAGE_AND_MASK.getType());
155             bos.write4Bytes(4 + 4 + 2 * 16 * 16 / 8);
156             // 1 bit image
157             for (int y = 0; y < 16; y++) {
158                 for (int x = 0; x < 16; x += 8) {
159                     int eightBits = 0;
160                     for (int pos = 0; pos < 8; pos++) {
161                         if (IMAGE[y][x + pos] != 0) {
162                             eightBits |= 1 << 7 - pos;
163                         }
164                     }
165                     bos.write(eightBits);
166                 }
167             }
168             // 1 bit mask
169             for (int y = 0; y < 16; y++) {
170                 for (int x = 0; x < 16; x += 8) {
171                     int eightBits = 0;
172                     for (int pos = 0; pos < 8; pos++) {
173                         if (IMAGE[y][x + pos] != 0) {
174                             eightBits |= 1 << 7 - pos;
175                         }
176                     }
177                     bos.write(eightBits);
178                 }
179             }
180             bos.flush();
181             writeAndReadImageData("32bpp-image-1bpp-mask", baos.toByteArray(), foreground, background);
182         }
183     }
184 
185     @Test
186     public void test32BPPMaskMissingIcon() throws Exception {
187         final int foreground = 0xff000000;
188         final int background = 0xff0000ff;
189         try (final ByteArrayOutputStream baos = new ByteArrayOutputStream();
190                 final BinaryOutputStream bos = BinaryOutputStream.bigEndian(baos)) {
191             bos.write4Bytes(IcnsImageParser.ICNS_MAGIC);
192             bos.write4Bytes(4 + 4 + 4 + 4 + 4 * 16 * 16);
193             bos.write4Bytes(IcnsType.ICNS_16x16_32BIT_IMAGE.getType());
194             bos.write4Bytes(4 + 4 + 4 * 16 * 16);
195             for (int y = 0; y < 16; y++) {
196                 for (int x = 0; x < 16; x++) {
197                     // argb, a ignored
198                     bos.write(0);
199                     final int pixel;
200                     if (IMAGE[y][x] != 0) {
201                         pixel = foreground;
202                     } else {
203                         pixel = background;
204                     }
205                     bos.write(0xff & pixel >> 16);
206                     bos.write(0xff & pixel >> 8);
207                     bos.write(0xff & pixel);
208                 }
209             }
210             bos.flush();
211             writeAndReadImageData("32bpp-mask-missing", baos.toByteArray(), foreground, background);
212         }
213     }
214 
215     @Test
216     public void test8BPPIcon1BPPMaskVersus8BPPMask() throws Exception {
217         final int foreground = 0xff000000;
218         final int background = 0x00cccccc;
219         try (final ByteArrayOutputStream baos = new ByteArrayOutputStream();
220                 final BinaryOutputStream bos = BinaryOutputStream.bigEndian(baos)) {
221             bos.write4Bytes(IcnsImageParser.ICNS_MAGIC);
222             bos.write4Bytes(4 + 4 + 4 + 4 + 16 * 16 + 4 + 4 + 16 * 16 + 4 + 4 + 2 * 16 * 16 / 8);
223             bos.write4Bytes(IcnsType.ICNS_16x16_8BIT_IMAGE.getType());
224             bos.write4Bytes(4 + 4 + 16 * 16);
225             // 8 BPP image
226             for (int y = 0; y < 16; y++) {
227                 for (int x = 0; x < 16; x++) {
228                     if (IMAGE[y][x] != 0) {
229                         bos.write(0xff);
230                     } else {
231                         bos.write(43);
232                     }
233                 }
234             }
235             // 1 BPP mask
236             bos.write4Bytes(IcnsType.ICNS_16x16_1BIT_IMAGE_AND_MASK.getType());
237             bos.write4Bytes(4 + 4 + 2 * 16 * 16 / 8);
238             // 1 bit image
239             for (int y = 0; y < 16; y++) {
240                 for (int x = 0; x < 16; x += 8) {
241                     int eightBits = 0;
242                     for (int pos = 0; pos < 8; pos++) {
243                         if (IMAGE[y][x + pos] != 0) {
244                             eightBits |= 1 << 7 - pos;
245                         }
246                     }
247                     bos.write(eightBits);
248                 }
249             }
250             // 1 bit mask, all opaque
251             for (int y = 0; y < 16; y++) {
252                 bos.write(0xff);
253                 bos.write(0xff);
254             }
255             // 8 BPP alpha mask, some transparent
256             bos.write4Bytes(IcnsType.ICNS_16x16_8BIT_MASK.getType());
257             bos.write4Bytes(4 + 4 + 16 * 16);
258             for (int y = 0; y < 16; y++) {
259                 for (int x = 0; x < 16; x++) {
260                     if (IMAGE[y][x] != 0) {
261                         bos.write(0xff);
262                     } else {
263                         bos.write(0x00);
264                     }
265                 }
266             }
267             bos.flush();
268             writeAndReadImageData("8bpp-image-1bpp-mask-vs-8bpp-mask", baos.toByteArray(), foreground, background);
269         }
270     }
271 
272     @Test
273     public void test8BPPIcon8BPPMask() throws Exception {
274         final int foreground = 0xff000000;
275         final int background = 0x00cccccc;
276         try (final ByteArrayOutputStream baos = new ByteArrayOutputStream();
277                 final BinaryOutputStream bos = BinaryOutputStream.bigEndian(baos)) {
278             bos.write4Bytes(IcnsImageParser.ICNS_MAGIC);
279             bos.write4Bytes(4 + 4 + 4 + 4 + 16 * 16 + 4 + 4 + 16 * 16);
280             bos.write4Bytes(IcnsType.ICNS_16x16_8BIT_IMAGE.getType());
281             bos.write4Bytes(4 + 4 + 16 * 16);
282             // 8 BPP image
283             for (int y = 0; y < 16; y++) {
284                 for (int x = 0; x < 16; x++) {
285                     if (IMAGE[y][x] != 0) {
286                         bos.write(0xff);
287                     } else {
288                         bos.write(43);
289                     }
290                 }
291             }
292             // 8 BPP alpha mask
293             bos.write4Bytes(IcnsType.ICNS_16x16_8BIT_MASK.getType());
294             bos.write4Bytes(4 + 4 + 16 * 16);
295             for (int y = 0; y < 16; y++) {
296                 for (int x = 0; x < 16; x++) {
297                     if (IMAGE[y][x] != 0) {
298                         bos.write(0xff);
299                     } else {
300                         bos.write(0x00);
301                     }
302                 }
303             }
304             bos.flush();
305             writeAndReadImageData("8bpp-image-8bpp-mask", baos.toByteArray(), foreground, background);
306         }
307     }
308 
309     @Test
310     public void test8BPPIcon8BPPMaskVersus1BPPMask() throws Exception {
311         final int foreground = 0xff000000;
312         final int background = 0x00cccccc;
313         try (final ByteArrayOutputStream baos = new ByteArrayOutputStream();
314                 final BinaryOutputStream bos = BinaryOutputStream.bigEndian(baos)) {
315             bos.write4Bytes(IcnsImageParser.ICNS_MAGIC);
316             bos.write4Bytes(4 + 4 + 4 + 4 + 16 * 16 + 4 + 4 + 16 * 16 + 4 + 4 + 2 * 16 * 16 / 8);
317             bos.write4Bytes(IcnsType.ICNS_16x16_8BIT_IMAGE.getType());
318             bos.write4Bytes(4 + 4 + 16 * 16);
319             // 8 BPP image
320             for (int y = 0; y < 16; y++) {
321                 for (int x = 0; x < 16; x++) {
322                     if (IMAGE[y][x] != 0) {
323                         bos.write(0xff);
324                     } else {
325                         bos.write(43);
326                     }
327                 }
328             }
329             // 8 BPP alpha mask, some transparent
330             bos.write4Bytes(IcnsType.ICNS_16x16_8BIT_MASK.getType());
331             bos.write4Bytes(4 + 4 + 16 * 16);
332             for (int y = 0; y < 16; y++) {
333                 for (int x = 0; x < 16; x++) {
334                     if (IMAGE[y][x] != 0) {
335                         bos.write(0xff);
336                     } else {
337                         bos.write(0x00);
338                     }
339                 }
340             }
341             // 1 BPP mask
342             bos.write4Bytes(IcnsType.ICNS_16x16_1BIT_IMAGE_AND_MASK.getType());
343             bos.write4Bytes(4 + 4 + 2 * 16 * 16 / 8);
344             // 1 bit image
345             for (int y = 0; y < 16; y++) {
346                 for (int x = 0; x < 16; x += 8) {
347                     int eightBits = 0;
348                     for (int pos = 0; pos < 8; pos++) {
349                         if (IMAGE[y][x + pos] != 0) {
350                             eightBits |= 1 << 7 - pos;
351                         }
352                     }
353                     bos.write(eightBits);
354                 }
355             }
356             // 1 bit mask, all opaque
357             for (int y = 0; y < 16; y++) {
358                 bos.write(0xff);
359                 bos.write(0xff);
360             }
361             bos.flush();
362             writeAndReadImageData("8bpp-image-8bpp-mask-vs-1bpp-mask", baos.toByteArray(), foreground, background);
363         }
364     }
365 
366     @Test
367     public void test8BPPIconNoMask() throws Exception {
368         final int foreground = 0xff000000;
369         final int background = 0xffcccccc;
370         try (final ByteArrayOutputStream baos = new ByteArrayOutputStream();
371                 final BinaryOutputStream bos = BinaryOutputStream.bigEndian(baos)) {
372             bos.write4Bytes(IcnsImageParser.ICNS_MAGIC);
373             bos.write4Bytes(4 + 4 + 4 + 4 + 16 * 16);
374             bos.write4Bytes(IcnsType.ICNS_16x16_8BIT_IMAGE.getType());
375             bos.write4Bytes(4 + 4 + 16 * 16);
376             // 8 BPP image
377             for (int y = 0; y < 16; y++) {
378                 for (int x = 0; x < 16; x++) {
379                     if (IMAGE[y][x] != 0) {
380                         bos.write(0xff);
381                     } else {
382                         bos.write(43);
383                     }
384                 }
385             }
386             bos.flush();
387             writeAndReadImageData("8bpp-image-no-mask", baos.toByteArray(), foreground, background);
388         }
389     }
390 
391     private void verify(final BufferedImage data, final int foreground, final int background) {
392         assertNotNull(data);
393         assertEquals(data.getHeight(), IMAGE.length);
394 
395         for (int y = 0; y < data.getHeight(); y++) {
396             assertEquals(data.getWidth(), IMAGE[y].length);
397             for (int x = 0; x < data.getWidth(); x++) {
398                 final int imageARGB = IMAGE[y][x] == 1 ? foreground : background;
399                 final int dataARGB = data.getRGB(x, y);
400 
401                 if (imageARGB != dataARGB) {
402                     Debug.debug("x: " + x + ", y: " + y + ", image: " + imageARGB + " (0x" + Integer.toHexString(imageARGB) + ")" + ", data: " + dataARGB
403                             + " (0x" + Integer.toHexString(dataARGB) + ")");
404                 }
405                 assertEquals(imageARGB, dataARGB);
406             }
407         }
408     }
409 
410     private void writeAndReadImageData(final String description, final byte[] rawData, final int foreground, final int background)
411             throws IOException, ImagingException {
412         final BufferedImage dstImage = Imaging.getBufferedImage(new ByteArrayInputStream(rawData), "description.icns");
413 
414         assertNotNull(dstImage);
415         assertEquals(dstImage.getWidth(), IMAGE[0].length);
416         assertEquals(dstImage.getHeight(), IMAGE.length);
417 
418         verify(dstImage, foreground, background);
419     }
420 }