/Users/lyon/j4p/src/ip/gif/gifAnimation/Gif89Frame.java
|
1 //******************************************************************************
2 // Gif89Frame.java
3 //******************************************************************************
4 package ip.gif.gifAnimation;
5
6 import java.awt.*;
7 import java.io.IOException;
8 import java.io.OutputStream;
9
10 //==============================================================================
11
12 /** First off, just to dispel any doubt, this class and its subclasses have
13 * nothing to do with GUI "frames" such as java.awt.Frame. We merely use the
14 * term in its very common sense of a still picture in an animation sequence.
15 * It's hoped that the restricted context will prevent any confusion.
16 * <p>
17 * An instance of this class is used in conjunction with a Gif89Encoder object
18 * to represent and encode a single static image and its associated "control"
19 * data. A Gif89Frame doesn't know or care whether it is encoding one of the
20 * many animation frames in a GIF movie, or the single bitmap in a "normal"
21 * GIF. (FYI, this design mirrors the encoded GIF structure.)
22 * <p>
23 * Since Gif89Frame is an abstract class we don't instantiate it directly, but
24 * instead create instances of its concrete subclasses, IndexGif89Frame and
25 * DirectGif89Frame. From the API standpoint, these subclasses differ only
26 * in the sort of data their instances are constructed from. Most folks will
27 * probably work with DirectGif89Frame, since it can be constructed from a
28 * java.awt.Image object, but the lower-level IndexGif89Frame class offers
29 * advantages in specialized circumstances. (Of course, in routine situations
30 * you might not explicitly instantiate any frames at all, instead letting
31 * Gif89Encoder's convenience methods do the honors.)
32 * <p>
33 * As far as the public API is concerned, objects in the Gif89Frame hierarchy
34 * interact with a Gif89Encoder only via the latter's methods for adding and
35 * querying frames. (As a side note, you should know that while Gif89Encoder
36 * objects are permanently modified by the addition of Gif89Frames, the reverse
37 * is NOT true. That is, even though the ultimate encoding of a Gif89Frame may
38 * be affected by the context its parent encoder object provides, it retains
39 * its original condition and can be reused in a different context.)
40 * <p>
41 * The core pixel-encoding code in this class was essentially lifted from
42 * Jef Poskanzer's well-known <cite>Acme GifEncoder</cite>, so please see the
43 * <a href="../readme.txt">readme</a> containing his notice.
44 *
45 * @version 0.90 beta (15-Jul-2000)
46 * @author J. M. G. Elliott (tep@jmge.net)
47 * @see Gif89Encoder
48 * @see DirectGif89Frame
49 * @see IndexGif89Frame
50 */
51 public abstract class Gif89Frame {
52
53 //// Public "Disposal Mode" constants ////
54
55 /** The animated GIF renderer shall decide how to dispose of this Gif89Frame's
56 * display area.
57 * @see Gif89Frame#setDisposalMode
58 */
59 public static final int DM_UNDEFINED = 0;
60
61 /** The animated GIF renderer shall take no display-disposal action.
62 * @see Gif89Frame#setDisposalMode
63 */
64 public static final int DM_LEAVE = 1;
65
66 /** The animated GIF renderer shall replace this Gif89Frame's area with the
67 * background color.
68 * @see Gif89Frame#setDisposalMode
69 */
70 public static final int DM_BGCOLOR = 2;
71
72 /** The animated GIF renderer shall replace this Gif89Frame's area with the
73 * previous frame's bitmap.
74 * @see Gif89Frame#setDisposalMode
75 */
76 public static final int DM_REVERT = 3;
77
78 //// Bitmap variables set in package subclass constructors ////
79 int theWidth = -1;
80 int theHeight = -1;
81 byte[] ciPixels;
82
83 //// GIF graphic frame control options ////
84 private Point thePosition = new Point(0, 0);
85 private boolean isInterlaced;
86 private int csecsDelay;
87 private int disposalCode = DM_LEAVE;
88
89 //----------------------------------------------------------------------------
90 /** Set the position of this frame within a larger animation display space.
91 *
92 * @param p
93 * Coordinates of the frame's upper left corner in the display space.
94 * (Default: The logical display's origin [0, 0])
95 * @see Gif89Encoder#setLogicalDisplay
96 */
97 public void setPosition(Point p) {
98 thePosition = new Point(p);
99 }
100
101 //----------------------------------------------------------------------------
102 /** Set or clear the interlace flag.
103 *
104 * @param b
105 * true if you want interlacing. (Default: false)
106 */
107 public void setInterlaced(boolean b) {
108 isInterlaced = b;
109 }
110
111 //----------------------------------------------------------------------------
112 /** Set the between-frame interval.
113 *
114 * @param interval
115 * Centiseconds to wait before displaying the subsequent frame.
116 * (Default: 0)
117 */
118 public void setDelay(int interval) {
119 csecsDelay = interval;
120 }
121
122 //----------------------------------------------------------------------------
123 /** Setting this option determines (in a cooperative GIF-viewer) what will be
124 * done with this frame's display area before the subsequent frame is
125 * displayed. For instance, a setting of DM_BGCOLOR can be used for erasure
126 * when redrawing with displacement.
127 *
128 * @param code
129 * One of the four int constants of the Gif89Frame.DM_* series.
130 * (Default: DM_LEAVE)
131 */
132 public void setDisposalMode(int code) {
133 disposalCode = code;
134 }
135
136 //----------------------------------------------------------------------------
137 Gif89Frame() {
138 } // package-visible default constructor
139
140 //----------------------------------------------------------------------------
141 abstract Object getPixelSource();
142
143 //----------------------------------------------------------------------------
144 int getWidth() {
145 return theWidth;
146 }
147
148 //----------------------------------------------------------------------------
149 int getHeight() {
150 return theHeight;
151 }
152
153 //----------------------------------------------------------------------------
154 byte[] getPixelSink() {
155 return ciPixels;
156 }
157
158 //----------------------------------------------------------------------------
159 void encode(OutputStream os, boolean epluribus, int color_depth,
160 int transparent_index) throws IOException {
161 writeGraphicControlExtension(os, epluribus, transparent_index);
162 writeImageDescriptor(os);
163 new GifPixelsEncoder(
164 theWidth, theHeight, ciPixels, isInterlaced, color_depth
165 ).encode(os);
166 }
167
168 //----------------------------------------------------------------------------
169 private void writeGraphicControlExtension(OutputStream os, boolean epluribus,
170 int itransparent) throws IOException {
171 int transflag = itransparent == -1 ? 0 : 1;
172 if (transflag == 1 || epluribus) // using transparency or animating ?
173 {
174 os.write((int) '!'); // GIF Extension Introducer
175 os.write(0xf9); // Graphic Control Label
176 os.write(4); // subsequent data block size
177 os.write((disposalCode << 2) | transflag); // packed fields (1 byte)
178 Put.leShort(csecsDelay, os); // delay field (2 bytes)
179 os.write(itransparent); // transparent index field
180 os.write(0); // block terminator
181 }
182 }
183
184 //----------------------------------------------------------------------------
185 private void writeImageDescriptor(OutputStream os) throws IOException {
186 os.write((int) ','); // Image Separator
187 Put.leShort(thePosition.x, os);
188 Put.leShort(thePosition.y, os);
189 Put.leShort(theWidth, os);
190 Put.leShort(theHeight, os);
191 os.write(isInterlaced ? 0x40 : 0); // packed fields (1 byte)
192 }
193 }
194
195 //==============================================================================
196
197 class GifPixelsEncoder {
198
199 private static final int EOF = -1;
200
201 private int imgW, imgH;
202 private byte[] pixAry;
203 private boolean wantInterlaced;
204 private int initCodeSize;
205
206 // raster data navigators
207 private int countDown;
208 private int xCur, yCur;
209 private int curPass;
210
211 //----------------------------------------------------------------------------
212 GifPixelsEncoder(int width, int height, byte[] pixels, boolean interlaced,
213 int color_depth) {
214 imgW = width;
215 imgH = height;
216 pixAry = pixels;
217 wantInterlaced = interlaced;
218 initCodeSize = Math.max(2, color_depth);
219 }
220
221 //----------------------------------------------------------------------------
222 void encode(OutputStream os) throws IOException {
223 os.write(initCodeSize); // write "initial code size" byte
224
225 countDown = imgW * imgH; // reset navigation variables
226 xCur = yCur = curPass = 0;
227
228 compress(initCodeSize + 1, os); // compress and write the pixel data
229
230 os.write(0); // write block terminator
231 }
232
233 //****************************************************************************
234 // (J.E.) The logic of the next two methods is largely intact from
235 // Jef Poskanzer. Some stylistic changes were made for consistency sake,
236 // plus the second method accesses the pixel value from a prefiltered linear
237 // array. That's about it.
238 //****************************************************************************
239
240 //----------------------------------------------------------------------------
241 // Bump the 'xCur' and 'yCur' to point to the next pixel.
242 //----------------------------------------------------------------------------
243 private void bumpPosition() {
244 // Bump the current X position
245 ++xCur;
246
247 // If we are at the end of a scan line, set xCur back to the beginning
248 // If we are interlaced, bump the yCur to the appropriate spot,
249 // otherwise, just increment it.
250 if (xCur == imgW) {
251 xCur = 0;
252
253 if (!wantInterlaced)
254 ++yCur;
255 else
256 switch (curPass) {
257 case 0:
258 yCur += 8;
259 if (yCur >= imgH) {
260 ++curPass;
261 yCur = 4;
262 }
263 break;
264 case 1:
265 yCur += 8;
266 if (yCur >= imgH) {
267 ++curPass;
268 yCur = 2;
269 }
270 break;
271 case 2:
272 yCur += 4;
273 if (yCur >= imgH) {
274 ++curPass;
275 yCur = 1;
276 }
277 break;
278 case 3:
279 yCur += 2;
280 break;
281 }
282 }
283 }
284
285 //----------------------------------------------------------------------------
286 // Return the next pixel from the image
287 //----------------------------------------------------------------------------
288 private int nextPixel() {
289 if (countDown == 0)
290 return EOF;
291
292 --countDown;
293
294 byte pix = pixAry[yCur * imgW + xCur];
295
296 bumpPosition();
297
298 return pix & 0xff;
299 }
300
301 //****************************************************************************
302 // (J.E.) I didn't touch Jef Poskanzer's code from this point on. (Well, OK,
303 // I changed the name of the sole outside method it accesses.) I figure
304 // if I have no idea how something works, I shouldn't play with it :)
305 //
306 // Despite its unencapsulated structure, this section is actually highly
307 // self-contained. The calling code merely calls compress(), and the present
308 // code calls nextPixel() in the caller. That's the sum total of their
309 // communication. I could have dumped it in a separate class with a callback
310 // via an interface, but it didn't seem worth messing with.
311 //****************************************************************************
312
313 // GIFCOMPR.C - GIF Image compression routines
314 //
315 // Lempel-Ziv compression based on 'compress'. GIF modifications by
316 // David Rowley (mgardi@watdcsu.waterloo.edu)
317
318 // General DEFINEs
319
320 static final int BITS = 12;
321
322 static final int HSIZE = 5003; // 80% occupancy
323
324 // GIF Image compression - modified 'compress'
325 //
326 // Based on: compress.c - File compression ala IEEE Computer, June 1984.
327 //
328 // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
329 // Jim McKie (decvax!mcvax!jim)
330 // Steve Davies (decvax!vax135!petsd!peora!srd)
331 // Ken Turkowski (decvax!decwrl!turtlevax!ken)
332 // James A. Woods (decvax!ihnp4!ames!jaw)
333 // Joe Orost (decvax!vax135!petsd!joe)
334
335 int n_bits; // number of bits/code
336 int maxbits = BITS; // user settable max # bits/code
337 int maxcode; // maximum code, given n_bits
338 int maxmaxcode = 1 << BITS; // should NEVER generate this code
339
340 final int MAXCODE(int n_bits) {
341 return (1 << n_bits) - 1;
342 }
343
344 int[] htab = new int[HSIZE];
345 int[] codetab = new int[HSIZE];
346
347 int hsize = HSIZE; // for dynamic table sizing
348
349 int free_ent = 0; // first unused entry
350
351 // block compression parameters -- after all codes are used up,
352 // and compression rate changes, start over.
353 boolean clear_flg = false;
354
355 // Algorithm: use open addressing double hashing (no chaining) on the
356 // prefix code / next character combination. We do a variant of Knuth's
357 // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
358 // secondary probe. Here, the modular division first probe is gives way
359 // to a faster exclusive-or manipulation. Also do block compression with
360 // an adaptive reset, whereby the code table is cleared when the compression
361 // ratio decreases, but after the table fills. The variable-length output
362 // codes are re-sized at this point, and a special CLEAR code is generated
363 // for the decompressor. Late addition: construct the table according to
364 // file size for noticeable speed improvement on small files. Please direct
365 // questions about this implementation to ames!jaw.
366
367 int g_init_bits;
368
369 int ClearCode;
370 int EOFCode;
371
372 void compress(int init_bits, OutputStream outs) throws IOException {
373 int fcode;
374 int i /* = 0 */;
375 int c;
376 int ent;
377 int disp;
378 int hsize_reg;
379 int hshift;
380
381 // Set up the globals: g_init_bits - initial number of bits
382 g_init_bits = init_bits;
383
384 // Set up the necessary values
385 clear_flg = false;
386 n_bits = g_init_bits;
387 maxcode = MAXCODE(n_bits);
388
389 ClearCode = 1 << (init_bits - 1);
390 EOFCode = ClearCode + 1;
391 free_ent = ClearCode + 2;
392
393 char_init();
394
395 ent = nextPixel();
396
397 hshift = 0;
398 for (fcode = hsize; fcode < 65536; fcode *= 2)
399 ++hshift;
400 hshift = 8 - hshift; // set hash code range bound
401
402 hsize_reg = hsize;
403 cl_hash(hsize_reg); // clear hash table
404
405 output(ClearCode, outs);
406
407 outer_loop:
408 while ((c = nextPixel()) != EOF) {
409 fcode = (c << maxbits) + ent;
410 i = (c << hshift) ^ ent; // xor hashing
411
412 if (htab[i] == fcode) {
413 ent = codetab[i];
414 continue;
415 } else if (htab[i] >= 0) // non-empty slot
416 {
417 disp = hsize_reg - i; // secondary hash (after G. Knott)
418 if (i == 0)
419 disp = 1;
420 do {
421 if ((i -= disp) < 0)
422 i += hsize_reg;
423
424 if (htab[i] == fcode) {
425 ent = codetab[i];
426 continue outer_loop;
427 }
428 } while (htab[i] >= 0);
429 }
430 output(ent, outs);
431 ent = c;
432 if (free_ent < maxmaxcode) {
433 codetab[i] = free_ent++; // code -> hashtable
434 htab[i] = fcode;
435 } else
436 cl_block(outs);
437 }
438 // Put out the final code.
439 output(ent, outs);
440 output(EOFCode, outs);
441 }
442
443 // output
444 //
445 // Output the given code.
446 // Inputs:
447 // code: A n_bits-bit integer. If == -1, then EOF. This assumes
448 // that n_bits =< wordsize - 1.
449 // Outputs:
450 // Outputs code to the file.
451 // Assumptions:
452 // Chars are 8 bits long.
453 // Algorithm:
454 // Maintain a BITS character long buffer (so that 8 codes will
455 // fit in it exactly). Use the VAX insv instruction to insert each
456 // code in turn. When the buffer fills up empty it and start over.
457
458 int cur_accum = 0;
459 int cur_bits = 0;
460
461 int masks[] = {0x0000, 0x0001, 0x0003, 0x0007, 0x000F,
462 0x001F, 0x003F, 0x007F, 0x00FF,
463 0x01FF, 0x03FF, 0x07FF, 0x0FFF,
464 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF};
465
466 void output(int code, OutputStream outs) throws IOException {
467 cur_accum &= masks[cur_bits];
468
469 if (cur_bits > 0)
470 cur_accum |= (code << cur_bits);
471 else
472 cur_accum = code;
473
474 cur_bits += n_bits;
475
476 while (cur_bits >= 8) {
477 char_out((byte) (cur_accum & 0xff), outs);
478 cur_accum >>= 8;
479 cur_bits -= 8;
480 }
481
482 // If the next entry is going to be too big for the code size,
483 // then increase it, if possible.
484 if (free_ent > maxcode || clear_flg) {
485 if (clear_flg) {
486 maxcode = MAXCODE(n_bits = g_init_bits);
487 clear_flg = false;
488 } else {
489 ++n_bits;
490 if (n_bits == maxbits)
491 maxcode = maxmaxcode;
492 else
493 maxcode = MAXCODE(n_bits);
494 }
495 }
496
497 if (code == EOFCode) {
498 // At EOF, write the rest of the buffer.
499 while (cur_bits > 0) {
500 char_out((byte) (cur_accum & 0xff), outs);
501 cur_accum >>= 8;
502 cur_bits -= 8;
503 }
504
505 flush_char(outs);
506 }
507 }
508
509 // Clear out the hash table
510
511 // table clear for block compress
512 void cl_block(OutputStream outs) throws IOException {
513 cl_hash(hsize);
514 free_ent = ClearCode + 2;
515 clear_flg = true;
516
517 output(ClearCode, outs);
518 }
519
520 // reset code table
521 void cl_hash(int hsize) {
522 for (int i = 0; i < hsize; ++i)
523 htab[i] = -1;
524 }
525
526 // GIF Specific routines
527
528 // Number of characters so far in this 'packet'
529 int a_count;
530
531 // Set up the 'byte output' routine
532 void char_init() {
533 a_count = 0;
534 }
535
536 // Define the storage for the packet accumulator
537 byte[] accum = new byte[256];
538
539 // Add a character to the end of the current packet, and if it is 254
540 // characters, flush the packet to disk.
541 void char_out(byte c, OutputStream outs) throws IOException {
542 accum[a_count++] = c;
543 if (a_count >= 254)
544 flush_char(outs);
545 }
546
547 // Flush the packet to disk, and reset the accumulator
548 void flush_char(OutputStream outs) throws IOException {
549 if (a_count > 0) {
550 outs.write(a_count);
551 outs.write(accum, 0, a_count);
552 a_count = 0;
553 }
554 }
555 }