1 /*******************************************************************************
2 * Copyright (c) 2000, 2012 IBM Corporation and others.
3 *
4 * This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
8 *
9 * SPDX-License-Identifier: EPL-2.0
10 *
11 * Contributors:
12 * IBM Corporation - initial API and implementation
13 *******************************************************************************/
14 package org.eclipse.swt.internal.image;
15
16
17 import org.eclipse.swt.*;
18 import org.eclipse.swt.graphics.*;
19 import java.io.*;
20
21 public final class WinICOFileFormat extends FileFormat {
22
bitInvertData(byte[] data, int startIndex, int endIndex)23 byte[] bitInvertData(byte[] data, int startIndex, int endIndex) {
24 // Destructively bit invert data in the given byte array.
25 for (int i = startIndex; i < endIndex; i++) {
26 data[i] = (byte)(255 - data[i - startIndex]);
27 }
28 return data;
29 }
30
convertPad(byte[] data, int width, int height, int depth, int pad, int newPad)31 static byte[] convertPad(byte[] data, int width, int height, int depth, int pad, int newPad) {
32 if (pad == newPad) return data;
33 int stride = (width * depth + 7) / 8;
34 int bpl = (stride + (pad - 1)) / pad * pad;
35 int newBpl = (stride + (newPad - 1)) / newPad * newPad;
36 byte[] newData = new byte[height * newBpl];
37 int srcIndex = 0, destIndex = 0;
38 for (int y = 0; y < height; y++) {
39 System.arraycopy(data, srcIndex, newData, destIndex, newBpl);
40 srcIndex += bpl;
41 destIndex += newBpl;
42 }
43 return newData;
44 }
45 /**
46 * Answer the size in bytes of the file representation of the given
47 * icon
48 */
iconSize(ImageData i)49 int iconSize(ImageData i) {
50 int shapeDataStride = (i.width * i.depth + 31) / 32 * 4;
51 int maskDataStride = (i.width + 31) / 32 * 4;
52 int dataSize = (shapeDataStride + maskDataStride) * i.height;
53 int paletteSize = i.palette.colors != null ? i.palette.colors.length * 4 : 0;
54 return WinBMPFileFormat.BMPHeaderFixedSize + paletteSize + dataSize;
55 }
56 @Override
isFileFormat(LEDataInputStream stream)57 boolean isFileFormat(LEDataInputStream stream) {
58 try {
59 byte[] header = new byte[4];
60 stream.read(header);
61 stream.unread(header);
62 return header[0] == 0 && header[1] == 0 && header[2] == 1 && header[3] == 0;
63 } catch (Exception e) {
64 return false;
65 }
66 }
isValidIcon(ImageData i)67 boolean isValidIcon(ImageData i) {
68 switch (i.depth) {
69 case 1:
70 case 4:
71 case 8:
72 if (i.palette.isDirect) return false;
73 int size = i.palette.colors.length;
74 return size == 2 || size == 16 || size == 32 || size == 256;
75 case 24:
76 case 32:
77 return i.palette.isDirect;
78 }
79 return false;
80 }
loadFileHeader(LEDataInputStream byteStream)81 int loadFileHeader(LEDataInputStream byteStream) {
82 int[] fileHeader = new int[3];
83 try {
84 fileHeader[0] = byteStream.readShort();
85 fileHeader[1] = byteStream.readShort();
86 fileHeader[2] = byteStream.readShort();
87 } catch (IOException e) {
88 SWT.error(SWT.ERROR_IO, e);
89 }
90 if ((fileHeader[0] != 0) || (fileHeader[1] != 1))
91 SWT.error(SWT.ERROR_INVALID_IMAGE);
92 int numIcons = fileHeader[2];
93 if (numIcons <= 0)
94 SWT.error(SWT.ERROR_INVALID_IMAGE);
95 return numIcons;
96 }
loadFileHeader(LEDataInputStream byteStream, boolean hasHeader)97 int loadFileHeader(LEDataInputStream byteStream, boolean hasHeader) {
98 int[] fileHeader = new int[3];
99 try {
100 if (hasHeader) {
101 fileHeader[0] = byteStream.readShort();
102 fileHeader[1] = byteStream.readShort();
103 } else {
104 fileHeader[0] = 0;
105 fileHeader[1] = 1;
106 }
107 fileHeader[2] = byteStream.readShort();
108 } catch (IOException e) {
109 SWT.error(SWT.ERROR_IO, e);
110 }
111 if ((fileHeader[0] != 0) || (fileHeader[1] != 1))
112 SWT.error(SWT.ERROR_INVALID_IMAGE);
113 int numIcons = fileHeader[2];
114 if (numIcons <= 0)
115 SWT.error(SWT.ERROR_INVALID_IMAGE);
116 return numIcons;
117 }
118 @Override
loadFromByteStream()119 ImageData[] loadFromByteStream() {
120 int numIcons = loadFileHeader(inputStream);
121 int[][] headers = loadIconHeaders(numIcons);
122 ImageData[] icons = new ImageData[headers.length];
123 for (int i = 0; i < icons.length; i++) {
124 icons[i] = loadIcon(headers[i]);
125 }
126 return icons;
127 }
128 /**
129 * Load one icon from the byte stream.
130 */
loadIcon(int[] iconHeader)131 ImageData loadIcon(int[] iconHeader) {
132 try {
133 FileFormat png = getFileFormat(inputStream, "PNG");
134 if (png != null) {
135 png.loader = this.loader;
136 return png.loadFromStream(inputStream)[0];
137 }
138 } catch (Exception e) {
139 }
140 byte[] infoHeader = loadInfoHeader(iconHeader);
141 WinBMPFileFormat bmpFormat = new WinBMPFileFormat();
142 bmpFormat.inputStream = inputStream;
143 PaletteData palette = bmpFormat.loadPalette(infoHeader);
144 byte[] shapeData = bmpFormat.loadData(infoHeader);
145 int width = (infoHeader[4] & 0xFF) | ((infoHeader[5] & 0xFF) << 8) | ((infoHeader[6] & 0xFF) << 16) | ((infoHeader[7] & 0xFF) << 24);
146 int height = (infoHeader[8] & 0xFF) | ((infoHeader[9] & 0xFF) << 8) | ((infoHeader[10] & 0xFF) << 16) | ((infoHeader[11] & 0xFF) << 24);
147 if (height < 0) height = -height;
148 int depth = (infoHeader[14] & 0xFF) | ((infoHeader[15] & 0xFF) << 8);
149 infoHeader[14] = 1;
150 infoHeader[15] = 0;
151 byte[] maskData = bmpFormat.loadData(infoHeader);
152 maskData = convertPad(maskData, width, height, 1, 4, 2);
153 bitInvertData(maskData, 0, maskData.length);
154 return ImageData.internal_new(
155 width,
156 height,
157 depth,
158 palette,
159 4,
160 shapeData,
161 2,
162 maskData,
163 null,
164 -1,
165 -1,
166 SWT.IMAGE_ICO,
167 0,
168 0,
169 0,
170 0);
171 }
loadIconHeaders(int numIcons)172 int[][] loadIconHeaders(int numIcons) {
173 int[][] headers = new int[numIcons][7];
174 try {
175 for (int i = 0; i < numIcons; i++) {
176 headers[i][0] = inputStream.read();
177 headers[i][1] = inputStream.read();
178 headers[i][2] = inputStream.readShort();
179 headers[i][3] = inputStream.readShort();
180 headers[i][4] = inputStream.readShort();
181 headers[i][5] = inputStream.readInt();
182 headers[i][6] = inputStream.readInt();
183 }
184 } catch (IOException e) {
185 SWT.error(SWT.ERROR_IO, e);
186 }
187 return headers;
188 }
loadInfoHeader(int[] iconHeader)189 byte[] loadInfoHeader(int[] iconHeader) {
190 int width = iconHeader[0];
191 int height = iconHeader[1];
192 int numColors = iconHeader[2]; // the number of colors is in the low byte, but the high byte must be 0
193 if (numColors == 0) numColors = 256; // this is specified: '00' represents '256' (0x100) colors
194 if ((numColors != 2) && (numColors != 8) && (numColors != 16) &&
195 (numColors != 32) && (numColors != 256))
196 SWT.error(SWT.ERROR_INVALID_IMAGE);
197 if (inputStream.getPosition() < iconHeader[6]) {
198 // Seek to the specified offset
199 try {
200 inputStream.skip(iconHeader[6] - inputStream.getPosition());
201 } catch (IOException e) {
202 SWT.error(SWT.ERROR_IO, e);
203 return null;
204 }
205 }
206 byte[] infoHeader = new byte[WinBMPFileFormat.BMPHeaderFixedSize];
207 try {
208 inputStream.read(infoHeader);
209 } catch (IOException e) {
210 SWT.error(SWT.ERROR_IO, e);
211 }
212 if (((infoHeader[12] & 0xFF) | ((infoHeader[13] & 0xFF) << 8)) != 1)
213 SWT.error(SWT.ERROR_INVALID_IMAGE);
214 int infoWidth = (infoHeader[4] & 0xFF) | ((infoHeader[5] & 0xFF) << 8) | ((infoHeader[6] & 0xFF) << 16) | ((infoHeader[7] & 0xFF) << 24);
215 int infoHeight = (infoHeader[8] & 0xFF) | ((infoHeader[9] & 0xFF) << 8) | ((infoHeader[10] & 0xFF) << 16) | ((infoHeader[11] & 0xFF) << 24);
216 int bitCount = (infoHeader[14] & 0xFF) | ((infoHeader[15] & 0xFF) << 8);
217 /*
218 * Feature in the ico spec. The spec says that a width/height of 0 represents 256, however, newer images can be created with even larger sizes.
219 * Images with a width/height >= 256 will have their width/height set to 0 in the icon header; the fix for this case is to read the width/height
220 * directly from the image header.
221 */
222 if (width == 0) width = infoWidth;
223 if (height == 0) height = infoHeight / 2;
224 if (height == infoHeight && bitCount == 1) height /= 2;
225 if (!((width == infoWidth) && (height * 2 == infoHeight) &&
226 (bitCount == 1 || bitCount == 4 || bitCount == 8 || bitCount == 24 || bitCount == 32)))
227 SWT.error(SWT.ERROR_INVALID_IMAGE);
228 infoHeader[8] = (byte)(height & 0xFF);
229 infoHeader[9] = (byte)((height >> 8) & 0xFF);
230 infoHeader[10] = (byte)((height >> 16) & 0xFF);
231 infoHeader[11] = (byte)((height >> 24) & 0xFF);
232 return infoHeader;
233 }
234 /**
235 * Unload a single icon
236 */
unloadIcon(ImageData icon)237 void unloadIcon(ImageData icon) {
238 int sizeImage = (((icon.width * icon.depth + 31) / 32 * 4) +
239 ((icon.width + 31) / 32 * 4)) * icon.height;
240 try {
241 outputStream.writeInt(WinBMPFileFormat.BMPHeaderFixedSize);
242 outputStream.writeInt(icon.width);
243 outputStream.writeInt(icon.height * 2);
244 outputStream.writeShort(1);
245 outputStream.writeShort((short)icon.depth);
246 outputStream.writeInt(0);
247 outputStream.writeInt(sizeImage);
248 outputStream.writeInt(0);
249 outputStream.writeInt(0);
250 outputStream.writeInt(icon.palette.colors != null ? icon.palette.colors.length : 0);
251 outputStream.writeInt(0);
252 } catch (IOException e) {
253 SWT.error(SWT.ERROR_IO, e);
254 }
255
256 byte[] rgbs = WinBMPFileFormat.paletteToBytes(icon.palette);
257 try {
258 outputStream.write(rgbs);
259 } catch (IOException e) {
260 SWT.error(SWT.ERROR_IO, e);
261 }
262 unloadShapeData(icon);
263 unloadMaskData(icon);
264 }
265 /**
266 * Unload the icon header for the given icon, calculating the offset.
267 */
unloadIconHeader(ImageData i)268 void unloadIconHeader(ImageData i) {
269 int headerSize = 16;
270 int offset = headerSize + 6;
271 int iconSize = iconSize(i);
272 try {
273 outputStream.write(i.width);
274 outputStream.write(i.height);
275 outputStream.writeShort(i.palette.colors != null ? i.palette.colors.length : 0);
276 outputStream.writeShort(0);
277 outputStream.writeShort(0);
278 outputStream.writeInt(iconSize);
279 outputStream.writeInt(offset);
280 } catch (IOException e) {
281 SWT.error(SWT.ERROR_IO, e);
282 }
283 }
284 @Override
unloadIntoByteStream(ImageLoader loader)285 void unloadIntoByteStream(ImageLoader loader) {
286 /* We do not currently support writing multi-image ico,
287 * so we use the first image data in the loader's array. */
288 ImageData image = loader.data[0];
289 if (!isValidIcon(image))
290 SWT.error(SWT.ERROR_INVALID_IMAGE);
291 try {
292 outputStream.writeShort(0);
293 outputStream.writeShort(1);
294 outputStream.writeShort(1);
295 } catch (IOException e) {
296 SWT.error(SWT.ERROR_IO, e);
297 }
298 unloadIconHeader(image);
299 unloadIcon(image);
300 }
301 /**
302 * Unload the mask data for an icon. The data is flipped vertically
303 * and inverted.
304 */
unloadMaskData(ImageData icon)305 void unloadMaskData(ImageData icon) {
306 ImageData mask = icon.getTransparencyMask();
307 int bpl = (icon.width + 7) / 8;
308 int pad = mask.scanlinePad;
309 int srcBpl = (bpl + pad - 1) / pad * pad;
310 int destBpl = (bpl + 3) / 4 * 4;
311 byte[] buf = new byte[destBpl];
312 int offset = (icon.height - 1) * srcBpl;
313 byte[] data = mask.data;
314 try {
315 for (int i = 0; i < icon.height; i++) {
316 System.arraycopy(data, offset, buf, 0, bpl);
317 bitInvertData(buf, 0, bpl);
318 outputStream.write(buf, 0, destBpl);
319 offset -= srcBpl;
320 }
321 } catch (IOException e) {
322 SWT.error(SWT.ERROR_IO, e);
323 }
324 }
325 /**
326 * Unload the shape data for an icon. The data is flipped vertically.
327 */
unloadShapeData(ImageData icon)328 void unloadShapeData(ImageData icon) {
329 int bpl = (icon.width * icon.depth + 7) / 8;
330 int pad = icon.scanlinePad;
331 int srcBpl = (bpl + pad - 1) / pad * pad;
332 int destBpl = (bpl + 3) / 4 * 4;
333 byte[] buf = new byte[destBpl];
334 int offset = (icon.height - 1) * srcBpl;
335 byte[] data = icon.data;
336 try {
337 for (int i = 0; i < icon.height; i++) {
338 System.arraycopy(data, offset, buf, 0, bpl);
339 outputStream.write(buf, 0, destBpl);
340 offset -= srcBpl;
341 }
342 } catch (IOException e) {
343 SWT.error(SWT.ERROR_IO, e);
344 }
345 }
346 }
347