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 GIFFileFormat extends FileFormat {
22 	String signature;
23 	int screenWidth, screenHeight, backgroundPixel, bitsPerPixel, defaultDepth;
24 	int disposalMethod = 0;
25 	int delayTime = 0;
26 	int transparentPixel = -1;
27 	int repeatCount = 1;
28 
29 	static final int GIF_APPLICATION_EXTENSION_BLOCK_ID = 0xFF;
30 	static final int GIF_GRAPHICS_CONTROL_BLOCK_ID = 0xF9;
31 	static final int GIF_PLAIN_TEXT_BLOCK_ID = 0x01;
32 	static final int GIF_COMMENT_BLOCK_ID = 0xFE;
33 	static final int GIF_EXTENSION_BLOCK_ID = 0x21;
34 	static final int GIF_IMAGE_BLOCK_ID = 0x2C;
35 	static final int GIF_TRAILER_ID = 0x3B;
36 	static final byte [] GIF89a = new byte[] { (byte)'G', (byte)'I', (byte)'F', (byte)'8', (byte)'9', (byte)'a' };
37 	static final byte [] NETSCAPE2_0 = new byte[] { (byte)'N', (byte)'E', (byte)'T', (byte)'S', (byte)'C', (byte)'A', (byte)'P', (byte)'E', (byte)'2', (byte)'.', (byte)'0' };
38 
39 	/**
40 	 * Answer a palette containing numGrays
41 	 * shades of gray, ranging from black to white.
42 	 */
grayRamp(int numGrays)43 	static PaletteData grayRamp(int numGrays) {
44 		int n = numGrays - 1;
45 		RGB[] colors = new RGB[numGrays];
46 		for (int i = 0; i < numGrays; i++) {
47 			int intensity = (byte)((i * 3) * 256 / n);
48 			colors[i] = new RGB(intensity, intensity, intensity);
49 		}
50 		return new PaletteData(colors);
51 	}
52 
53 	@Override
isFileFormat(LEDataInputStream stream)54 	boolean isFileFormat(LEDataInputStream stream) {
55 		try {
56 			byte[] signature = new byte[3];
57 			stream.read(signature);
58 			stream.unread(signature);
59 			return signature[0] == 'G' && signature[1] == 'I' && signature[2] == 'F';
60 		} catch (Exception e) {
61 			return false;
62 		}
63 	}
64 
65 	/**
66 	 * Load the GIF image(s) stored in the input stream.
67 	 * Return an array of ImageData representing the image(s).
68 	 */
69 	@Override
loadFromByteStream()70 	ImageData[] loadFromByteStream() {
71 		byte[] signature = new byte[3];
72 		byte[] versionBytes = new byte[3];
73 		byte[] block = new byte[7];
74 		try {
75 			inputStream.read(signature);
76 			if (!(signature[0] == 'G' && signature[1] == 'I' && signature[2] == 'F'))
77 				SWT.error(SWT.ERROR_INVALID_IMAGE);
78 
79 			inputStream.read(versionBytes);
80 
81 			inputStream.read(block);
82 		} catch (IOException e) {
83 			SWT.error(SWT.ERROR_IO, e);
84 		}
85 		screenWidth = (block[0] & 0xFF) | ((block[1] & 0xFF) << 8);
86 		loader.logicalScreenWidth = screenWidth;
87 		screenHeight = (block[2] & 0xFF) | ((block[3] & 0xFF) << 8);
88 		loader.logicalScreenHeight = screenHeight;
89 		byte bitField = block[4];
90 		backgroundPixel = block[5] & 0xFF;
91 		//aspect = block[6] & 0xFF;
92 		bitsPerPixel = ((bitField >> 4) & 0x07) + 1;
93 		defaultDepth = (bitField & 0x7) + 1;
94 		PaletteData palette = null;
95 		if ((bitField & 0x80) != 0) {
96 			// Global palette.
97 			//sorted = (bitField & 0x8) != 0;
98 			palette = readPalette(1 << defaultDepth);
99 		} else {
100 			// No global palette.
101 			//sorted = false;
102 			backgroundPixel = -1;
103 			defaultDepth = bitsPerPixel;
104 		}
105 		loader.backgroundPixel = backgroundPixel;
106 
107 		ImageData[] images = new ImageData[0];
108 		int id = readID();
109 		while (id != GIF_TRAILER_ID && id != -1) {
110 			if (id == GIF_IMAGE_BLOCK_ID) {
111 				ImageData image = readImageBlock(palette);
112 				if (loader.hasListeners()) {
113 					loader.notifyListeners(new ImageLoaderEvent(loader, image, 3, true));
114 				}
115 				ImageData[] oldImages = images;
116 				images = new ImageData[oldImages.length + 1];
117 				System.arraycopy(oldImages, 0, images, 0, oldImages.length);
118 				images[images.length - 1] = image;
119 			} else if (id == GIF_EXTENSION_BLOCK_ID) {
120 				/* Read the extension block. Currently, only the
121 				 * interesting parts of certain extensions are kept,
122 				 * and the rest is discarded. In future, if we want
123 				 * to keep extensions, they should be grouped with
124 				 * the image data before which they appear.
125 				 */
126 				readExtension();
127 			} else {
128 				/* The GIF is not to spec, but try to salvage it
129 				 * if we read at least one image. */
130 				if (images.length > 0) break;
131 				SWT.error(SWT.ERROR_INVALID_IMAGE);
132 			}
133 			id = readID(); // block terminator (0)
134 			if (id == 0) id = readID(); // next block ID (unless we just read it)
135 		}
136 		return images;
137 	}
138 
139 	/**
140 	 * Read and return the next block or extension identifier from the file.
141 	 */
readID()142 	int readID() {
143 		try {
144 			return inputStream.read();
145 		} catch (IOException e) {
146 			SWT.error(SWT.ERROR_IO, e);
147 		}
148 		return -1;
149 	}
150 
151 	/**
152 	 * Read a control extension.
153 	 * Return the extension block data.
154 	 */
readExtension()155 	byte[] readExtension() {
156 		int extensionID = readID();
157 		if (extensionID == GIF_COMMENT_BLOCK_ID)
158 			return readCommentExtension();
159 		if (extensionID == GIF_PLAIN_TEXT_BLOCK_ID)
160 			return readPlainTextExtension();
161 		if (extensionID == GIF_GRAPHICS_CONTROL_BLOCK_ID)
162 			return readGraphicsControlExtension();
163 		if (extensionID == GIF_APPLICATION_EXTENSION_BLOCK_ID)
164 			return readApplicationExtension();
165 		// Otherwise, we don't recognize the block. If the
166 		// field size is correct, we can just skip over
167 		// the block contents.
168 		try {
169 			int extSize = inputStream.read();
170 			if (extSize < 0) {
171 				SWT.error(SWT.ERROR_INVALID_IMAGE);
172 			}
173 			byte[] ext = new byte[extSize];
174 			inputStream.read(ext, 0, extSize);
175 			return ext;
176 		} catch (IOException e) {
177 			SWT.error(SWT.ERROR_IO, e);
178 			return null;
179 		}
180 	}
181 
182 	/**
183 	 * We have just read the Comment extension identifier
184 	 * from the input stream. Read in the rest of the comment
185 	 * and return it. GIF comment blocks are variable size.
186 	 */
readCommentExtension()187 	byte[] readCommentExtension() {
188 		try {
189 			byte[] comment = new byte[0];
190 			byte[] block = new byte[255];
191 			int size = inputStream.read();
192 			while ((size > 0) && (inputStream.read(block, 0, size) != -1)) {
193 				byte[] oldComment = comment;
194 				comment = new byte[oldComment.length + size];
195 				System.arraycopy(oldComment, 0, comment, 0, oldComment.length);
196 				System.arraycopy(block, 0, comment, oldComment.length, size);
197 				size = inputStream.read();
198 			}
199 			return comment;
200 		} catch (Exception e) {
201 			SWT.error(SWT.ERROR_IO, e);
202 			return null;
203 		}
204 	}
205 
206 	/**
207 	 * We have just read the PlainText extension identifier
208 	 * from the input stream. Read in the plain text info and text,
209 	 * and return the text. GIF plain text blocks are variable size.
210 	 */
readPlainTextExtension()211 	byte[] readPlainTextExtension() {
212 		try {
213 			// Read size of block = 0x0C.
214 			inputStream.read();
215 			// Read the text information (x, y, width, height, colors).
216 			byte[] info = new byte[12];
217 			inputStream.read(info);
218 			// Read the text.
219 			byte[] text = new byte[0];
220 			byte[] block = new byte[255];
221 			int size = inputStream.read();
222 			while ((size > 0) && (inputStream.read(block, 0, size) != -1)) {
223 				byte[] oldText = text;
224 				text = new byte[oldText.length + size];
225 				System.arraycopy(oldText, 0, text, 0, oldText.length);
226 				System.arraycopy(block, 0, text, oldText.length, size);
227 				size = inputStream.read();
228 			}
229 			return text;
230 		} catch (Exception e) {
231 			SWT.error(SWT.ERROR_IO, e);
232 			return null;
233 		}
234 	}
235 
236 	/**
237 	 * We have just read the GraphicsControl extension identifier
238 	 * from the input stream. Read in the control information, store
239 	 * it, and return it.
240 	 */
readGraphicsControlExtension()241 	byte[] readGraphicsControlExtension() {
242 		try {
243 			// Read size of block = 0x04.
244 			inputStream.read();
245 			// Read the control block.
246 			byte[] controlBlock = new byte[4];
247 			inputStream.read(controlBlock);
248 			byte bitField = controlBlock[0];
249 			// Store the user input field.
250 			//userInput = (bitField & 0x02) != 0;
251 			// Store the disposal method.
252 			disposalMethod = (bitField >> 2) & 0x07;
253 			// Store the delay time.
254 			delayTime = (controlBlock[1] & 0xFF) | ((controlBlock[2] & 0xFF) << 8);
255 			// Store the transparent color.
256 			if ((bitField & 0x01) != 0) {
257 				transparentPixel = controlBlock[3] & 0xFF;
258 			} else {
259 				transparentPixel = -1;
260 			}
261 			return controlBlock;
262 		} catch (Exception e) {
263 			SWT.error(SWT.ERROR_IO, e);
264 			return null;
265 		}
266 	}
267 
268 	/**
269 	 * We have just read the Application extension identifier
270 	 * from the input stream.  Read in the rest of the extension,
271 	 * look for and store 'number of repeats', and return the data.
272 	 */
readApplicationExtension()273 	byte[] readApplicationExtension() {
274 		try {
275 			// Read block data.
276 			int blockSize = inputStream.read();
277 			byte[] blockData = new byte[blockSize];
278 			inputStream.read(blockData);
279 			// Read application data.
280 			byte[] data = new byte[0];
281 			byte[] block = new byte[255];
282 			int size = inputStream.read();
283 			while ((size > 0) && (inputStream.read(block, 0, size) != -1)) {
284 				byte[] oldData = data;
285 				data = new byte[oldData.length + size];
286 				System.arraycopy(oldData, 0, data, 0, oldData.length);
287 				System.arraycopy(block, 0, data, oldData.length, size);
288 				size = inputStream.read();
289 			}
290 			// Look for the NETSCAPE 'repeat count' field for an animated GIF.
291 			boolean netscape =
292 				blockSize > 7 &&
293 				blockData[0] == 'N' &&
294 				blockData[1] == 'E' &&
295 				blockData[2] == 'T' &&
296 				blockData[3] == 'S' &&
297 				blockData[4] == 'C' &&
298 				blockData[5] == 'A' &&
299 				blockData[6] == 'P' &&
300 				blockData[7] == 'E';
301 			boolean authentic =
302 				blockSize > 10 &&
303 				blockData[8] == '2' &&
304 				blockData[9] == '.' &&
305 				blockData[10] == '0';
306 			if (netscape && authentic && data[0] == 01) { //$NON-NLS-1$ //$NON-NLS-2$
307 				repeatCount = (data[1] & 0xFF) | ((data[2] & 0xFF) << 8);
308 				loader.repeatCount = repeatCount;
309 			}
310 			return data;
311 		} catch (Exception e) {
312 			SWT.error(SWT.ERROR_IO, e);
313 			return null;
314 		}
315 	}
316 
317 	/**
318 	 * Return a DeviceIndependentImage representing the
319 	 * image block at the current position in the input stream.
320 	 * Throw an error if an error occurs.
321 	 */
readImageBlock(PaletteData defaultPalette)322 	ImageData readImageBlock(PaletteData defaultPalette) {
323 		int depth;
324 		PaletteData palette;
325 		byte[] block = new byte[9];
326 		try {
327 			inputStream.read(block);
328 		} catch (IOException e) {
329 			SWT.error(SWT.ERROR_IO, e);
330 		}
331 		int left = (block[0] & 0xFF) | ((block[1] & 0xFF) << 8);
332 		int top = (block[2] & 0xFF) | ((block[3] & 0xFF) << 8);
333 		int width = (block[4] & 0xFF) | ((block[5] & 0xFF) << 8);
334 		int height = (block[6] & 0xFF) | ((block[7] & 0xFF) << 8);
335 		byte bitField = block[8];
336 		boolean interlaced = (bitField & 0x40) != 0;
337 		//boolean sorted = (bitField & 0x20) != 0;
338 		if ((bitField & 0x80) != 0) {
339 			// Local palette.
340 			depth = (bitField & 0x7) + 1;
341 			palette = readPalette(1 << depth);
342 		} else {
343 			// No local palette.
344 			depth = defaultDepth;
345 			palette = defaultPalette;
346 		}
347 		/* Work around: Ignore the case where a GIF specifies an
348 		 * invalid index for the transparent pixel that is larger
349 		 * than the number of entries in the palette. */
350 		if (transparentPixel > 1 << depth) {
351 			transparentPixel = -1;
352 		}
353 		// Promote depth to next highest supported value.
354 		if (!(depth == 1 || depth == 4 || depth == 8)) {
355 			if (depth < 4)
356 				depth = 4;
357 			else
358 				depth = 8;
359 		}
360 		if (palette == null) {
361 			palette = grayRamp(1 << depth);
362 		}
363 		int initialCodeSize = -1;
364 		try {
365 			initialCodeSize = inputStream.read();
366 		} catch (IOException e) {
367 			SWT.error(SWT.ERROR_IO, e);
368 		}
369 		if (initialCodeSize < 0) {
370 			SWT.error(SWT.ERROR_INVALID_IMAGE);
371 		}
372 		ImageData image = ImageData.internal_new(
373 			width,
374 			height,
375 			depth,
376 			palette,
377 			4,
378 			null,
379 			0,
380 			null,
381 			null,
382 			-1,
383 			transparentPixel,
384 			SWT.IMAGE_GIF,
385 			left,
386 			top,
387 			disposalMethod,
388 			delayTime);
389 		LZWCodec codec = new LZWCodec();
390 		codec.decode(inputStream, loader, image, interlaced, initialCodeSize);
391 		return image;
392 	}
393 
394 	/**
395 	 * Read a palette from the input stream.
396 	 */
readPalette(int numColors)397 	PaletteData readPalette(int numColors) {
398 		byte[] bytes = new byte[numColors * 3];
399 		try {
400 			if (inputStream.read(bytes) != bytes.length)
401 				SWT.error(SWT.ERROR_INVALID_IMAGE);
402 		} catch (IOException e) {
403 			SWT.error(SWT.ERROR_IO, e);
404 		}
405 		RGB[] colors = new RGB[numColors];
406 		for (int i = 0; i < numColors; i++)
407 			colors[i] = new RGB(bytes[i*3] & 0xFF,
408 				bytes[i*3+1] & 0xFF, bytes[i*3+2] & 0xFF);
409 		return new PaletteData(colors);
410 	}
411 
412 	@Override
unloadIntoByteStream(ImageLoader loader)413 	void unloadIntoByteStream(ImageLoader loader) {
414 
415 		/* Step 1: Acquire GIF parameters. */
416 		ImageData[] data = loader.data;
417 		int frameCount = data.length;
418 		boolean multi = frameCount > 1;
419 		ImageData firstImage = data[0];
420 		int logicalScreenWidth = multi ? loader.logicalScreenWidth : firstImage.width;
421 		int logicalScreenHeight = multi ? loader.logicalScreenHeight : firstImage.height;
422 		int backgroundPixel = loader.backgroundPixel;
423 		int depth = firstImage.depth;
424 		PaletteData palette = firstImage.palette;
425 		RGB[] colors = palette.getRGBs();
426 		short globalTable = 1;
427 
428 		/* Step 2: Check for validity and global/local color map. */
429 		if (!(depth == 1 || depth == 4 || depth == 8)) {
430 			SWT.error(SWT.ERROR_UNSUPPORTED_DEPTH);
431 		}
432 		for (int i=0; i<frameCount; i++) {
433 			if (data[i].palette.isDirect) {
434 				SWT.error(SWT.ERROR_INVALID_IMAGE);
435 			}
436 			if (multi) {
437 				if (!(data[i].height <= logicalScreenHeight && data[i].width <= logicalScreenWidth && data[i].depth == depth)) {
438 					SWT.error(SWT.ERROR_INVALID_IMAGE);
439 				}
440 				if (globalTable == 1) {
441 					RGB rgbs[] = data[i].palette.getRGBs();
442 					if (rgbs.length != colors.length) {
443 						globalTable = 0;
444 					} else {
445 						for (int j=0; j<colors.length; j++) {
446 							if (!(rgbs[j].red == colors[j].red &&
447 								rgbs[j].green == colors[j].green &&
448 								rgbs[j].blue == colors[j].blue))
449 									globalTable = 0;
450 						}
451 					}
452 				}
453 			}
454 		}
455 
456 		try {
457 			/* Step 3: Write the GIF89a Header and Logical Screen Descriptor. */
458 			outputStream.write(GIF89a);
459 			int bitField = globalTable*128 + (depth-1)*16 + depth-1;
460 			outputStream.writeShort((short)logicalScreenWidth);
461 			outputStream.writeShort((short)logicalScreenHeight);
462 			outputStream.write(bitField);
463 			outputStream.write(backgroundPixel);
464 			outputStream.write(0); // Aspect ratio is 1:1
465 		} catch (IOException e) {
466 			SWT.error(SWT.ERROR_IO, e);
467 		}
468 
469 		/* Step 4: Write Global Color Table if applicable. */
470 		if (globalTable == 1) {
471 			writePalette(palette, depth);
472 		}
473 
474 		/* Step 5: Write Application Extension if applicable. */
475 		if (multi) {
476 			int repeatCount = loader.repeatCount;
477 			try {
478 				outputStream.write(GIF_EXTENSION_BLOCK_ID);
479 				outputStream.write(GIF_APPLICATION_EXTENSION_BLOCK_ID);
480 				outputStream.write(NETSCAPE2_0.length);
481 				outputStream.write(NETSCAPE2_0);
482 				outputStream.write(3); // Three bytes follow
483 				outputStream.write(1); // Extension type
484 				outputStream.writeShort((short) repeatCount);
485 				outputStream.write(0); // Block terminator
486 			} catch (IOException e) {
487 				SWT.error(SWT.ERROR_IO, e);
488 			}
489 		}
490 
491 		for (int frame=0; frame<frameCount; frame++) {
492 
493 			/* Step 6: Write Graphics Control Block for each frame if applicable. */
494 			if (multi || data[frame].transparentPixel != -1) {
495 				writeGraphicsControlBlock(data[frame]);
496 			}
497 
498 			/* Step 7: Write Image Header for each frame. */
499 			int x = data[frame].x;
500 			int y = data[frame].y;
501 			int width = data[frame].width;
502 			int height = data[frame].height;
503 			try {
504 				outputStream.write(GIF_IMAGE_BLOCK_ID);
505 				byte[] block = new byte[9];
506 				block[0] = (byte)(x & 0xFF);
507 				block[1] = (byte)((x >> 8) & 0xFF);
508 				block[2] = (byte)(y & 0xFF);
509 				block[3] = (byte)((y >> 8) & 0xFF);
510 				block[4] = (byte)(width & 0xFF);
511 				block[5] = (byte)((width >> 8) & 0xFF);
512 				block[6] = (byte)(height & 0xFF);
513 				block[7] = (byte)((height >> 8) & 0xFF);
514 				block[8] = (byte)(globalTable == 0 ? (depth-1) | 0x80 : 0x00);
515 				outputStream.write(block);
516 			} catch (IOException e) {
517 				SWT.error(SWT.ERROR_IO, e);
518 			}
519 
520 			/* Step 8: Write Local Color Table for each frame if applicable. */
521 			if (globalTable == 0) {
522 				writePalette(data[frame].palette, depth);
523 			}
524 
525 			/* Step 9: Write the actual data for each frame. */
526 			try {
527 				outputStream.write(depth); // Minimum LZW Code size
528 			} catch (IOException e) {
529 				SWT.error(SWT.ERROR_IO, e);
530 			}
531 			new LZWCodec().encode(outputStream, data[frame]);
532 		}
533 
534 		/* Step 10: Write GIF terminator. */
535 		try {
536 			outputStream.write(0x3B);
537 		} catch (IOException e) {
538 			SWT.error(SWT.ERROR_IO, e);
539 		}
540 	}
541 
542 	/**
543 	 * Write out a GraphicsControlBlock to describe
544 	 * the specified device independent image.
545 	 */
writeGraphicsControlBlock(ImageData image)546 	void writeGraphicsControlBlock(ImageData image) {
547 		try {
548 			outputStream.write(GIF_EXTENSION_BLOCK_ID);
549 			outputStream.write(GIF_GRAPHICS_CONTROL_BLOCK_ID);
550 			byte[] gcBlock = new byte[4];
551 			gcBlock[0] = 0;
552 			gcBlock[1] = 0;
553 			gcBlock[2] = 0;
554 			gcBlock[3] = 0;
555 			if (image.transparentPixel != -1) {
556 				gcBlock[0] = (byte)0x01;
557 				gcBlock[3] = (byte)image.transparentPixel;
558 			}
559 			if (image.disposalMethod != 0) {
560 				gcBlock[0] |= (byte)((image.disposalMethod & 0x07) << 2);
561 			}
562 			if (image.delayTime != 0) {
563 				gcBlock[1] = (byte)(image.delayTime & 0xFF);
564 				gcBlock[2] = (byte)((image.delayTime >> 8) & 0xFF);
565 			}
566 			outputStream.write((byte)gcBlock.length);
567 			outputStream.write(gcBlock);
568 			outputStream.write(0); // Block terminator
569 		} catch (IOException e) {
570 			SWT.error(SWT.ERROR_IO, e);
571 		}
572 	}
573 
574 	/**
575 	 * Write the specified palette to the output stream.
576 	 */
writePalette(PaletteData palette, int depth)577 	void writePalette(PaletteData palette, int depth) {
578 		byte[] bytes = new byte[(1 << depth) * 3];
579 		int offset = 0;
580 		for (RGB color : palette.colors) {
581 			bytes[offset] = (byte)color.red;
582 			bytes[offset + 1] = (byte)color.green;
583 			bytes[offset + 2] = (byte)color.blue;
584 			offset += 3;
585 		}
586 		try {
587 			outputStream.write(bytes);
588 		} catch (IOException e) {
589 			SWT.error(SWT.ERROR_IO, e);
590 		}
591 	}
592 }
593