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