KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > swt > internal > image > GIFFileFormat


1 /*******************************************************************************
2  * Copyright (c) 2000, 2005 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.swt.internal.image;
12
13
14 import org.eclipse.swt.*;
15 import org.eclipse.swt.graphics.*;
16 import java.io.*;
17
18 final class GIFFileFormat extends FileFormat {
19     String JavaDoc signature;
20     int screenWidth, screenHeight, backgroundPixel, bitsPerPixel, defaultDepth;
21     int disposalMethod = 0;
22     int delayTime = 0;
23     int transparentPixel = -1;
24     int repeatCount = 1;
25     
26     static final int GIF_APPLICATION_EXTENSION_BLOCK_ID = 0xFF;
27     static final int GIF_GRAPHICS_CONTROL_BLOCK_ID = 0xF9;
28     static final int GIF_PLAIN_TEXT_BLOCK_ID = 0x01;
29     static final int GIF_COMMENT_BLOCK_ID = 0xFE;
30     static final int GIF_EXTENSION_BLOCK_ID = 0x21;
31     static final int GIF_IMAGE_BLOCK_ID = 0x2C;
32     static final int GIF_TRAILER_ID = 0x3B;
33     static final byte [] GIF89a = new byte[] { (byte)'G', (byte)'I', (byte)'F', (byte)'8', (byte)'9', (byte)'a' };
34     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' };
35     
36     /**
37      * Answer a palette containing numGrays
38      * shades of gray, ranging from black to white.
39      */

40     static PaletteData grayRamp(int numGrays) {
41         int n = numGrays - 1;
42         RGB[] colors = new RGB[numGrays];
43         for (int i = 0; i < numGrays; i++) {
44             int intensity = (byte)((i * 3) * 256 / n);
45             colors[i] = new RGB(intensity, intensity, intensity);
46         }
47         return new PaletteData(colors);
48     }
49
50     boolean isFileFormat(LEDataInputStream stream) {
51         try {
52             byte[] signature = new byte[3];
53             stream.read(signature);
54             stream.unread(signature);
55             return new String JavaDoc(signature).equals("GIF"); //$NON-NLS-1$
56
} catch (Exception JavaDoc e) {
57             return false;
58         }
59     }
60
61     /**
62      * Load the GIF image(s) stored in the input stream.
63      * Return an array of ImageData representing the image(s).
64      */

65     ImageData[] loadFromByteStream() {
66         byte[] signatureBytes = new byte[3];
67         byte[] versionBytes = new byte[3];
68         byte[] block = new byte[7];
69         try {
70             inputStream.read(signatureBytes);
71             signature = new String JavaDoc(signatureBytes);
72             if (!signature.equals("GIF")) //$NON-NLS-1$
73
SWT.error(SWT.ERROR_INVALID_IMAGE);
74
75             inputStream.read(versionBytes);
76
77             inputStream.read(block);
78         } catch (IOException e) {
79             SWT.error(SWT.ERROR_IO, e);
80         }
81         screenWidth = (block[0] & 0xFF) | ((block[1] & 0xFF) << 8);
82         loader.logicalScreenWidth = screenWidth;
83         screenHeight = (block[2] & 0xFF) | ((block[3] & 0xFF) << 8);
84         loader.logicalScreenHeight = screenHeight;
85         byte bitField = block[4];
86         backgroundPixel = block[5] & 0xFF;
87         //aspect = block[6] & 0xFF;
88
bitsPerPixel = ((bitField >> 4) & 0x07) + 1;
89         defaultDepth = (bitField & 0x7) + 1;
90         PaletteData palette = null;
91         if ((bitField & 0x80) != 0) {
92             // Global palette.
93
//sorted = (bitField & 0x8) != 0;
94
palette = readPalette(1 << defaultDepth);
95         } else {
96             // No global palette.
97
//sorted = false;
98
backgroundPixel = -1;
99             defaultDepth = bitsPerPixel;
100         }
101         loader.backgroundPixel = backgroundPixel;
102
103         getExtensions();
104         int id = readID();
105         ImageData[] images = new ImageData[0];
106         while (id == GIF_IMAGE_BLOCK_ID) {
107             ImageData image = readImageBlock(palette);
108             if (loader.hasListeners()) {
109                 loader.notifyListeners(new ImageLoaderEvent(loader, image, 3, true));
110             }
111             ImageData[] oldImages = images;
112             images = new ImageData[oldImages.length + 1];
113             System.arraycopy(oldImages, 0, images, 0, oldImages.length);
114             images[images.length - 1] = image;
115             try {
116                 /* Read the 0-byte terminator at the end of the image. */
117                 id = inputStream.read();
118                 if (id > 0) {
119                     /* We read the terminator earlier. */
120                     inputStream.unread(new byte[] {(byte)id});
121                 }
122             } catch (IOException e) {
123                 SWT.error(SWT.ERROR_IO, e);
124             }
125             getExtensions();
126             id = readID();
127         }
128         return images;
129     }
130
131     /**
132      * Read and return the next block or extension identifier from the file.
133      */

134     int readID() {
135         try {
136             return inputStream.read();
137         } catch (IOException e) {
138             SWT.error(SWT.ERROR_IO, e);
139         }
140         return -1;
141     }
142
143     /**
144      * Read extensions until an image descriptor appears.
145      * In the future, if we care about the extensions, they
146      * should be properly grouped with the image data before
147      * which they appeared. Right now, the interesting parts
148      * of some extensions are kept, but the rest is discarded.
149      * Throw an error if an error occurs.
150      */

151     void getExtensions() {
152         int id = readID();
153         while (id != GIF_IMAGE_BLOCK_ID && id != GIF_TRAILER_ID && id > 0) {
154             if (id == GIF_EXTENSION_BLOCK_ID) {
155                 readExtension();
156             } else {
157                 SWT.error(SWT.ERROR_INVALID_IMAGE);
158             }
159             id = readID();
160         }
161         if (id == GIF_IMAGE_BLOCK_ID || id == GIF_TRAILER_ID) {
162             try {
163                 inputStream.unread(new byte[] {(byte)id});
164             } catch (IOException e) {
165                 SWT.error(SWT.ERROR_IO, e);
166             }
167         }
168     }
169
170     /**
171      * Read a control extension.
172      * Return the extension block data.
173      */

174     byte[] readExtension() {
175         int extensionID = readID();
176         if (extensionID == GIF_COMMENT_BLOCK_ID)
177             return readCommentExtension();
178         if (extensionID == GIF_PLAIN_TEXT_BLOCK_ID)
179             return readPlainTextExtension();
180         if (extensionID == GIF_GRAPHICS_CONTROL_BLOCK_ID)
181             return readGraphicsControlExtension();
182         if (extensionID == GIF_APPLICATION_EXTENSION_BLOCK_ID)
183             return readApplicationExtension();
184         // Otherwise, we don't recognize the block. If the
185
// field size is correct, we can just skip over
186
// the block contents.
187
try {
188             int extSize = inputStream.read();
189             if (extSize < 0) {
190                 SWT.error(SWT.ERROR_INVALID_IMAGE);
191             }
192             byte[] ext = new byte[extSize];
193             inputStream.read(ext, 0, extSize);
194             return ext;
195         } catch (IOException e) {
196             SWT.error(SWT.ERROR_IO, e);
197             return null;
198         }
199     }
200
201     /**
202      * We have just read the Comment extension identifier
203      * from the input stream. Read in the rest of the comment
204      * and return it. GIF comment blocks are variable size.
205      */

206     byte[] readCommentExtension() {
207         try {
208             byte[] comment = new byte[0];
209             byte[] block = new byte[255];
210             int size = inputStream.read();
211             while ((size > 0) && (inputStream.read(block, 0, size) != -1)) {
212                 byte[] oldComment = comment;
213                 comment = new byte[oldComment.length + size];
214                 System.arraycopy(oldComment, 0, comment, 0, oldComment.length);
215                 System.arraycopy(block, 0, comment, oldComment.length, size);
216                 size = inputStream.read();
217             }
218             return comment;
219         } catch (Exception JavaDoc e) {
220             SWT.error(SWT.ERROR_IO, e);
221             return null;
222         }
223     }
224
225     /**
226      * We have just read the PlainText extension identifier
227      * from the input stream. Read in the plain text info and text,
228      * and return the text. GIF plain text blocks are variable size.
229      */

230     byte[] readPlainTextExtension() {
231         try {
232             // Read size of block = 0x0C.
233
inputStream.read();
234             // Read the text information (x, y, width, height, colors).
235
byte[] info = new byte[12];
236             inputStream.read(info);
237             // Read the text.
238
byte[] text = new byte[0];
239             byte[] block = new byte[255];
240             int size = inputStream.read();
241             while ((size > 0) && (inputStream.read(block, 0, size) != -1)) {
242                 byte[] oldText = text;
243                 text = new byte[oldText.length + size];
244                 System.arraycopy(oldText, 0, text, 0, oldText.length);
245                 System.arraycopy(block, 0, text, oldText.length, size);
246                 size = inputStream.read();
247             }
248             return text;
249         } catch (Exception JavaDoc e) {
250             SWT.error(SWT.ERROR_IO, e);
251             return null;
252         }
253     }
254
255     /**
256      * We have just read the GraphicsControl extension identifier
257      * from the input stream. Read in the control information, store
258      * it, and return it.
259      */

260     byte[] readGraphicsControlExtension() {
261         try {
262             // Read size of block = 0x04.
263
inputStream.read();
264             // Read the control block.
265
byte[] controlBlock = new byte[4];
266             inputStream.read(controlBlock);
267             byte bitField = controlBlock[0];
268             // Store the user input field.
269
//userInput = (bitField & 0x02) != 0;
270
// Store the disposal method.
271
disposalMethod = (bitField >> 2) & 0x07;
272             // Store the delay time.
273
delayTime = (controlBlock[1] & 0xFF) | ((controlBlock[2] & 0xFF) << 8);
274             // Store the transparent color.
275
if ((bitField & 0x01) != 0) {
276                 transparentPixel = controlBlock[3] & 0xFF;
277             } else {
278                 transparentPixel = -1;
279             }
280             // Read block terminator.
281
inputStream.read();
282             return controlBlock;
283         } catch (Exception JavaDoc e) {
284             SWT.error(SWT.ERROR_IO, e);
285             return null;
286         }
287     }
288
289     /**
290      * We have just read the Application extension identifier
291      * from the input stream. Read in the rest of the extension,
292      * look for and store 'number of repeats', and return the data.
293      */

294     byte[] readApplicationExtension() {
295         try {
296             // Read size of block = 0x0B.
297
inputStream.read();
298             // Read application identifier.
299
byte[] applicationBytes = new byte[8];
300             inputStream.read(applicationBytes);
301             String JavaDoc application = new String JavaDoc(applicationBytes);
302             // Read authentication code.
303
byte[] authenticationBytes = new byte[3];
304             inputStream.read(authenticationBytes);
305             String JavaDoc authentication = new String JavaDoc(authenticationBytes);
306             // Read application data.
307
byte[] data = new byte[0];
308             byte[] block = new byte[255];
309             int size = inputStream.read();
310             while ((size > 0) && (inputStream.read(block, 0, size) != -1)) {
311                 byte[] oldData = data;
312                 data = new byte[oldData.length + size];
313                 System.arraycopy(oldData, 0, data, 0, oldData.length);
314                 System.arraycopy(block, 0, data, oldData.length, size);
315                 size = inputStream.read();
316             }
317             // Look for the NETSCAPE 'repeat count' field for an animated GIF.
318
if (application.equals("NETSCAPE") && authentication.equals("2.0") && data[0] == 01) { //$NON-NLS-1$ //$NON-NLS-2$
319
repeatCount = (data[1] & 0xFF) | ((data[2] & 0xFF) << 8);
320                 loader.repeatCount = repeatCount;
321             }
322             return data;
323         } catch (Exception JavaDoc e) {
324             SWT.error(SWT.ERROR_IO, e);
325             return null;
326         }
327     }
328
329     /**
330      * Return a DeviceIndependentImage representing the
331      * image block at the current position in the input stream.
332      * Throw an error if an error occurs.
333      */

334     ImageData readImageBlock(PaletteData defaultPalette) {
335         int depth;
336         PaletteData palette;
337         byte[] block = new byte[9];
338         try {
339             inputStream.read(block);
340         } catch (IOException e) {
341             SWT.error(SWT.ERROR_IO, e);
342         }
343         int left = (block[0] & 0xFF) | ((block[1] & 0xFF) << 8);
344         int top = (block[2] & 0xFF) | ((block[3] & 0xFF) << 8);
345         int width = (block[4] & 0xFF) | ((block[5] & 0xFF) << 8);
346         int height = (block[6] & 0xFF) | ((block[7] & 0xFF) << 8);
347         byte bitField = block[8];
348         boolean interlaced = (bitField & 0x40) != 0;
349         //boolean sorted = (bitField & 0x20) != 0;
350
if ((bitField & 0x80) != 0) {
351             // Local palette.
352
depth = (bitField & 0x7) + 1;
353             palette = readPalette(1 << depth);
354         } else {
355             // No local palette.
356
depth = defaultDepth;
357             palette = defaultPalette;
358         }
359         /* Work around: Ignore the case where a GIF specifies an
360          * invalid index for the transparent pixel that is larger
361          * than the number of entries in the palette. */

362         if (transparentPixel > 1 << depth) {
363             transparentPixel = -1;
364         }
365         // Promote depth to next highest supported value.
366
if (!(depth == 1 || depth == 4 || depth == 8)) {
367             if (depth < 4)
368                 depth = 4;
369             else
370                 depth = 8;
371         }
372         if (palette == null) {
373             palette = grayRamp(1 << depth);
374         }
375         int initialCodeSize = -1;
376         try {
377             initialCodeSize = inputStream.read();
378         } catch (IOException e) {
379             SWT.error(SWT.ERROR_IO, e);
380         }
381         if (initialCodeSize < 0) {
382             SWT.error(SWT.ERROR_INVALID_IMAGE);
383         }
384         ImageData image = ImageData.internal_new(
385             width,
386             height,
387             depth,
388             palette,
389             4,
390             null,
391             0,
392             null,
393             null,
394             -1,
395             transparentPixel,
396             SWT.IMAGE_GIF,
397             left,
398             top,
399             disposalMethod,
400             delayTime);
401         LZWCodec codec = new LZWCodec();
402         codec.decode(inputStream, loader, image, interlaced, initialCodeSize);
403         return image;
404     }
405
406     /**
407      * Read a palette from the input stream.
408      */

409     PaletteData readPalette(int numColors) {
410         byte[] bytes = new byte[numColors * 3];
411         try {
412             if (inputStream.read(bytes) != bytes.length)
413                 SWT.error(SWT.ERROR_INVALID_IMAGE);
414         } catch (IOException e) {
415             SWT.error(SWT.ERROR_IO, e);
416         }
417         RGB[] colors = new RGB[numColors];
418         for (int i = 0; i < numColors; i++)
419             colors[i] = new RGB(bytes[i*3] & 0xFF,
420                 bytes[i*3+1] & 0xFF, bytes[i*3+2] & 0xFF);
421         return new PaletteData(colors);
422     }
423
424     void unloadIntoByteStream(ImageLoader loader) {
425         
426         /* Step 1: Acquire GIF parameters. */
427         ImageData[] data = loader.data;
428         int frameCount = data.length;
429         boolean multi = frameCount > 1;
430         ImageData firstImage = data[0];
431         int logicalScreenWidth = multi ? loader.logicalScreenWidth : firstImage.width;
432         int logicalScreenHeight = multi ? loader.logicalScreenHeight : 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 && data[i].width <= logicalScreenWidth && data[i].depth == depth)) {
449                     SWT.error(SWT.ERROR_INVALID_IMAGE);
450                 }
451                 if (globalTable == 1) {
452                     RGB rgbs[] = data[i].palette.getRGBs();
453                     if (rgbs.length != colors.length) {
454                         globalTable = 0;
455                     } else {
456                         for (int j=0; j<colors.length; j++) {
457                             if (!(rgbs[j].red == colors[j].red &&
458                                 rgbs[j].green == colors[j].green &&
459                                 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-1;
471             outputStream.writeShort((short)logicalScreenWidth);
472             outputStream.writeShort((short)logicalScreenHeight);
473             outputStream.write(bitField);
474             outputStream.write(backgroundPixel);
475             outputStream.write(0); // Aspect ratio is 1:1
476
} catch (IOException e) {
477             SWT.error(SWT.ERROR_IO, e);
478         }
479         
480         /* Step 4: Write Global Color Table if applicable. */
481         if (globalTable == 1) {
482             writePalette(palette, depth);
483         }
484
485         /* Step 5: Write Application Extension if applicable. */
486         if (multi) {
487             int repeatCount = loader.repeatCount;
488             try {
489                 outputStream.write(GIF_EXTENSION_BLOCK_ID);
490                 outputStream.write(GIF_APPLICATION_EXTENSION_BLOCK_ID);
491                 outputStream.write(NETSCAPE2_0.length);
492                 outputStream.write(NETSCAPE2_0);
493                 outputStream.write(3); // Three bytes follow
494
outputStream.write(1); // Extension type
495
outputStream.writeShort((short) repeatCount);
496                 outputStream.write(0); // Block terminator
497
} catch (IOException e) {
498                 SWT.error(SWT.ERROR_IO, e);
499             }
500         }
501         
502         for (int frame=0; frame<frameCount; frame++) {
503             
504             /* Step 6: Write Graphics Control Block for each frame if applicable. */
505             if (multi || data[frame].transparentPixel != -1) {
506                 writeGraphicsControlBlock(data[frame]);
507             }
508             
509             /* Step 7: Write Image Header for each frame. */
510             int x = data[frame].x;
511             int y = data[frame].y;
512             int width = data[frame].width;
513             int height = data[frame].height;
514             try {
515                 outputStream.write(GIF_IMAGE_BLOCK_ID);
516                 byte[] block = new byte[9];
517                 block[0] = (byte)(x & 0xFF);
518                 block[1] = (byte)((x >> 8) & 0xFF);
519                 block[2] = (byte)(y & 0xFF);
520                 block[3] = (byte)((y >> 8) & 0xFF);
521                 block[4] = (byte)(width & 0xFF);
522                 block[5] = (byte)((width >> 8) & 0xFF);
523                 block[6] = (byte)(height & 0xFF);
524                 block[7] = (byte)((height >> 8) & 0xFF);
525                 block[8] = (byte)(globalTable == 0 ? (depth-1) | 0x80 : 0x00);
526                 outputStream.write(block);
527             } catch (IOException e) {
528                 SWT.error(SWT.ERROR_IO, e);
529             }
530             
531             /* Step 8: Write Local Color Table for each frame if applicable. */
532             if (globalTable == 0) {
533                 writePalette(data[frame].palette, depth);
534             }
535             
536             /* Step 9: Write the actual data for each frame. */
537             try {
538                 outputStream.write(depth); // Minimum LZW Code size
539
} catch (IOException e) {
540                 SWT.error(SWT.ERROR_IO, e);
541             }
542             new LZWCodec().encode(outputStream, data[frame]);
543         }
544
545         /* Step 10: Write GIF terminator. */
546         try {
547             outputStream.write(0x3B);
548         } catch (IOException e) {
549             SWT.error(SWT.ERROR_IO, e);
550         }
551     }
552
553     /**
554      * Write out a GraphicsControlBlock to describe
555      * the specified device independent image.
556      */

557     void writeGraphicsControlBlock(ImageData image) {
558         try {
559             outputStream.write(GIF_EXTENSION_BLOCK_ID);
560             outputStream.write(GIF_GRAPHICS_CONTROL_BLOCK_ID);
561             byte[] gcBlock = new byte[4];
562             gcBlock[0] = 0;
563             gcBlock[1] = 0;
564             gcBlock[2] = 0;
565             gcBlock[3] = 0;
566             if (image.transparentPixel != -1) {
567                 gcBlock[0] = (byte)0x01;
568                 gcBlock[3] = (byte)image.transparentPixel;
569             }
570             if (image.disposalMethod != 0) {
571                 gcBlock[0] |= (byte)((image.disposalMethod & 0x07) << 2);
572             }
573             if (image.delayTime != 0) {
574                 gcBlock[1] = (byte)(image.delayTime & 0xFF);
575                 gcBlock[2] = (byte)((image.delayTime >> 8) & 0xFF);
576             }
577             outputStream.write((byte)gcBlock.length);
578             outputStream.write(gcBlock);
579             outputStream.write(0); // Block terminator
580
} catch (IOException e) {
581             SWT.error(SWT.ERROR_IO, e);
582         }
583     }
584
585     /**
586      * Write the specified palette to the output stream.
587      */

588     void writePalette(PaletteData palette, int depth) {
589         byte[] bytes = new byte[(1 << depth) * 3];
590         int offset = 0;
591         for (int i = 0; i < palette.colors.length; i++) {
592             RGB color = palette.colors[i];
593             bytes[offset] = (byte)color.red;
594             bytes[offset + 1] = (byte)color.green;
595             bytes[offset + 2] = (byte)color.blue;
596             offset += 3;
597         }
598         try {
599             outputStream.write(bytes);
600         } catch (IOException e) {
601             SWT.error(SWT.ERROR_IO, e);
602         }
603     }
604 }
605
Popular Tags