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