root/dwt/internal/image/GIFFileFormat.d

Revision 246:fd9c62a2998e, 22.8 kB (checked in by Frank Benoit <benoit@tionex.de>, 6 months ago)

Updater SWT 3.4M7 to 3.4

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