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