1 package org.catacomb.movie.gif;
2
3
4
5 import java.awt.Color;
6 import java.awt.Graphics2D;
7 import java.awt.image.BufferedImage;
8 import java.awt.image.DataBufferByte;
9 import java.io.*;
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 public class AnimatedGifEncoder {
34
35 protected int width;
36 protected int height;
37 protected Color transparent = null;
38 protected int transIndex;
39 protected int repeat = -1;
40 protected int delay = 0;
41 protected boolean started = false;
42 protected OutputStream out;
43 protected BufferedImage image;
44 protected byte[] pixels;
45 protected byte[] indexedPixels;
46 protected int colorDepth;
47 protected byte[] colorTab;
48 protected boolean[] usedEntry = new boolean[256];
49 protected int palSize = 7;
50 protected int dispose = -1;
51 protected boolean closeStream = false;
52 protected boolean firstFrame = true;
53 protected boolean sizeSet = false;
54 protected int sample = 10;
55
56
57
58
59
60
61
62 public void setDelay(int ms) {
63 delay = Math.round(ms / 10.0f);
64 }
65
66
67
68
69
70
71
72 public void setDispose(int code) {
73 if (code >= 0) {
74 dispose = code;
75 }
76 }
77
78
79
80
81
82
83
84
85
86
87 public void setRepeat(int iter) {
88 if (iter >= 0) {
89 repeat = iter;
90 }
91 }
92
93
94
95
96
97
98
99
100
101
102
103
104 public void setTransparent(Color c) {
105 transparent = c;
106 }
107
108
109
110
111
112
113
114
115
116
117
118 public boolean addFrame(BufferedImage im) {
119 if ((im == null) || !started) {
120 return false;
121 }
122 boolean ok = true;
123 try {
124 if (!sizeSet) {
125
126 setSize(im.getWidth(), im.getHeight());
127 }
128 image = im;
129 getImagePixels();
130 analyzePixels();
131 if (firstFrame) {
132 writeLSD();
133 writePalette();
134 if (repeat >= 0) {
135
136 writeNetscapeExt();
137 }
138 }
139 writeGraphicCtrlExt();
140 writeImageDesc();
141 if (!firstFrame) {
142 writePalette();
143 }
144 writePixels();
145 firstFrame = false;
146 } catch (IOException e) {
147 ok = false;
148 }
149
150 return ok;
151 }
152
153
154
155
156
157
158 public boolean finish() {
159 if (!started) {
160 return false;
161 }
162 boolean ok = true;
163 started = false;
164 try {
165 out.write(0x3b);
166 out.flush();
167 if (closeStream) {
168 out.close();
169 }
170 } catch (IOException e) {
171 ok = false;
172 }
173
174
175 transIndex = 0;
176 out = null;
177 image = null;
178 pixels = null;
179 indexedPixels = null;
180 colorTab = null;
181 closeStream = false;
182 firstFrame = true;
183
184 return ok;
185 }
186
187
188
189
190
191
192
193 public void setFrameRate(float fps) {
194 if (fps != 0f) {
195 delay = Math.round(100f / fps);
196 }
197 }
198
199
200
201
202
203
204
205
206
207
208
209
210 public void setQuality(int q) {
211
212 if (q >= 1) {
213 sample = q;
214 } else {
215 sample = 1;
216 }
217 }
218
219
220
221
222
223
224
225
226
227 public void setSize(int w, int h) {
228 if (started && !firstFrame) {
229 return;
230 }
231 width = w;
232 height = h;
233 if (width < 1) {
234 width = 320;
235 }
236 if (height < 1) {
237 height = 240;
238 }
239 sizeSet = true;
240 }
241
242
243
244
245
246
247
248
249 public boolean start(OutputStream os) {
250 if (os == null) {
251 return false;
252 }
253 boolean ok = true;
254 closeStream = false;
255 out = os;
256 try {
257 writeString("GIF89a");
258 } catch (IOException e) {
259 ok = false;
260 }
261 return started = ok;
262 }
263
264
265
266
267
268
269
270 public boolean start(File file) {
271 boolean ok = true;
272 try {
273 out = new BufferedOutputStream(new FileOutputStream(file));
274 ok = start(out);
275 closeStream = true;
276 } catch (IOException e) {
277 ok = false;
278 }
279 return started = ok;
280 }
281
282
283
284
285 protected void analyzePixels() {
286 int len = pixels.length;
287 int nPix = len / 3;
288 indexedPixels = new byte[nPix];
289 NeuQuant nq = new NeuQuant(pixels, len, sample);
290
291 colorTab = nq.process();
292
293 for (int i = 0; i < colorTab.length; i += 3) {
294 byte temp = colorTab[i];
295 colorTab[i] = colorTab[i + 2];
296 colorTab[i + 2] = temp;
297 usedEntry[i / 3] = false;
298 }
299
300 int k = 0;
301 for (int i = 0; i < nPix; i++) {
302 int index =
303 nq.map(pixels[k++] & 0xff,
304 pixels[k++] & 0xff,
305 pixels[k++] & 0xff);
306 usedEntry[index] = true;
307 indexedPixels[i] = (byte) index;
308 }
309 pixels = null;
310 colorDepth = 8;
311 palSize = 7;
312
313 if (transparent != null) {
314 transIndex = findClosest(transparent);
315 }
316 }
317
318
319
320
321
322 protected int findClosest(Color c) {
323 if (colorTab == null) {
324 return -1;
325 }
326 int r = c.getRed();
327 int g = c.getGreen();
328 int b = c.getBlue();
329 int minpos = 0;
330 int dmin = 256 * 256 * 256;
331 int len = colorTab.length;
332 for (int i = 0; i < len;) {
333 int dr = r - (colorTab[i++] & 0xff);
334 int dg = g - (colorTab[i++] & 0xff);
335 int db = b - (colorTab[i] & 0xff);
336 int d = dr * dr + dg * dg + db * db;
337 int index = i / 3;
338 if (usedEntry[index] && (d < dmin)) {
339 dmin = d;
340 minpos = index;
341 }
342 i++;
343 }
344 return minpos;
345 }
346
347
348
349
350 protected void getImagePixels() {
351 int w = image.getWidth();
352 int h = image.getHeight();
353 int type = image.getType();
354 if ((w != width)
355 || (h != height)
356 || (type != BufferedImage.TYPE_3BYTE_BGR)) {
357
358 BufferedImage temp =
359 new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
360 Graphics2D g = temp.createGraphics();
361 g.drawImage(image, 0, 0, null);
362 image = temp;
363 }
364 pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
365 }
366
367
368
369
370 protected void writeGraphicCtrlExt() throws IOException {
371 out.write(0x21);
372 out.write(0xf9);
373 out.write(4);
374 int transp, disp;
375 if (transparent == null) {
376 transp = 0;
377 disp = 0;
378 } else {
379 transp = 1;
380 disp = 2;
381 }
382 if (dispose >= 0) {
383 disp = dispose & 7;
384 }
385 disp <<= 2;
386
387
388 out.write(0 |
389 disp |
390 0 |
391 transp);
392
393 writeShort(delay);
394 out.write(transIndex);
395 out.write(0);
396 }
397
398
399
400
401 protected void writeImageDesc() throws IOException {
402 out.write(0x2c);
403 writeShort(0);
404 writeShort(0);
405 writeShort(width);
406 writeShort(height);
407
408 if (firstFrame) {
409
410 out.write(0);
411 } else {
412
413 out.write(0x80 |
414 0 |
415 0 |
416 0 |
417 palSize);
418 }
419 }
420
421
422
423
424 protected void writeLSD() throws IOException {
425
426 writeShort(width);
427 writeShort(height);
428
429 out.write((0x80 |
430 0x70 |
431 0x00 |
432 palSize));
433
434 out.write(0);
435 out.write(0);
436 }
437
438
439
440
441
442 protected void writeNetscapeExt() throws IOException {
443 out.write(0x21);
444 out.write(0xff);
445 out.write(11);
446 writeString("NETSCAPE" + "2.0");
447 out.write(3);
448 out.write(1);
449 writeShort(repeat);
450 out.write(0);
451 }
452
453
454
455
456 protected void writePalette() throws IOException {
457 out.write(colorTab, 0, colorTab.length);
458 int n = (3 * 256) - colorTab.length;
459 for (int i = 0; i < n; i++) {
460 out.write(0);
461 }
462 }
463
464
465
466
467 protected void writePixels() throws IOException {
468 LZWEncoder encoder =
469 new LZWEncoder(width, height, indexedPixels, colorDepth);
470 encoder.encode(out);
471 }
472
473
474
475
476 protected void writeShort(int value) throws IOException {
477 out.write(value & 0xff);
478 out.write((value >> 8) & 0xff);
479 }
480
481
482
483
484 protected void writeString(String s) throws IOException {
485 for (int i = 0; i < s.length(); i++) {
486 out.write((byte) s.charAt(i));
487 }
488 }
489 }