001: /*******************************************************************************
002: * Copyright (c) 2000, 2005 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.swt.internal.image;
011:
012: import org.eclipse.swt.*;
013: import org.eclipse.swt.graphics.*;
014: import java.io.*;
015:
016: final class GIFFileFormat extends FileFormat {
017: String signature;
018: int screenWidth, screenHeight, backgroundPixel, bitsPerPixel,
019: defaultDepth;
020: int disposalMethod = 0;
021: int delayTime = 0;
022: int transparentPixel = -1;
023: int repeatCount = 1;
024:
025: static final int GIF_APPLICATION_EXTENSION_BLOCK_ID = 0xFF;
026: static final int GIF_GRAPHICS_CONTROL_BLOCK_ID = 0xF9;
027: static final int GIF_PLAIN_TEXT_BLOCK_ID = 0x01;
028: static final int GIF_COMMENT_BLOCK_ID = 0xFE;
029: static final int GIF_EXTENSION_BLOCK_ID = 0x21;
030: static final int GIF_IMAGE_BLOCK_ID = 0x2C;
031: static final int GIF_TRAILER_ID = 0x3B;
032: static final byte[] GIF89a = new byte[] { (byte) 'G', (byte) 'I',
033: (byte) 'F', (byte) '8', (byte) '9', (byte) 'a' };
034: static final byte[] NETSCAPE2_0 = new byte[] { (byte) 'N',
035: (byte) 'E', (byte) 'T', (byte) 'S', (byte) 'C', (byte) 'A',
036: (byte) 'P', (byte) 'E', (byte) '2', (byte) '.', (byte) '0' };
037:
038: /**
039: * Answer a palette containing numGrays
040: * shades of gray, ranging from black to white.
041: */
042: static PaletteData grayRamp(int numGrays) {
043: int n = numGrays - 1;
044: RGB[] colors = new RGB[numGrays];
045: for (int i = 0; i < numGrays; i++) {
046: int intensity = (byte) ((i * 3) * 256 / n);
047: colors[i] = new RGB(intensity, intensity, intensity);
048: }
049: return new PaletteData(colors);
050: }
051:
052: boolean isFileFormat(LEDataInputStream stream) {
053: try {
054: byte[] signature = new byte[3];
055: stream.read(signature);
056: stream.unread(signature);
057: return new String(signature).equals("GIF"); //$NON-NLS-1$
058: } catch (Exception e) {
059: return false;
060: }
061: }
062:
063: /**
064: * Load the GIF image(s) stored in the input stream.
065: * Return an array of ImageData representing the image(s).
066: */
067: ImageData[] loadFromByteStream() {
068: byte[] signatureBytes = new byte[3];
069: byte[] versionBytes = new byte[3];
070: byte[] block = new byte[7];
071: try {
072: inputStream.read(signatureBytes);
073: signature = new String(signatureBytes);
074: if (!signature.equals("GIF")) //$NON-NLS-1$
075: SWT.error(SWT.ERROR_INVALID_IMAGE);
076:
077: inputStream.read(versionBytes);
078:
079: inputStream.read(block);
080: } catch (IOException e) {
081: SWT.error(SWT.ERROR_IO, e);
082: }
083: screenWidth = (block[0] & 0xFF) | ((block[1] & 0xFF) << 8);
084: loader.logicalScreenWidth = screenWidth;
085: screenHeight = (block[2] & 0xFF) | ((block[3] & 0xFF) << 8);
086: loader.logicalScreenHeight = screenHeight;
087: byte bitField = block[4];
088: backgroundPixel = block[5] & 0xFF;
089: //aspect = block[6] & 0xFF;
090: bitsPerPixel = ((bitField >> 4) & 0x07) + 1;
091: defaultDepth = (bitField & 0x7) + 1;
092: PaletteData palette = null;
093: if ((bitField & 0x80) != 0) {
094: // Global palette.
095: //sorted = (bitField & 0x8) != 0;
096: palette = readPalette(1 << defaultDepth);
097: } else {
098: // No global palette.
099: //sorted = false;
100: backgroundPixel = -1;
101: defaultDepth = bitsPerPixel;
102: }
103: loader.backgroundPixel = backgroundPixel;
104:
105: getExtensions();
106: int id = readID();
107: ImageData[] images = new ImageData[0];
108: while (id == GIF_IMAGE_BLOCK_ID) {
109: ImageData image = readImageBlock(palette);
110: if (loader.hasListeners()) {
111: loader.notifyListeners(new ImageLoaderEvent(loader,
112: image, 3, true));
113: }
114: ImageData[] oldImages = images;
115: images = new ImageData[oldImages.length + 1];
116: System.arraycopy(oldImages, 0, images, 0, oldImages.length);
117: images[images.length - 1] = image;
118: try {
119: /* Read the 0-byte terminator at the end of the image. */
120: id = inputStream.read();
121: if (id > 0) {
122: /* We read the terminator earlier. */
123: inputStream.unread(new byte[] { (byte) id });
124: }
125: } catch (IOException e) {
126: SWT.error(SWT.ERROR_IO, e);
127: }
128: getExtensions();
129: id = readID();
130: }
131: return images;
132: }
133:
134: /**
135: * Read and return the next block or extension identifier from the file.
136: */
137: int readID() {
138: try {
139: return inputStream.read();
140: } catch (IOException e) {
141: SWT.error(SWT.ERROR_IO, e);
142: }
143: return -1;
144: }
145:
146: /**
147: * Read extensions until an image descriptor appears.
148: * In the future, if we care about the extensions, they
149: * should be properly grouped with the image data before
150: * which they appeared. Right now, the interesting parts
151: * of some extensions are kept, but the rest is discarded.
152: * Throw an error if an error occurs.
153: */
154: void getExtensions() {
155: int id = readID();
156: while (id != GIF_IMAGE_BLOCK_ID && id != GIF_TRAILER_ID
157: && id > 0) {
158: if (id == GIF_EXTENSION_BLOCK_ID) {
159: readExtension();
160: } else {
161: SWT.error(SWT.ERROR_INVALID_IMAGE);
162: }
163: id = readID();
164: }
165: if (id == GIF_IMAGE_BLOCK_ID || id == GIF_TRAILER_ID) {
166: try {
167: inputStream.unread(new byte[] { (byte) id });
168: } catch (IOException e) {
169: SWT.error(SWT.ERROR_IO, e);
170: }
171: }
172: }
173:
174: /**
175: * Read a control extension.
176: * Return the extension block data.
177: */
178: byte[] readExtension() {
179: int extensionID = readID();
180: if (extensionID == GIF_COMMENT_BLOCK_ID)
181: return readCommentExtension();
182: if (extensionID == GIF_PLAIN_TEXT_BLOCK_ID)
183: return readPlainTextExtension();
184: if (extensionID == GIF_GRAPHICS_CONTROL_BLOCK_ID)
185: return readGraphicsControlExtension();
186: if (extensionID == GIF_APPLICATION_EXTENSION_BLOCK_ID)
187: return readApplicationExtension();
188: // Otherwise, we don't recognize the block. If the
189: // field size is correct, we can just skip over
190: // the block contents.
191: try {
192: int extSize = inputStream.read();
193: if (extSize < 0) {
194: SWT.error(SWT.ERROR_INVALID_IMAGE);
195: }
196: byte[] ext = new byte[extSize];
197: inputStream.read(ext, 0, extSize);
198: return ext;
199: } catch (IOException e) {
200: SWT.error(SWT.ERROR_IO, e);
201: return null;
202: }
203: }
204:
205: /**
206: * We have just read the Comment extension identifier
207: * from the input stream. Read in the rest of the comment
208: * and return it. GIF comment blocks are variable size.
209: */
210: byte[] readCommentExtension() {
211: try {
212: byte[] comment = new byte[0];
213: byte[] block = new byte[255];
214: int size = inputStream.read();
215: while ((size > 0)
216: && (inputStream.read(block, 0, size) != -1)) {
217: byte[] oldComment = comment;
218: comment = new byte[oldComment.length + size];
219: System.arraycopy(oldComment, 0, comment, 0,
220: oldComment.length);
221: System.arraycopy(block, 0, comment, oldComment.length,
222: size);
223: size = inputStream.read();
224: }
225: return comment;
226: } catch (Exception e) {
227: SWT.error(SWT.ERROR_IO, e);
228: return null;
229: }
230: }
231:
232: /**
233: * We have just read the PlainText extension identifier
234: * from the input stream. Read in the plain text info and text,
235: * and return the text. GIF plain text blocks are variable size.
236: */
237: byte[] readPlainTextExtension() {
238: try {
239: // Read size of block = 0x0C.
240: inputStream.read();
241: // Read the text information (x, y, width, height, colors).
242: byte[] info = new byte[12];
243: inputStream.read(info);
244: // Read the text.
245: byte[] text = new byte[0];
246: byte[] block = new byte[255];
247: int size = inputStream.read();
248: while ((size > 0)
249: && (inputStream.read(block, 0, size) != -1)) {
250: byte[] oldText = text;
251: text = new byte[oldText.length + size];
252: System.arraycopy(oldText, 0, text, 0, oldText.length);
253: System.arraycopy(block, 0, text, oldText.length, size);
254: size = inputStream.read();
255: }
256: return text;
257: } catch (Exception e) {
258: SWT.error(SWT.ERROR_IO, e);
259: return null;
260: }
261: }
262:
263: /**
264: * We have just read the GraphicsControl extension identifier
265: * from the input stream. Read in the control information, store
266: * it, and return it.
267: */
268: byte[] readGraphicsControlExtension() {
269: try {
270: // Read size of block = 0x04.
271: inputStream.read();
272: // Read the control block.
273: byte[] controlBlock = new byte[4];
274: inputStream.read(controlBlock);
275: byte bitField = controlBlock[0];
276: // Store the user input field.
277: //userInput = (bitField & 0x02) != 0;
278: // Store the disposal method.
279: disposalMethod = (bitField >> 2) & 0x07;
280: // Store the delay time.
281: delayTime = (controlBlock[1] & 0xFF)
282: | ((controlBlock[2] & 0xFF) << 8);
283: // Store the transparent color.
284: if ((bitField & 0x01) != 0) {
285: transparentPixel = controlBlock[3] & 0xFF;
286: } else {
287: transparentPixel = -1;
288: }
289: // Read block terminator.
290: inputStream.read();
291: return controlBlock;
292: } catch (Exception e) {
293: SWT.error(SWT.ERROR_IO, e);
294: return null;
295: }
296: }
297:
298: /**
299: * We have just read the Application extension identifier
300: * from the input stream. Read in the rest of the extension,
301: * look for and store 'number of repeats', and return the data.
302: */
303: byte[] readApplicationExtension() {
304: try {
305: // Read size of block = 0x0B.
306: inputStream.read();
307: // Read application identifier.
308: byte[] applicationBytes = new byte[8];
309: inputStream.read(applicationBytes);
310: String application = new String(applicationBytes);
311: // Read authentication code.
312: byte[] authenticationBytes = new byte[3];
313: inputStream.read(authenticationBytes);
314: String authentication = new String(authenticationBytes);
315: // Read application data.
316: byte[] data = new byte[0];
317: byte[] block = new byte[255];
318: int size = inputStream.read();
319: while ((size > 0)
320: && (inputStream.read(block, 0, size) != -1)) {
321: byte[] oldData = data;
322: data = new byte[oldData.length + size];
323: System.arraycopy(oldData, 0, data, 0, oldData.length);
324: System.arraycopy(block, 0, data, oldData.length, size);
325: size = inputStream.read();
326: }
327: // Look for the NETSCAPE 'repeat count' field for an animated GIF.
328: if (application.equals("NETSCAPE") && authentication.equals("2.0") && data[0] == 01) { //$NON-NLS-1$ //$NON-NLS-2$
329: repeatCount = (data[1] & 0xFF)
330: | ((data[2] & 0xFF) << 8);
331: loader.repeatCount = repeatCount;
332: }
333: return data;
334: } catch (Exception e) {
335: SWT.error(SWT.ERROR_IO, e);
336: return null;
337: }
338: }
339:
340: /**
341: * Return a DeviceIndependentImage representing the
342: * image block at the current position in the input stream.
343: * Throw an error if an error occurs.
344: */
345: ImageData readImageBlock(PaletteData defaultPalette) {
346: int depth;
347: PaletteData palette;
348: byte[] block = new byte[9];
349: try {
350: inputStream.read(block);
351: } catch (IOException e) {
352: SWT.error(SWT.ERROR_IO, e);
353: }
354: int left = (block[0] & 0xFF) | ((block[1] & 0xFF) << 8);
355: int top = (block[2] & 0xFF) | ((block[3] & 0xFF) << 8);
356: int width = (block[4] & 0xFF) | ((block[5] & 0xFF) << 8);
357: int height = (block[6] & 0xFF) | ((block[7] & 0xFF) << 8);
358: byte bitField = block[8];
359: boolean interlaced = (bitField & 0x40) != 0;
360: //boolean sorted = (bitField & 0x20) != 0;
361: if ((bitField & 0x80) != 0) {
362: // Local palette.
363: depth = (bitField & 0x7) + 1;
364: palette = readPalette(1 << depth);
365: } else {
366: // No local palette.
367: depth = defaultDepth;
368: palette = defaultPalette;
369: }
370: /* Work around: Ignore the case where a GIF specifies an
371: * invalid index for the transparent pixel that is larger
372: * than the number of entries in the palette. */
373: if (transparentPixel > 1 << depth) {
374: transparentPixel = -1;
375: }
376: // Promote depth to next highest supported value.
377: if (!(depth == 1 || depth == 4 || depth == 8)) {
378: if (depth < 4)
379: depth = 4;
380: else
381: depth = 8;
382: }
383: if (palette == null) {
384: palette = grayRamp(1 << depth);
385: }
386: int initialCodeSize = -1;
387: try {
388: initialCodeSize = inputStream.read();
389: } catch (IOException e) {
390: SWT.error(SWT.ERROR_IO, e);
391: }
392: if (initialCodeSize < 0) {
393: SWT.error(SWT.ERROR_INVALID_IMAGE);
394: }
395: ImageData image = ImageData.internal_new(width, height, depth,
396: palette, 4, null, 0, null, null, -1, transparentPixel,
397: SWT.IMAGE_GIF, left, top, disposalMethod, delayTime);
398: LZWCodec codec = new LZWCodec();
399: codec.decode(inputStream, loader, image, interlaced,
400: initialCodeSize);
401: return image;
402: }
403:
404: /**
405: * Read a palette from the input stream.
406: */
407: PaletteData readPalette(int numColors) {
408: byte[] bytes = new byte[numColors * 3];
409: try {
410: if (inputStream.read(bytes) != bytes.length)
411: SWT.error(SWT.ERROR_INVALID_IMAGE);
412: } catch (IOException e) {
413: SWT.error(SWT.ERROR_IO, e);
414: }
415: RGB[] colors = new RGB[numColors];
416: for (int i = 0; i < numColors; i++)
417: colors[i] = new RGB(bytes[i * 3] & 0xFF,
418: bytes[i * 3 + 1] & 0xFF, bytes[i * 3 + 2] & 0xFF);
419: return new PaletteData(colors);
420: }
421:
422: void unloadIntoByteStream(ImageLoader loader) {
423:
424: /* Step 1: Acquire GIF parameters. */
425: ImageData[] data = loader.data;
426: int frameCount = data.length;
427: boolean multi = frameCount > 1;
428: ImageData firstImage = data[0];
429: int logicalScreenWidth = multi ? loader.logicalScreenWidth
430: : firstImage.width;
431: int logicalScreenHeight = multi ? loader.logicalScreenHeight
432: : firstImage.height;
433: int backgroundPixel = loader.backgroundPixel;
434: int depth = firstImage.depth;
435: PaletteData palette = firstImage.palette;
436: RGB[] colors = palette.getRGBs();
437: short globalTable = 1;
438:
439: /* Step 2: Check for validity and global/local color map. */
440: if (!(depth == 1 || depth == 4 || depth == 8)) {
441: SWT.error(SWT.ERROR_UNSUPPORTED_DEPTH);
442: }
443: for (int i = 0; i < frameCount; i++) {
444: if (data[i].palette.isDirect) {
445: SWT.error(SWT.ERROR_INVALID_IMAGE);
446: }
447: if (multi) {
448: if (!(data[i].height <= logicalScreenHeight
449: && data[i].width <= logicalScreenWidth && data[i].depth == depth)) {
450: SWT.error(SWT.ERROR_INVALID_IMAGE);
451: }
452: if (globalTable == 1) {
453: RGB rgbs[] = data[i].palette.getRGBs();
454: if (rgbs.length != colors.length) {
455: globalTable = 0;
456: } else {
457: for (int j = 0; j < colors.length; j++) {
458: if (!(rgbs[j].red == colors[j].red
459: && rgbs[j].green == colors[j].green && rgbs[j].blue == colors[j].blue))
460: globalTable = 0;
461: }
462: }
463: }
464: }
465: }
466:
467: try {
468: /* Step 3: Write the GIF89a Header and Logical Screen Descriptor. */
469: outputStream.write(GIF89a);
470: int bitField = globalTable * 128 + (depth - 1) * 16 + depth
471: - 1;
472: outputStream.writeShort((short) logicalScreenWidth);
473: outputStream.writeShort((short) logicalScreenHeight);
474: outputStream.write(bitField);
475: outputStream.write(backgroundPixel);
476: outputStream.write(0); // Aspect ratio is 1:1
477: } catch (IOException e) {
478: SWT.error(SWT.ERROR_IO, e);
479: }
480:
481: /* Step 4: Write Global Color Table if applicable. */
482: if (globalTable == 1) {
483: writePalette(palette, depth);
484: }
485:
486: /* Step 5: Write Application Extension if applicable. */
487: if (multi) {
488: int repeatCount = loader.repeatCount;
489: try {
490: outputStream.write(GIF_EXTENSION_BLOCK_ID);
491: outputStream.write(GIF_APPLICATION_EXTENSION_BLOCK_ID);
492: outputStream.write(NETSCAPE2_0.length);
493: outputStream.write(NETSCAPE2_0);
494: outputStream.write(3); // Three bytes follow
495: outputStream.write(1); // Extension type
496: outputStream.writeShort((short) repeatCount);
497: outputStream.write(0); // Block terminator
498: } catch (IOException e) {
499: SWT.error(SWT.ERROR_IO, e);
500: }
501: }
502:
503: for (int frame = 0; frame < frameCount; frame++) {
504:
505: /* Step 6: Write Graphics Control Block for each frame if applicable. */
506: if (multi || data[frame].transparentPixel != -1) {
507: writeGraphicsControlBlock(data[frame]);
508: }
509:
510: /* Step 7: Write Image Header for each frame. */
511: int x = data[frame].x;
512: int y = data[frame].y;
513: int width = data[frame].width;
514: int height = data[frame].height;
515: try {
516: outputStream.write(GIF_IMAGE_BLOCK_ID);
517: byte[] block = new byte[9];
518: block[0] = (byte) (x & 0xFF);
519: block[1] = (byte) ((x >> 8) & 0xFF);
520: block[2] = (byte) (y & 0xFF);
521: block[3] = (byte) ((y >> 8) & 0xFF);
522: block[4] = (byte) (width & 0xFF);
523: block[5] = (byte) ((width >> 8) & 0xFF);
524: block[6] = (byte) (height & 0xFF);
525: block[7] = (byte) ((height >> 8) & 0xFF);
526: block[8] = (byte) (globalTable == 0 ? (depth - 1) | 0x80
527: : 0x00);
528: outputStream.write(block);
529: } catch (IOException e) {
530: SWT.error(SWT.ERROR_IO, e);
531: }
532:
533: /* Step 8: Write Local Color Table for each frame if applicable. */
534: if (globalTable == 0) {
535: writePalette(data[frame].palette, depth);
536: }
537:
538: /* Step 9: Write the actual data for each frame. */
539: try {
540: outputStream.write(depth); // Minimum LZW Code size
541: } catch (IOException e) {
542: SWT.error(SWT.ERROR_IO, e);
543: }
544: new LZWCodec().encode(outputStream, data[frame]);
545: }
546:
547: /* Step 10: Write GIF terminator. */
548: try {
549: outputStream.write(0x3B);
550: } catch (IOException e) {
551: SWT.error(SWT.ERROR_IO, e);
552: }
553: }
554:
555: /**
556: * Write out a GraphicsControlBlock to describe
557: * the specified device independent image.
558: */
559: void writeGraphicsControlBlock(ImageData image) {
560: try {
561: outputStream.write(GIF_EXTENSION_BLOCK_ID);
562: outputStream.write(GIF_GRAPHICS_CONTROL_BLOCK_ID);
563: byte[] gcBlock = new byte[4];
564: gcBlock[0] = 0;
565: gcBlock[1] = 0;
566: gcBlock[2] = 0;
567: gcBlock[3] = 0;
568: if (image.transparentPixel != -1) {
569: gcBlock[0] = (byte) 0x01;
570: gcBlock[3] = (byte) image.transparentPixel;
571: }
572: if (image.disposalMethod != 0) {
573: gcBlock[0] |= (byte) ((image.disposalMethod & 0x07) << 2);
574: }
575: if (image.delayTime != 0) {
576: gcBlock[1] = (byte) (image.delayTime & 0xFF);
577: gcBlock[2] = (byte) ((image.delayTime >> 8) & 0xFF);
578: }
579: outputStream.write((byte) gcBlock.length);
580: outputStream.write(gcBlock);
581: outputStream.write(0); // Block terminator
582: } catch (IOException e) {
583: SWT.error(SWT.ERROR_IO, e);
584: }
585: }
586:
587: /**
588: * Write the specified palette to the output stream.
589: */
590: void writePalette(PaletteData palette, int depth) {
591: byte[] bytes = new byte[(1 << depth) * 3];
592: int offset = 0;
593: for (int i = 0; i < palette.colors.length; i++) {
594: RGB color = palette.colors[i];
595: bytes[offset] = (byte) color.red;
596: bytes[offset + 1] = (byte) color.green;
597: bytes[offset + 2] = (byte) color.blue;
598: offset += 3;
599: }
600: try {
601: outputStream.write(bytes);
602: } catch (IOException e) {
603: SWT.error(SWT.ERROR_IO, e);
604: }
605: }
606: }
|