/Users/lyon/j4p/src/ip/gif/gifAnimation/Gif89Encoder.java
|
1 //******************************************************************************
2 // Gif89Encoder.java
3 //******************************************************************************
4 package ip.gif.gifAnimation;
5
6 import java.awt.*;
7 import java.io.*;
8 import java.util.Vector;
9
10 //==============================================================================
11
12 /**
13 * This is the central class of a JDK 1.1 compatible GIF encoder that,
14 * AFAIK, supports more features of the extended GIF spec than any other
15 * Java open source encoder. Some sections of the source are lifted or
16 * adapted from Jef Poskanzer's <cite>Acme GifEncoder</cite> (so please see
17 * the <a href="../readme.txt">readme</a> containing his notice), but much
18 * of it, including nearly all of the present class, is original code. My
19 * main motivation for writing a new encoder was to support animated GIFs,
20 * but the package also adds support for embedded textual comments.
21 * <p/>
22 * There are still some limitations. For instance, animations are limited
23 * to a single global color table. But that is usually what you want
24 * anyway, so as to avoid irregularities on some displays. (So this is not
25 * really a limitation, but a "disciplinary feature" :) Another rather
26 * more serious restriction is that the total number of RGB colors in a
27 * given input-batch mustn't exceed 256. Obviously, there is an opening
28 * here for someone who would like to add a color-reducing preprocessor.
29 * <p/>
30 * The encoder, though very usable in its present form, is at bottom only a
31 * partial implementation skewed toward my own particular needs. Hence a
32 * couple of caveats are in order. (1) During development it was in the
33 * back of my mind that an encoder object should be reusable - i.e., you
34 * should be able to make multiple calls to encode() on the same object,
35 * with or without intervening frame additions or changes to options. But
36 * I haven't reviewed the code with such usage in mind, much less tested
37 * it, so it's likely I overlooked something. (2) The encoder classes
38 * aren't thread safe, so use caution in a context where access is shared
39 * by multiple threads. (Better yet, finish the library and re-release it
40 * :)
41 * <p/>
42 * There follow a couple of simple examples illustrating the most common
43 * way to use the encoder, i.e., to encode AWT Image objects created
44 * elsewhere in the program. Use of some of the most popular format
45 * options is also shown, though you will want to peruse the API for
46 * additional features.
47 * <p/>
48 * <p/>
49 * <strong>Animated GIF Example</strong>
50 * <pre>
51 * import net.jmge.gif.Gif89Encoder;
52 * // ...
53 * void writeAnimatedGIF(Image[] still_images,
54 * String annotation,
55 * boolean looped,
56 * double frames_per_second,
57 * OutputStream out) throws IOException
58 * {
59 * Gif89Encoder gifenc = new Gif89Encoder();
60 * for (int i = 0; i < still_images.length; ++i)
61 * gifenc.addFrame(still_images[i]);
62 * gifenc.setComments(annotation);
63 * gifenc.setLoopCount(looped ? 0 : 1);
64 * gifenc.setUniformDelay((int) Math.round(100 / frames_per_second));
65 * gifenc.encode(out);
66 * }
67 * </pre>
68 * <p/>
69 * <strong>Static GIF Example</strong>
70 * <pre>
71 * import net.jmge.gif.Gif89Encoder;
72 * // ...
73 * void writeNormalGIF(Image img,
74 * String annotation,
75 * int transparent_index, // pass -1 for none
76 * boolean interlaced,
77 * OutputStream out) throws IOException
78 * {
79 * Gif89Encoder gifenc = new Gif89Encoder(img);
80 * gifenc.setComments(annotation);
81 * gifenc.setTransparentIndex(transparent_index);
82 * gifenc.getFrameAt(0).setInterlaced(interlaced);
83 * gifenc.encode(out);
84 * }
85 * </pre>
86 *
87 * @author J. M. G. Elliott (tep@jmge.net)
88 * @version 0.90 beta (15-Jul-2000)
89 * @see Gif89Frame
90 * @see DirectGif89Frame
91 * @see IndexGif89Frame
92 */
93 public class Gif89Encoder {
94
95 private Dimension dispDim = new Dimension(0, 0);
96 private GifColorTable colorTable;
97 private int bgIndex = 0;
98 private int loopCount = 1;
99 private String theComments;
100 private Vector vFrames = new Vector();
101
102 //----------------------------------------------------------------------------
103 /**
104 * Use this default constructor if you'll be adding multiple frames
105 * constructed from RGB data (i.e., AWT Image objects or ARGB-pixel
106 * arrays).
107 */
108 public Gif89Encoder() {
109 // empty color table puts us into "palette autodetect" mode
110 colorTable = new GifColorTable();
111 }
112
113 //----------------------------------------------------------------------------
114 /**
115 * Like the default except that it also adds a single frame, for
116 * conveniently encoding a static GIF from an image.
117 *
118 * @param static_image Any Image object that supports pixel-grabbing.
119 * @throws IOException See the addFrame() methods.
120 */
121 public Gif89Encoder(Image static_image) throws IOException {
122 this();
123 addFrame(static_image);
124 }
125
126 //----------------------------------------------------------------------------
127 /**
128 * This constructor installs a user color table, overriding the
129 * detection of of a palette from ARBG pixels.
130 * <p/>
131 * Use of this constructor imposes a couple of restrictions: (1) Frame
132 * objects can't be of type DirectGif89Frame (2) Transparency, if
133 * desired, must be set explicitly.
134 *
135 * @param colors Array of color values; no more than 256 colors will be
136 * read, since that's the limit for a GIF.
137 */
138 public Gif89Encoder(Color[] colors) {
139 colorTable = new GifColorTable(colors);
140 }
141
142 //----------------------------------------------------------------------------
143 /**
144 * Convenience constructor for encoding a static GIF from index-model
145 * data. Adds a single frame as specified.
146 *
147 * @param colors Array of color values; no more than 256 colors will
148 * be read, since that's the limit for a GIF.
149 * @param width Width of the GIF bitmap.
150 * @param height Height of same.
151 * @param ci_pixels Array of color-index pixels no less than width *
152 * height in length.
153 * @throws IOException See the addFrame() methods.
154 */
155 public Gif89Encoder(Color[] colors,
156 int width,
157 int height,
158 byte ci_pixels[])
159 throws IOException {
160 this(colors);
161 addFrame(width, height, ci_pixels);
162 }
163
164 //----------------------------------------------------------------------------
165 /**
166 * Get the number of frames that have been added so far.
167 *
168 * @return Number of frame items.
169 */
170 public int getFrameCount() {
171 return vFrames.size();
172 }
173
174 //----------------------------------------------------------------------------
175 /**
176 * Get a reference back to a Gif89Frame object by position.
177 *
178 * @param index Zero-based index of the frame in the sequence.
179 * @return Gif89Frame object at the specified position (or null if no
180 * such frame).
181 */
182 public Gif89Frame getFrameAt(int index) {
183 return isOk(index) ? (Gif89Frame) vFrames.elementAt(index) : null;
184 }
185
186 //----------------------------------------------------------------------------
187 /**
188 * Add a Gif89Frame frame to the end of the internal sequence. Note
189 * that there are restrictions on the Gif89Frame type: if the encoder
190 * object was constructed with an explicit color table, an attempt to
191 * add a DirectGif89Frame will throw an exception.
192 *
193 * @param gf An externally constructed Gif89Frame.
194 * @throws IOException If Gif89Frame can't be accommodated. This could
195 * happen if either (1) the aggregate cross-frame
196 * RGB color count exceeds 256, or (2) the
197 * Gif89Frame subclass is incompatible with the
198 * present encoder object.
199 */
200 public void addFrame(Gif89Frame gf) throws IOException {
201 accommodateFrame(gf);
202 vFrames.addElement(gf);
203 }
204
205 //----------------------------------------------------------------------------
206 /**
207 * Convenience version of addFrame() that takes a Java Image,
208 * internally constructing the requisite DirectGif89Frame.
209 *
210 * @param image Any Image object that supports pixel-grabbing.
211 * @throws IOException If either (1) pixel-grabbing fails, (2) the
212 * aggregate cross-frame RGB color count exceeds
213 * 256, or (3) this encoder object was constructed
214 * with an explicit color table.
215 */
216 public void addFrame(Image image) throws IOException {
217 addFrame(new DirectGif89Frame(image));
218 }
219
220 //----------------------------------------------------------------------------
221 /**
222 * The index-model convenience version of addFrame().
223 *
224 * @param width Width of the GIF bitmap.
225 * @param height Height of same.
226 * @param ci_pixels Array of color-index pixels no less than width *
227 * height in length.
228 * @throws IOException Actually, in the present implementation, there
229 * aren't any unchecked exceptions that can be
230 * thrown when adding an IndexGif89Frame <i>per
231 * se</i>. But I might add some pedantic check
232 * later, to justify the generality :)
233 */
234 public void addFrame(int width, int height, byte ci_pixels[])
235 throws IOException {
236 addFrame(new IndexGif89Frame(width, height, ci_pixels));
237 }
238
239 //----------------------------------------------------------------------------
240 /**
241 * Like addFrame() except that the frame is inserted at a specific
242 * point in the sequence rather than appended.
243 *
244 * @param index Zero-based index at which to insert frame.
245 * @param gf An externally constructed Gif89Frame.
246 * @throws IOException If Gif89Frame can't be accommodated. This could
247 * happen if either (1) the aggregate cross-frame
248 * RGB color count exceeds 256, or (2) the
249 * Gif89Frame subclass is incompatible with the
250 * present encoder object.
251 */
252 public void insertFrame(int index, Gif89Frame gf) throws IOException {
253 accommodateFrame(gf);
254 vFrames.insertElementAt(gf, index);
255 }
256
257 //----------------------------------------------------------------------------
258 /**
259 * Set the color table index for the transparent color, if any.
260 *
261 * @param index Index of the color that should be rendered as
262 * transparent, if any. A value of -1 turns off
263 * transparency. (Default: -1)
264 */
265 public void setTransparentIndex(int index) {
266 colorTable.setTransparent(index);
267 }
268
269 //----------------------------------------------------------------------------
270 /**
271 * Sets attributes of the multi-image display area, if applicable.
272 *
273 * @param dim Width/height of display. (Default: largest
274 * detected frame size)
275 * @param background Color table index of background color. (Default:
276 * 0)
277 * @see Gif89Frame#setPosition
278 */
279 public void setLogicalDisplay(Dimension dim, int background) {
280 dispDim = new Dimension(dim);
281 bgIndex = background;
282 }
283
284 //----------------------------------------------------------------------------
285 /**
286 * Set animation looping parameter, if applicable.
287 *
288 * @param count Number of times to play sequence. Special value of 0
289 * specifies indefinite looping. (Default: 1)
290 */
291 public void setLoopCount(int count) {
292 loopCount = count;
293 }
294
295 //----------------------------------------------------------------------------
296 /**
297 * Specify some textual comments to be embedded in GIF.
298 *
299 * @param comments String containing ASCII comments.
300 */
301 public void setComments(String comments) {
302 theComments = comments;
303 }
304
305 //----------------------------------------------------------------------------
306 /**
307 * A convenience method for setting the "animation speed". It simply
308 * sets the delay parameter for each frame in the sequence to the
309 * supplied value. Since this is actually frame-level rather than
310 * animation-level data, take care to add your frames before calling
311 * this method.
312 *
313 * @param interval Interframe interval in centiseconds.
314 */
315 public void setUniformDelay(int interval) {
316 for (int i = 0; i < vFrames.size(); ++i)
317 ((Gif89Frame) vFrames.elementAt(i)).setDelay(interval);
318 }
319
320 //----------------------------------------------------------------------------
321 /**
322 * After adding your frame(s) and setting your options, simply call
323 * this method to write the GIF to the passed stream. Multiple calls
324 * are permissible if for some reason that is useful to your
325 * application. (The method simply encodes the current state of the
326 * object with no thought to previous calls.)
327 *
328 * @param out The stream you want the GIF written to.
329 * @throws IOException If a write error is encountered.
330 */
331 public void encode(OutputStream out) throws IOException {
332 int nframes = getFrameCount();
333 boolean is_sequence = nframes > 1;
334
335 // N.B. must be called before writing screen descriptor
336 colorTable.closePixelProcessing();
337
338 // write GIF HEADER
339 Put.ascii("GIF89a", out);
340
341 // write global blocks
342 writeLogicalScreenDescriptor(out);
343 colorTable.encode(out);
344 if (is_sequence && loopCount != 1)
345 writeNetscapeExtension(out);
346 if (theComments != null && theComments.length() > 0)
347 writeCommentExtension(out);
348
349 // write out the control and rendering data for each frame
350 for (int i = 0; i < nframes; ++i)
351 ((Gif89Frame) vFrames.elementAt(i)).encode(out,
352 is_sequence,
353 colorTable.getDepth(),
354 colorTable.getTransparent());
355
356 // write GIF TRAILER
357 out.write((int) ';');
358
359 out.flush();
360 }
361
362 //----------------------------------------------------------------------------
363 /**
364 * A simple driver to test the installation and to demo usage. Put the
365 * JAR on your classpath and run ala <blockquote>java
366 * net.jmge.gif.Gif89Encoder {filename}</blockquote> The filename must
367 * be either (1) a JPEG file with extension 'jpg', for conversion to a
368 * static GIF, or (2) a file containing a list of GIFs and/or JPEGs,
369 * one per line, to be combined into an animated GIF. The output will
370 * appear in the current directory as 'gif89out.gif'.
371 * <p/>
372 * (N.B. This test program will abort if the input file(s) exceed(s)
373 * 256 total RGB colors, so in its present form it has no value as a
374 * generic JPEG to GIF converter. Also, when multiple files are input,
375 * you need to be wary of the total color count, regardless of file
376 * type.)
377 *
378 * @param args Command-line arguments, only the first of which is used,
379 * as mentioned above.
380 */
381 public static void main(String[] args) {
382 try {
383
384 Toolkit tk = Toolkit.getDefaultToolkit();
385 OutputStream out = new BufferedOutputStream(
386 new FileOutputStream("gif89out.gif"));
387
388 if (args[0].toUpperCase().endsWith(".JPG"))
389 new Gif89Encoder(tk.getImage(args[0])).encode(out);
390 else {
391 BufferedReader in = new BufferedReader(
392 new FileReader(args[0]));
393 Gif89Encoder ge = new Gif89Encoder();
394
395 String line;
396 while ((line = in.readLine()) != null)
397 ge.addFrame(tk.getImage(line.trim()));
398 ge.setLoopCount(0); // let's loop indefinitely
399 ge.encode(out);
400
401 in.close();
402 }
403 out.close();
404
405 } catch (Exception e) {
406 e.printStackTrace();
407 } finally {
408 System.exit(0);
409 } // must kill VM explicitly (Toolkit thread?)
410 }
411
412 //----------------------------------------------------------------------------
413 private void accommodateFrame(Gif89Frame gf) throws IOException {
414 dispDim.width = Math.max(dispDim.width, gf.getWidth());
415 dispDim.height = Math.max(dispDim.height, gf.getHeight());
416 colorTable.processPixels(gf);
417 }
418
419 //----------------------------------------------------------------------------
420 private void writeLogicalScreenDescriptor(OutputStream os)
421 throws IOException {
422 Put.leShort(dispDim.width, os);
423 Put.leShort(dispDim.height, os);
424
425 // write 4 fields, packed into a byte (bitfieldsize:value)
426 // global color map present? (1:1)
427 // bits per primary color less 1 (3:7)
428 // sorted color table? (1:0)
429 // bits per pixel less 1 (3:varies)
430 os.write(0xf0 | colorTable.getDepth() - 1);
431
432 // write background color index
433 os.write(bgIndex);
434
435 // Jef Poskanzer's notes on the next field, for our possible edification:
436 // Pixel aspect ratio - 1:1.
437 //Putbyte( (byte) 49, outs );
438 // Java's GIF reader currently has a bug, if the aspect ratio byte is
439 // not zero it throws an ImageFormatException. It doesn't know that
440 // 49 means a 1:1 aspect ratio. Well, whatever, zero works with all
441 // the other decoders I've tried so it probably doesn't hurt.
442
443 // OK, if it's good enough for Jef, it's definitely good enough for us:
444 os.write(0);
445 }
446
447 //----------------------------------------------------------------------------
448 private void writeNetscapeExtension(OutputStream os)
449 throws IOException {
450 // n.b. most software seems to interpret the count as a repeat count
451 // (i.e., interations beyond 1) rather than as an iteration count
452 // (thus, to avoid repeating we have to omit the whole extension)
453
454 os.write((int) '!'); // GIF Extension Introducer
455 os.write(0xff); // Application Extension Label
456
457 os.write(11); // application ID block size
458 Put.ascii("NETSCAPE2.0", os); // application ID data
459
460 os.write(3); // data sub-block size
461 os.write(1); // a looping flag? dunno
462
463 // we finally write the relevent data
464 Put.leShort(loopCount > 1 ? loopCount - 1 : 0, os);
465
466 os.write(0); // block terminator
467 }
468
469 //----------------------------------------------------------------------------
470 private void writeCommentExtension(OutputStream os)
471 throws IOException {
472 os.write((int) '!'); // GIF Extension Introducer
473 os.write(0xfe); // Comment Extension Label
474
475 int remainder = theComments.length() % 255;
476 int nsubblocks_full = theComments.length() / 255;
477 int nsubblocks = nsubblocks_full + (remainder > 0 ? 1 : 0);
478 int ibyte = 0;
479 for (int isb = 0; isb < nsubblocks; ++isb) {
480 int size = isb < nsubblocks_full ? 255 : remainder;
481
482 os.write(size);
483 Put.ascii(theComments.substring(ibyte, ibyte + size), os);
484 ibyte += size;
485 }
486
487 os.write(0); // block terminator
488 }
489
490 //----------------------------------------------------------------------------
491 private boolean isOk(int frame_index) {
492 return frame_index >= 0 && frame_index < vFrames.size();
493 }
494 }
495
496 //==============================================================================
497
498 class GifColorTable {
499
500 // the palette of ARGB colors, packed as returned by Color.getRGB()
501 private int[] theColors = new int[256];
502
503 // other basic attributes
504 private int colorDepth;
505 private int transparentIndex = -1;
506
507 // these fields track color-index info across frames
508 private int ciCount = 0; // count of distinct color indices
509 private ReverseColorMap ciLookup; // cumulative rgb-to-ci lookup table
510
511 //----------------------------------------------------------------------------
512 GifColorTable() {
513 ciLookup = new ReverseColorMap(); // puts us into "auto-detect mode"
514 }
515
516 //----------------------------------------------------------------------------
517 GifColorTable(Color[] colors) {
518 int n2copy = Math.min(theColors.length, colors.length);
519 for (int i = 0; i < n2copy; ++i)
520 theColors[i] = colors[i].getRGB();
521 }
522
523 //----------------------------------------------------------------------------
524 int getDepth() {
525 return colorDepth;
526 }
527
528 //----------------------------------------------------------------------------
529 int getTransparent() {
530 return transparentIndex;
531 }
532
533 //----------------------------------------------------------------------------
534 // default: -1 (no transparency)
535 void setTransparent(int color_index) {
536 transparentIndex = color_index;
537 }
538
539 //----------------------------------------------------------------------------
540 void processPixels(Gif89Frame gf) throws IOException {
541 if (gf instanceof DirectGif89Frame)
542 filterPixels((DirectGif89Frame) gf);
543 else
544 trackPixelUsage((IndexGif89Frame) gf);
545 }
546
547 //----------------------------------------------------------------------------
548 void closePixelProcessing() // must be called before encode()
549 {
550 colorDepth = computeColorDepth(ciCount);
551 }
552
553 //----------------------------------------------------------------------------
554 void encode(OutputStream os) throws IOException {
555 // size of palette written is the smallest power of 2 that can accomdate
556 // the number of RGB colors detected (or largest color index, in case of
557 // index pixels)
558 int palette_size = 1 << colorDepth;
559 for (int i = 0; i < palette_size; ++i) {
560 os.write(theColors[i] >> 16 & 0xff);
561 os.write(theColors[i] >> 8 & 0xff);
562 os.write(theColors[i] & 0xff);
563 }
564 }
565
566 //----------------------------------------------------------------------------
567 // This method accomplishes three things:
568 // (1) converts the passed rgb pixels to indexes into our rgb lookup table
569 // (2) fills the rgb table as new colors are encountered
570 // (3) looks for transparent pixels so as to set the transparent index
571 // The information is cumulative across multiple calls.
572 //
573 // (Note: some of the logic is borrowed from Jef Poskanzer's code.)
574 //----------------------------------------------------------------------------
575 private void filterPixels(DirectGif89Frame dgf) throws IOException {
576 if (ciLookup == null)
577 throw new IOException(
578 "RGB frames require palette autodetection");
579
580 int[] argb_pixels = (int[]) dgf.getPixelSource();
581 byte[] ci_pixels = dgf.getPixelSink();
582 int npixels = argb_pixels.length;
583 for (int i = 0; i < npixels; ++i) {
584 int argb = argb_pixels[i];
585
586 // handle transparency
587 if ((argb >>> 24) < 0x80) // transparent pixel?
588 if (transparentIndex == -1) // first transparent color encountered?
589 transparentIndex = ciCount; // record its index
590 else if (argb != theColors[transparentIndex]) // different pixel value?
591 {
592 // collapse all transparent pixels into one color index
593 ci_pixels[i] = (byte) transparentIndex;
594 continue; // CONTINUE - index already in table
595 }
596
597 // try to look up the index in our "reverse" color table
598 int color_index = ciLookup.getPaletteIndex(argb & 0xffffff);
599
600 if (color_index == -1) // if it isn't in there yet
601 {
602 if (ciCount == 256)
603 throw new IOException(
604 "can't encode as GIF (> 256 colors)");
605
606 // store color in our accumulating palette
607 theColors[ciCount] = argb;
608
609 // store index in reverse color table
610 ciLookup.put(argb & 0xffffff, ciCount);
611
612 // send color index to our output array
613 ci_pixels[i] = (byte) ciCount;
614
615 // increment count of distinct color indices
616 ++ciCount;
617 } else // we've already snagged color into our palette
618 ci_pixels[i] = (byte) color_index; // just send filtered pixel
619 }
620 }
621
622 //----------------------------------------------------------------------------
623 private void trackPixelUsage(IndexGif89Frame igf) throws IOException {
624 byte[] ci_pixels = (byte[]) igf.getPixelSource();
625 int npixels = ci_pixels.length;
626 for (int i = 0; i < npixels; ++i)
627 if (ci_pixels[i] >= ciCount)
628 ciCount = ci_pixels[i] + 1;
629 }
630
631 //----------------------------------------------------------------------------
632 private int computeColorDepth(int colorcount) {
633 // color depth = log-base-2 of maximum number of simultaneous colors, i.e.
634 // bits per color-index pixel
635 if (colorcount <= 2)
636 return 1;
637 if (colorcount <= 4)
638 return 2;
639 if (colorcount <= 16)
640 return 4;
641 return 8;
642 }
643 }
644
645 //==============================================================================
646 // We're doing a very simple linear hashing thing here, which seems sufficient
647 // for our needs. I make no claims for this approach other than that it seems
648 // an improvement over doing a brute linear search for each pixel on the one
649 // hand, and creating a Java object for each pixel (if we were to use a Java
650 // Hashtable) on the other. Doubtless my little hash could be improved by
651 // tuning the capacity (at the very least). Suggestions are welcome.
652 //==============================================================================
653
654 class ReverseColorMap {
655
656 private static class ColorRecord {
657 int rgb;
658 int ipalette;
659
660 ColorRecord(int rgb, int ipalette) {
661 this.rgb = rgb;
662 this.ipalette = ipalette;
663 }
664 }
665
666 // I wouldn't really know what a good hashing capacity is, having missed out
667 // on data structures and algorithms class :) Alls I know is, we've got a lot
668 // more space than we have time. So let's try a sparse table with a maximum
669 // load of about 1/8 capacity.
670 private static final int HCAPACITY = 2053; // a nice prime number
671
672 // our hash table proper
673 private ColorRecord[] hTable = new ColorRecord[HCAPACITY];
674
675 //----------------------------------------------------------------------------
676 // Assert: rgb is not negative (which is the same as saying, be sure the
677 // alpha transparency byte - i.e., the high byte - has been masked out).
678 //----------------------------------------------------------------------------
679 int getPaletteIndex(int rgb) {
680 ColorRecord rec;
681
682 for (int itable = rgb % hTable.length;
683 (rec = hTable[itable]) != null && rec.rgb != rgb;
684 itable = ++itable % hTable.length
685 )
686 ;
687
688 if (rec != null)
689 return rec.ipalette;
690
691 return -1;
692 }
693
694 //----------------------------------------------------------------------------
695 // Assert: (1) same as above; (2) rgb key not already present
696 //----------------------------------------------------------------------------
697 void put(int rgb, int ipalette) {
698 int itable;
699
700 for (itable = rgb % hTable.length;
701 hTable[itable] != null;
702 itable = ++itable % hTable.length
703 )
704 ;
705
706 hTable[itable] = new ColorRecord(rgb, ipalette);
707 }
708 }