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