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  package org.apache.commons.imaging.formats.icns;
18  
19  import java.awt.image.BufferedImage;
20  import java.util.ArrayList;
21  import java.util.List;
22  
23  import org.apache.commons.imaging.Imaging;
24  import org.apache.commons.imaging.ImagingException;
25  import org.apache.commons.imaging.common.ImageBuilder;
26  import org.apache.commons.imaging.formats.icns.IcnsImageParser.IcnsElement;
27  
28  final class IcnsDecoder {
29      private static final int[] PALETTE_4BPP = { 0xffffffff, 0xfffcf305, 0xffff6402, 0xffdd0806, 0xfff20884, 0xff4600a5, 0xff0000d4, 0xff02abea, 0xff1fb714,
30              0xff006411, 0xff562c05, 0xff90713a, 0xffc0c0c0, 0xff808080, 0xff404040, 0xff000000 };
31  
32      private static final int[] PALETTE_8BPP = { 0xFFFFFFFF, 0xFFFFFFCC, 0xFFFFFF99, 0xFFFFFF66, 0xFFFFFF33, 0xFFFFFF00, 0xFFFFCCFF, 0xFFFFCCCC, 0xFFFFCC99,
33              0xFFFFCC66, 0xFFFFCC33, 0xFFFFCC00, 0xFFFF99FF, 0xFFFF99CC, 0xFFFF9999, 0xFFFF9966, 0xFFFF9933, 0xFFFF9900, 0xFFFF66FF, 0xFFFF66CC, 0xFFFF6699,
34              0xFFFF6666, 0xFFFF6633, 0xFFFF6600, 0xFFFF33FF, 0xFFFF33CC, 0xFFFF3399, 0xFFFF3366, 0xFFFF3333, 0xFFFF3300, 0xFFFF00FF, 0xFFFF00CC, 0xFFFF0099,
35              0xFFFF0066, 0xFFFF0033, 0xFFFF0000, 0xFFCCFFFF, 0xFFCCFFCC, 0xFFCCFF99, 0xFFCCFF66, 0xFFCCFF33, 0xFFCCFF00, 0xFFCCCCFF, 0xFFCCCCCC, 0xFFCCCC99,
36              0xFFCCCC66, 0xFFCCCC33, 0xFFCCCC00, 0xFFCC99FF, 0xFFCC99CC, 0xFFCC9999, 0xFFCC9966, 0xFFCC9933, 0xFFCC9900, 0xFFCC66FF, 0xFFCC66CC, 0xFFCC6699,
37              0xFFCC6666, 0xFFCC6633, 0xFFCC6600, 0xFFCC33FF, 0xFFCC33CC, 0xFFCC3399, 0xFFCC3366, 0xFFCC3333, 0xFFCC3300, 0xFFCC00FF, 0xFFCC00CC, 0xFFCC0099,
38              0xFFCC0066, 0xFFCC0033, 0xFFCC0000, 0xFF99FFFF, 0xFF99FFCC, 0xFF99FF99, 0xFF99FF66, 0xFF99FF33, 0xFF99FF00, 0xFF99CCFF, 0xFF99CCCC, 0xFF99CC99,
39              0xFF99CC66, 0xFF99CC33, 0xFF99CC00, 0xFF9999FF, 0xFF9999CC, 0xFF999999, 0xFF999966, 0xFF999933, 0xFF999900, 0xFF9966FF, 0xFF9966CC, 0xFF996699,
40              0xFF996666, 0xFF996633, 0xFF996600, 0xFF9933FF, 0xFF9933CC, 0xFF993399, 0xFF993366, 0xFF993333, 0xFF993300, 0xFF9900FF, 0xFF9900CC, 0xFF990099,
41              0xFF990066, 0xFF990033, 0xFF990000, 0xFF66FFFF, 0xFF66FFCC, 0xFF66FF99, 0xFF66FF66, 0xFF66FF33, 0xFF66FF00, 0xFF66CCFF, 0xFF66CCCC, 0xFF66CC99,
42              0xFF66CC66, 0xFF66CC33, 0xFF66CC00, 0xFF6699FF, 0xFF6699CC, 0xFF669999, 0xFF669966, 0xFF669933, 0xFF669900, 0xFF6666FF, 0xFF6666CC, 0xFF666699,
43              0xFF666666, 0xFF666633, 0xFF666600, 0xFF6633FF, 0xFF6633CC, 0xFF663399, 0xFF663366, 0xFF663333, 0xFF663300, 0xFF6600FF, 0xFF6600CC, 0xFF660099,
44              0xFF660066, 0xFF660033, 0xFF660000, 0xFF33FFFF, 0xFF33FFCC, 0xFF33FF99, 0xFF33FF66, 0xFF33FF33, 0xFF33FF00, 0xFF33CCFF, 0xFF33CCCC, 0xFF33CC99,
45              0xFF33CC66, 0xFF33CC33, 0xFF33CC00, 0xFF3399FF, 0xFF3399CC, 0xFF339999, 0xFF339966, 0xFF339933, 0xFF339900, 0xFF3366FF, 0xFF3366CC, 0xFF336699,
46              0xFF336666, 0xFF336633, 0xFF336600, 0xFF3333FF, 0xFF3333CC, 0xFF333399, 0xFF333366, 0xFF333333, 0xFF333300, 0xFF3300FF, 0xFF3300CC, 0xFF330099,
47              0xFF330066, 0xFF330033, 0xFF330000, 0xFF00FFFF, 0xFF00FFCC, 0xFF00FF99, 0xFF00FF66, 0xFF00FF33, 0xFF00FF00, 0xFF00CCFF, 0xFF00CCCC, 0xFF00CC99,
48              0xFF00CC66, 0xFF00CC33, 0xFF00CC00, 0xFF0099FF, 0xFF0099CC, 0xFF009999, 0xFF009966, 0xFF009933, 0xFF009900, 0xFF0066FF, 0xFF0066CC, 0xFF006699,
49              0xFF006666, 0xFF006633, 0xFF006600, 0xFF0033FF, 0xFF0033CC, 0xFF003399, 0xFF003366, 0xFF003333, 0xFF003300, 0xFF0000FF, 0xFF0000CC, 0xFF000099,
50              0xFF000066, 0xFF000033, 0xFFEE0000, 0xFFDD0000, 0xFFBB0000, 0xFFAA0000, 0xFF880000, 0xFF770000, 0xFF550000, 0xFF440000, 0xFF220000, 0xFF110000,
51              0xFF00EE00, 0xFF00DD00, 0xFF00BB00, 0xFF00AA00, 0xFF008800, 0xFF007700, 0xFF005500, 0xFF004400, 0xFF002200, 0xFF001100, 0xFF0000EE, 0xFF0000DD,
52              0xFF0000BB, 0xFF0000AA, 0xFF000088, 0xFF000077, 0xFF000055, 0xFF000044, 0xFF000022, 0xFF000011, 0xFFEEEEEE, 0xFFDDDDDD, 0xFFBBBBBB, 0xFFAAAAAA,
53              0xFF888888, 0xFF777777, 0xFF555555, 0xFF444444, 0xFF222222, 0xFF111111, 0xFF000000 };
54  
55      private static void apply1BPPMask(final byte[] maskData, final ImageBuilder image) throws ImagingException {
56          int position = 0;
57          int bitsLeft = 0;
58          int value = 0;
59  
60          // 1 bit icon types have image data followed by mask data in the same
61          // entry
62          final int totalBytes = (image.getWidth() * image.getHeight() + 7) / 8;
63          if (maskData.length < 2 * totalBytes) {
64              throw new ImagingException("1 BPP mask underrun parsing ICNS file");
65          }
66          position = totalBytes;
67  
68          for (int y = 0; y < image.getHeight(); y++) {
69              for (int x = 0; x < image.getWidth(); x++) {
70                  if (bitsLeft == 0) {
71                      value = 0xff & maskData[position++];
72                      bitsLeft = 8;
73                  }
74                  int alpha;
75                  if ((value & 0x80) != 0) {
76                      alpha = 0xff;
77                  } else {
78                      alpha = 0x00;
79                  }
80                  value <<= 1;
81                  bitsLeft--;
82                  image.setRgb(x, y, alpha << 24 | 0xffffff & image.getRgb(x, y));
83              }
84          }
85      }
86  
87      private static void apply8BPPMask(final byte[] maskData, final ImageBuilder image) {
88          for (int y = 0; y < image.getHeight(); y++) {
89              for (int x = 0; x < image.getWidth(); x++) {
90                  final int alpha = 0xff & maskData[y * image.getWidth() + x];
91                  image.setRgb(x, y, alpha << 24 | 0xffffff & image.getRgb(x, y));
92              }
93          }
94      }
95  
96      private static void decode1BPPImage(final IcnsType imageType, final byte[] imageData, final ImageBuilder image) {
97          int position = 0;
98          int bitsLeft = 0;
99          int value = 0;
100         for (int y = 0; y < imageType.getHeight(); y++) {
101             for (int x = 0; x < imageType.getWidth(); x++) {
102                 if (bitsLeft == 0) {
103                     value = 0xff & imageData[position++];
104                     bitsLeft = 8;
105                 }
106                 int argb;
107                 if ((value & 0x80) != 0) {
108                     argb = 0xff000000;
109                 } else {
110                     argb = 0xffffffff;
111                 }
112                 value <<= 1;
113                 bitsLeft--;
114                 image.setRgb(x, y, argb);
115             }
116         }
117     }
118 
119     private static void decode32BPPImage(final IcnsType imageType, final byte[] imageData, final ImageBuilder image) {
120         for (int y = 0; y < imageType.getHeight(); y++) {
121             for (int x = 0; x < imageType.getWidth(); x++) {
122                 final int argb = 0xff000000 /* the "alpha" is ignored */
123                         | (0xff & imageData[4 * (y * imageType.getWidth() + x) + 1]) << 16 | (0xff & imageData[4 * (y * imageType.getWidth() + x) + 2]) << 8
124                         | 0xff & imageData[4 * (y * imageType.getWidth() + x) + 3];
125                 image.setRgb(x, y, argb);
126             }
127         }
128     }
129 
130     private static void decode4BPPImage(final IcnsType imageType, final byte[] imageData, final ImageBuilder image) {
131         int i = 0;
132         boolean visited = false;
133         for (int y = 0; y < imageType.getHeight(); y++) {
134             for (int x = 0; x < imageType.getWidth(); x++) {
135                 int index;
136                 if (!visited) {
137                     index = 0xf & imageData[i] >> 4;
138                 } else {
139                     index = 0xf & imageData[i++];
140                 }
141                 visited = !visited;
142                 image.setRgb(x, y, PALETTE_4BPP[index]);
143             }
144         }
145     }
146 
147     private static void decode8BPPImage(final IcnsType imageType, final byte[] imageData, final ImageBuilder image) {
148         for (int y = 0; y < imageType.getHeight(); y++) {
149             for (int x = 0; x < imageType.getWidth(); x++) {
150                 final int index = 0xff & imageData[y * imageType.getWidth() + x];
151                 image.setRgb(x, y, PALETTE_8BPP[index]);
152             }
153         }
154     }
155 
156     public static List<BufferedImage> decodeAllImages(final IcnsImageParser.IcnsElement[] icnsElements) throws ImagingException {
157         final List<BufferedImage> result = new ArrayList<>();
158         for (int i = 0; i < icnsElements.length; i++) {
159             final BufferedImage image = decodeImage(icnsElements, i);
160             if (image != null) {
161                 result.add(image);
162             }
163         }
164         return result;
165     }
166 
167     public static BufferedImage decodeImage(final IcnsImageParser.IcnsElement[] icnsElements, final int index) throws ImagingException {
168         final IcnsImageParser.IcnsElement imageElement = icnsElements[index];
169         final IcnsType imageType = IcnsType.findImageType(imageElement.type);
170         if (imageType == null) {
171             return null;
172         }
173 
174         // PNG or JPEG 2000
175         if (imageType == IcnsType.ICNS_16x16_32BIT_ARGB_IMAGE || imageType == IcnsType.ICNS_32x32_32BIT_ARGB_IMAGE
176                 || imageType == IcnsType.ICNS_64x64_32BIT_ARGB_IMAGE || imageType == IcnsType.ICNS_128x128_32BIT_ARGB_IMAGE
177                 || imageType == IcnsType.ICNS_256x256_32BIT_ARGB_IMAGE || imageType == IcnsType.ICNS_512x512_32BIT_ARGB_IMAGE
178                 || imageType == IcnsType.ICNS_1024x1024_32BIT_ARGB_IMAGE || imageType == IcnsType.ICNS_32x32_2x_32BIT_ARGB_IMAGE
179                 || imageType == IcnsType.ICNS_64x64_2x_32BIT_ARGB_IMAGE || imageType == IcnsType.ICNS_256x256_2x_32BIT_ARGB_IMAGE
180                 || imageType == IcnsType.ICNS_512x512_2x_32BIT_ARGB_IMAGE) {
181             BufferedImage image = null;
182             try {
183                 image = Imaging.getBufferedImage(imageElement.data);
184             } catch (final Exception ex) {
185                 if (imageType.getWidth() <= 32) {
186                     try {
187                         image = decodeImageImpl(imageType, imageElement, icnsElements);
188                     } catch (final Exception ignored) {
189                         // ignored
190                     }
191                 }
192                 if (image == null) {
193                     image = new BufferedImage(imageType.getWidth(), imageType.getHeight(), BufferedImage.TYPE_INT_ARGB);
194                 }
195             }
196             return image;
197         }
198 
199         return decodeImageImpl(imageType, imageElement, icnsElements);
200     }
201 
202     private static BufferedImage decodeImageImpl(final IcnsType imageType, final IcnsElement imageElement, final IcnsElement[] icnsElements)
203             throws ImagingException {
204         final int expectedSize = (imageType.getWidth() * imageType.getHeight() * imageType.getBitsPerPixel() + 7) / 8;
205         byte[] imageData;
206         if (imageElement.data.length < expectedSize) {
207             if (imageType.getBitsPerPixel() != 32) {
208                 throw new ImagingException("Short image data but not a 32 bit compressed type");
209             }
210             imageData = Rle24Compression.decompress(imageType.getWidth(), imageType.getHeight(), imageElement.data);
211         } else {
212             imageData = imageElement.data;
213         }
214 
215         final ImageBuilder imageBuilder = new ImageBuilder(imageType.getWidth(), imageType.getHeight(), true);
216         switch (imageType.getBitsPerPixel()) {
217         case 1:
218             decode1BPPImage(imageType, imageData, imageBuilder);
219             break;
220         case 4:
221             decode4BPPImage(imageType, imageData, imageBuilder);
222             break;
223         case 8:
224             decode8BPPImage(imageType, imageData, imageBuilder);
225             break;
226         case 32:
227             decode32BPPImage(imageType, imageData, imageBuilder);
228             break;
229         default:
230             throw new ImagingException("Unsupported bit depth " + imageType.getBitsPerPixel());
231         }
232 
233         IcnsType maskType;
234         IcnsElement maskElement = null;
235         if (imageType.hasMask()) {
236             maskType = imageType;
237             maskElement = imageElement;
238         } else {
239             maskType = IcnsType.find8BPPMaskType(imageType);
240             if (maskType != null) {
241                 for (final IcnsElement icnsElement : icnsElements) {
242                     if (icnsElement.type == maskType.getType()) {
243                         maskElement = icnsElement;
244                         break;
245                     }
246                 }
247             }
248             if (maskElement == null) {
249                 maskType = IcnsType.find1BPPMaskType(imageType);
250                 if (maskType != null) {
251                     for (final IcnsElement icnsElement : icnsElements) {
252                         if (icnsElement.type == maskType.getType()) {
253                             maskElement = icnsElement;
254                             break;
255                         }
256                     }
257                 }
258             }
259         }
260 
261         if (maskElement != null) {
262             if (maskType.getBitsPerPixel() == 1) {
263                 apply1BPPMask(maskElement.data, imageBuilder);
264             } else if (maskType.getBitsPerPixel() == 8) {
265                 apply8BPPMask(maskElement.data, imageBuilder);
266             } else {
267                 throw new ImagingException("Unsupported mask bit depth " + maskType.getBitsPerPixel());
268             }
269         }
270 
271         return imageBuilder.getBufferedImage();
272     }
273 
274     private IcnsDecoder() {
275     }
276 }