comparison org.eclipse.swt.win32.win32.x86/src/org/eclipse/swt/internal/image/GIFFileFormat.d @ 0:6dd524f61e62

add dwt win and basic java stuff
author Frank Benoit <benoit@tionex.de>
date Mon, 02 Mar 2009 14:44:16 +0100
parents
children f36c67707cb3
comparison
equal deleted inserted replaced
-1:000000000000 0:6dd524f61e62
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 org.eclipse.swt.internal.image.GIFFileFormat;
14
15 public import org.eclipse.swt.internal.image.FileFormat;
16 public import org.eclipse.swt.graphics.PaletteData;
17 import org.eclipse.swt.internal.image.LEDataInputStream;
18 import org.eclipse.swt.internal.image.LZWCodec;
19 import org.eclipse.swt.graphics.RGB;
20 import org.eclipse.swt.SWT;
21 import org.eclipse.swt.graphics.ImageData;
22 import org.eclipse.swt.graphics.ImageLoaderEvent;
23 import org.eclipse.swt.graphics.ImageLoader;
24 import tango.core.Exception;
25 import java.lang.all;
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 SWT.error(SWT.ERROR_INVALID_IMAGE);
83
84 inputStream.read(versionBytes);
85
86 inputStream.read(block);
87 } catch (IOException e) {
88 SWT.error(SWT.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 SWT.error(SWT.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 SWT.error(SWT.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 SWT.error(SWT.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 SWT.error(SWT.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 SWT.error(SWT.ERROR_INVALID_IMAGE);
205 }
206 byte[] ext = new byte[extSize];
207 inputStream.read(ext, 0, extSize);
208 return ext;
209 } catch (IOException e) {
210 SWT.error(SWT.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 SWT.error(SWT.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 SWT.error(SWT.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 SWT.error(SWT.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 SWT.error(SWT.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 SWT.error(SWT.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 SWT.error(SWT.ERROR_IO, e);
408 }
409 if (initialCodeSize < 0) {
410 SWT.error(SWT.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 SWT.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 SWT.error(SWT.ERROR_INVALID_IMAGE);
442 } catch (IOException e) {
443 SWT.error(SWT.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 SWT.error(SWT.ERROR_UNSUPPORTED_DEPTH);
470 }
471 for (int i=0; i<frameCount; i++) {
472 if (data[i].palette.isDirect) {
473 SWT.error(SWT.ERROR_INVALID_IMAGE);
474 }
475 if (multi) {
476 if (!(data[i].height <= logicalScreenHeight && data[i].width <= logicalScreenWidth && data[i].depth is depth)) {
477 SWT.error(SWT.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 SWT.error(SWT.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 /* Step 5: Write Application Extension if applicable. */
514 if (multi) {
515 int repeatCount = loader.repeatCount;
516 try {
517 outputStream.write(GIF_EXTENSION_BLOCK_ID);
518 outputStream.write(GIF_APPLICATION_EXTENSION_BLOCK_ID);
519 outputStream.write(NETSCAPE2_0.length);
520 outputStream.write(NETSCAPE2_0);
521 outputStream.write(3); // Three bytes follow
522 outputStream.write(1); // Extension type
523 outputStream.writeShort(cast(short) repeatCount);
524 outputStream.write(0); // Block terminator
525 } catch (IOException e) {
526 SWT.error(SWT.ERROR_IO, e);
527 }
528 }
529
530 for (int frame=0; frame<frameCount; frame++) {
531
532 /* Step 6: Write Graphics Control Block for each frame if applicable. */
533 if (multi || data[frame].transparentPixel !is -1) {
534 writeGraphicsControlBlock(data[frame]);
535 }
536
537 /* Step 7: Write Image Header for each frame. */
538 int x = data[frame].x;
539 int y = data[frame].y;
540 int width = data[frame].width;
541 int height = data[frame].height;
542 try {
543 outputStream.write(GIF_IMAGE_BLOCK_ID);
544 byte[] block = new byte[9];
545 block[0] = cast(byte)(x & 0xFF);
546 block[1] = cast(byte)((x >> 8) & 0xFF);
547 block[2] = cast(byte)(y & 0xFF);
548 block[3] = cast(byte)((y >> 8) & 0xFF);
549 block[4] = cast(byte)(width & 0xFF);
550 block[5] = cast(byte)((width >> 8) & 0xFF);
551 block[6] = cast(byte)(height & 0xFF);
552 block[7] = cast(byte)((height >> 8) & 0xFF);
553 block[8] = cast(byte)(globalTable is 0 ? (depth-1) | 0x80 : 0x00);
554 outputStream.write(block);
555 } catch (IOException e) {
556 SWT.error(SWT.ERROR_IO, e);
557 }
558
559 /* Step 8: Write Local Color Table for each frame if applicable. */
560 if (globalTable is 0) {
561 writePalette(data[frame].palette, depth);
562 }
563
564 /* Step 9: Write the actual data for each frame. */
565 try {
566 outputStream.write(depth); // Minimum LZW Code size
567 } catch (IOException e) {
568 SWT.error(SWT.ERROR_IO, e);
569 }
570 (new LZWCodec()).encode(outputStream, data[frame]);
571 }
572
573 /* Step 10: Write GIF terminator. */
574 try {
575 outputStream.write(0x3B);
576 } catch (IOException e) {
577 SWT.error(SWT.ERROR_IO, e);
578 }
579 }
580
581 /**
582 * Write out a GraphicsControlBlock to describe
583 * the specified device independent image.
584 */
585 void writeGraphicsControlBlock(ImageData image) {
586 try {
587 outputStream.write(GIF_EXTENSION_BLOCK_ID);
588 outputStream.write(GIF_GRAPHICS_CONTROL_BLOCK_ID);
589 byte[] gcBlock = new byte[4];
590 gcBlock[0] = 0;
591 gcBlock[1] = 0;
592 gcBlock[2] = 0;
593 gcBlock[3] = 0;
594 if (image.transparentPixel !is -1) {
595 gcBlock[0] = cast(byte)0x01;
596 gcBlock[3] = cast(byte)image.transparentPixel;
597 }
598 if (image.disposalMethod !is 0) {
599 gcBlock[0] |= cast(byte)((image.disposalMethod & 0x07) << 2);
600 }
601 if (image.delayTime !is 0) {
602 gcBlock[1] = cast(byte)(image.delayTime & 0xFF);
603 gcBlock[2] = cast(byte)((image.delayTime >> 8) & 0xFF);
604 }
605 outputStream.write(cast(byte)gcBlock.length);
606 outputStream.write(gcBlock);
607 outputStream.write(0); // Block terminator
608 } catch (IOException e) {
609 SWT.error(SWT.ERROR_IO, e);
610 }
611 }
612
613 /**
614 * Write the specified palette to the output stream.
615 */
616 void writePalette(PaletteData palette, int depth) {
617 byte[] bytes = new byte[(1 << depth) * 3];
618 int offset = 0;
619 for (int i = 0; i < palette.colors.length; i++) {
620 RGB color = palette.colors[i];
621 bytes[offset] = cast(byte)color.red;
622 bytes[offset + 1] = cast(byte)color.green;
623 bytes[offset + 2] = cast(byte)color.blue;
624 offset += 3;
625 }
626 try {
627 outputStream.write(bytes);
628 } catch (IOException e) {
629 SWT.error(SWT.ERROR_IO, e);
630 }
631 }
632 }