001: package net.charabia.util.codec;
002:
003: /*
004: * @(#)Quantize.java 0.90 9/19/00 Adam Doppelt
005: */
006:
007: /**
008: * An efficient color quantization algorithm, adapted from the C++
009: * implementation quantize.c in <a
010: * href="http://www.imagemagick.org/">ImageMagick</a>. The pixels for
011: * an image are placed into an oct tree. The oct tree is reduced in
012: * size, and the pixels from the original image are reassigned to the
013: * nodes in the reduced tree.<p>
014: *
015: * Here is the copyright notice from ImageMagick:
016: *
017: * <pre>
018: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
019: % Permission is hereby granted, free of charge, to any person obtaining a %
020: % copy of this software and associated documentation files ("ImageMagick"), %
021: % to deal in ImageMagick without restriction, including without limitation %
022: % the rights to use, copy, modify, merge, publish, distribute, sublicense, %
023: % and/or sell copies of ImageMagick, and to permit persons to whom the %
024: % ImageMagick is furnished to do so, subject to the following conditions: %
025: % %
026: % The above copyright notice and this permission notice shall be included in %
027: % all copies or substantial portions of ImageMagick. %
028: % %
029: % The software is provided "as is", without warranty of any kind, express or %
030: % implied, including but not limited to the warranties of merchantability, %
031: % fitness for a particular purpose and noninfringement. In no event shall %
032: % E. I. du Pont de Nemours and Company be liable for any claim, damages or %
033: % other liability, whether in an action of contract, tort or otherwise, %
034: % arising from, out of or in connection with ImageMagick or the use or other %
035: % dealings in ImageMagick. %
036: % %
037: % Except as contained in this notice, the name of the E. I. du Pont de %
038: % Nemours and Company shall not be used in advertising or otherwise to %
039: % promote the sale, use or other dealings in ImageMagick without prior %
040: % written authorization from the E. I. du Pont de Nemours and Company. %
041: % %
042: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
043: </pre>
044: *
045: *
046: * @version 0.90 19 Sep 2000
047: * @author <a href="http://www.gurge.com/amd/">Adam Doppelt</a>
048: */
049: public class Quantize {
050:
051: /*
052: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
053: % %
054: % %
055: % %
056: % QQQ U U AAA N N TTTTT IIIII ZZZZZ EEEEE %
057: % Q Q U U A A NN N T I ZZ E %
058: % Q Q U U AAAAA N N N T I ZZZ EEEEE %
059: % Q QQ U U A A N NN T I ZZ E %
060: % QQQQ UUU A A N N T IIIII ZZZZZ EEEEE %
061: % %
062: % %
063: % Reduce the Number of Unique Colors in an Image %
064: % %
065: % %
066: % Software Design %
067: % John Cristy %
068: % July 1992 %
069: % %
070: % %
071: % Copyright 1998 E. I. du Pont de Nemours and Company %
072: % %
073: % Permission is hereby granted, free of charge, to any person obtaining a %
074: % copy of this software and associated documentation files ("ImageMagick"), %
075: % to deal in ImageMagick without restriction, including without limitation %
076: % the rights to use, copy, modify, merge, publish, distribute, sublicense, %
077: % and/or sell copies of ImageMagick, and to permit persons to whom the %
078: % ImageMagick is furnished to do so, subject to the following conditions: %
079: % %
080: % The above copyright notice and this permission notice shall be included in %
081: % all copies or substantial portions of ImageMagick. %
082: % %
083: % The software is provided "as is", without warranty of any kind, express or %
084: % implied, including but not limited to the warranties of merchantability, %
085: % fitness for a particular purpose and noninfringement. In no event shall %
086: % E. I. du Pont de Nemours and Company be liable for any claim, damages or %
087: % other liability, whether in an action of contract, tort or otherwise, %
088: % arising from, out of or in connection with ImageMagick or the use or other %
089: % dealings in ImageMagick. %
090: % %
091: % Except as contained in this notice, the name of the E. I. du Pont de %
092: % Nemours and Company shall not be used in advertising or otherwise to %
093: % promote the sale, use or other dealings in ImageMagick without prior %
094: % written authorization from the E. I. du Pont de Nemours and Company. %
095: % %
096: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
097: %
098: % Realism in computer graphics typically requires using 24 bits/pixel to
099: % generate an image. Yet many graphic display devices do not contain
100: % the amount of memory necessary to match the spatial and color
101: % resolution of the human eye. The QUANTIZE program takes a 24 bit
102: % image and reduces the number of colors so it can be displayed on
103: % raster device with less bits per pixel. In most instances, the
104: % quantized image closely resembles the original reference image.
105: %
106: % A reduction of colors in an image is also desirable for image
107: % transmission and real-time animation.
108: %
109: % Function Quantize takes a standard RGB or monochrome images and quantizes
110: % them down to some fixed number of colors.
111: %
112: % For purposes of color allocation, an image is a set of n pixels, where
113: % each pixel is a point in RGB space. RGB space is a 3-dimensional
114: % vector space, and each pixel, pi, is defined by an ordered triple of
115: % red, green, and blue coordinates, (ri, gi, bi).
116: %
117: % Each primary color component (red, green, or blue) represents an
118: % intensity which varies linearly from 0 to a maximum value, cmax, which
119: % corresponds to full saturation of that color. Color allocation is
120: % defined over a domain consisting of the cube in RGB space with
121: % opposite vertices at (0,0,0) and (cmax,cmax,cmax). QUANTIZE requires
122: % cmax = 255.
123: %
124: % The algorithm maps this domain onto a tree in which each node
125: % represents a cube within that domain. In the following discussion
126: % these cubes are defined by the coordinate of two opposite vertices:
127: % The vertex nearest the origin in RGB space and the vertex farthest
128: % from the origin.
129: %
130: % The tree's root node represents the the entire domain, (0,0,0) through
131: % (cmax,cmax,cmax). Each lower level in the tree is generated by
132: % subdividing one node's cube into eight smaller cubes of equal size.
133: % This corresponds to bisecting the parent cube with planes passing
134: % through the midpoints of each edge.
135: %
136: % The basic algorithm operates in three phases: Classification,
137: % Reduction, and Assignment. Classification builds a color
138: % description tree for the image. Reduction collapses the tree until
139: % the number it represents, at most, the number of colors desired in the
140: % output image. Assignment defines the output image's color map and
141: % sets each pixel's color by reclassification in the reduced tree.
142: % Our goal is to minimize the numerical discrepancies between the original
143: % colors and quantized colors (quantization error).
144: %
145: % Classification begins by initializing a color description tree of
146: % sufficient depth to represent each possible input color in a leaf.
147: % However, it is impractical to generate a fully-formed color
148: % description tree in the classification phase for realistic values of
149: % cmax. If colors components in the input image are quantized to k-bit
150: % precision, so that cmax= 2k-1, the tree would need k levels below the
151: % root node to allow representing each possible input color in a leaf.
152: % This becomes prohibitive because the tree's total number of nodes is
153: % 1 + sum(i=1,k,8k).
154: %
155: % A complete tree would require 19,173,961 nodes for k = 8, cmax = 255.
156: % Therefore, to avoid building a fully populated tree, QUANTIZE: (1)
157: % Initializes data structures for nodes only as they are needed; (2)
158: % Chooses a maximum depth for the tree as a function of the desired
159: % number of colors in the output image (currently log2(colormap size)).
160: %
161: % For each pixel in the input image, classification scans downward from
162: % the root of the color description tree. At each level of the tree it
163: % identifies the single node which represents a cube in RGB space
164: % containing the pixel's color. It updates the following data for each
165: % such node:
166: %
167: % n1: Number of pixels whose color is contained in the RGB cube
168: % which this node represents;
169: %
170: % n2: Number of pixels whose color is not represented in a node at
171: % lower depth in the tree; initially, n2 = 0 for all nodes except
172: % leaves of the tree.
173: %
174: % Sr, Sg, Sb: Sums of the red, green, and blue component values for
175: % all pixels not classified at a lower depth. The combination of
176: % these sums and n2 will ultimately characterize the mean color of a
177: % set of pixels represented by this node.
178: %
179: % E: The distance squared in RGB space between each pixel contained
180: % within a node and the nodes' center. This represents the quantization
181: % error for a node.
182: %
183: % Reduction repeatedly prunes the tree until the number of nodes with
184: % n2 > 0 is less than or equal to the maximum number of colors allowed
185: % in the output image. On any given iteration over the tree, it selects
186: % those nodes whose E count is minimal for pruning and merges their
187: % color statistics upward. It uses a pruning threshold, Ep, to govern
188: % node selection as follows:
189: %
190: % Ep = 0
191: % while number of nodes with (n2 > 0) > required maximum number of colors
192: % prune all nodes such that E <= Ep
193: % Set Ep to minimum E in remaining nodes
194: %
195: % This has the effect of minimizing any quantization error when merging
196: % two nodes together.
197: %
198: % When a node to be pruned has offspring, the pruning procedure invokes
199: % itself recursively in order to prune the tree from the leaves upward.
200: % n2, Sr, Sg, and Sb in a node being pruned are always added to the
201: % corresponding data in that node's parent. This retains the pruned
202: % node's color characteristics for later averaging.
203: %
204: % For each node, n2 pixels exist for which that node represents the
205: % smallest volume in RGB space containing those pixel's colors. When n2
206: % > 0 the node will uniquely define a color in the output image. At the
207: % beginning of reduction, n2 = 0 for all nodes except a the leaves of
208: % the tree which represent colors present in the input image.
209: %
210: % The other pixel count, n1, indicates the total number of colors
211: % within the cubic volume which the node represents. This includes n1 -
212: % n2 pixels whose colors should be defined by nodes at a lower level in
213: % the tree.
214: %
215: % Assignment generates the output image from the pruned tree. The
216: % output image consists of two parts: (1) A color map, which is an
217: % array of color descriptions (RGB triples) for each color present in
218: % the output image; (2) A pixel array, which represents each pixel as
219: % an index into the color map array.
220: %
221: % First, the assignment phase makes one pass over the pruned color
222: % description tree to establish the image's color map. For each node
223: % with n2 > 0, it divides Sr, Sg, and Sb by n2 . This produces the
224: % mean color of all pixels that classify no lower than this node. Each
225: % of these colors becomes an entry in the color map.
226: %
227: % Finally, the assignment phase reclassifies each pixel in the pruned
228: % tree to identify the deepest node containing the pixel's color. The
229: % pixel's value in the pixel array becomes the index of this node's mean
230: % color in the color map.
231: %
232: % With the permission of USC Information Sciences Institute, 4676 Admiralty
233: % Way, Marina del Rey, California 90292, this code was adapted from module
234: % ALCOLS written by Paul Raveling.
235: %
236: % The names of ISI and USC are not used in advertising or publicity
237: % pertaining to distribution of the software without prior specific
238: % written permission from ISI.
239: %
240: */
241:
242: final static boolean QUICK = true;
243:
244: final static int MAX_RGB = 255;
245: final static int MAX_NODES = 266817;
246: final static int MAX_TREE_DEPTH = 8;
247:
248: // these are precomputed in advance
249: static int SQUARES[];
250: static int SHIFT[];
251:
252: static {
253: SQUARES = new int[MAX_RGB + MAX_RGB + 1];
254: for (int i = -MAX_RGB; i <= MAX_RGB; i++) {
255: SQUARES[i + MAX_RGB] = i * i;
256: }
257:
258: SHIFT = new int[MAX_TREE_DEPTH + 1];
259: for (int i = 0; i < MAX_TREE_DEPTH + 1; ++i) {
260: SHIFT[i] = 1 << (15 - i);
261: }
262: }
263:
264: /**
265: * Reduce the image to the given number of colors. The pixels are
266: * reduced in place.
267: * @return The new color palette.
268: */
269: public static int[] quantizeImage(int pixels[][], int max_colors) {
270: Cube cube = new Cube(pixels, max_colors);
271: cube.classification();
272: cube.reduction();
273: cube.assignment();
274: return cube.colormap;
275: }
276:
277: static class Cube {
278: int pixels[][];
279: int max_colors;
280: int colormap[];
281:
282: Node root;
283: int depth;
284:
285: // counter for the number of colors in the cube. this gets
286: // recalculated often.
287: int colors;
288:
289: // counter for the number of nodes in the tree
290: int nodes;
291:
292: Cube(int pixels[][], int max_colors) {
293: this .pixels = pixels;
294: this .max_colors = max_colors;
295:
296: int i = max_colors;
297: // tree_depth = log max_colors
298: // 4
299: for (depth = 1; i != 0; depth++) {
300: i /= 4;
301: }
302: if (depth > 1) {
303: --depth;
304: }
305: if (depth > MAX_TREE_DEPTH) {
306: depth = MAX_TREE_DEPTH;
307: } else if (depth < 2) {
308: depth = 2;
309: }
310:
311: root = new Node(this );
312: }
313:
314: /*
315: * Procedure Classification begins by initializing a color
316: * description tree of sufficient depth to represent each
317: * possible input color in a leaf. However, it is impractical
318: * to generate a fully-formed color description tree in the
319: * classification phase for realistic values of cmax. If
320: * colors components in the input image are quantized to k-bit
321: * precision, so that cmax= 2k-1, the tree would need k levels
322: * below the root node to allow representing each possible
323: * input color in a leaf. This becomes prohibitive because the
324: * tree's total number of nodes is 1 + sum(i=1,k,8k).
325: *
326: * A complete tree would require 19,173,961 nodes for k = 8,
327: * cmax = 255. Therefore, to avoid building a fully populated
328: * tree, QUANTIZE: (1) Initializes data structures for nodes
329: * only as they are needed; (2) Chooses a maximum depth for
330: * the tree as a function of the desired number of colors in
331: * the output image (currently log2(colormap size)).
332: *
333: * For each pixel in the input image, classification scans
334: * downward from the root of the color description tree. At
335: * each level of the tree it identifies the single node which
336: * represents a cube in RGB space containing It updates the
337: * following data for each such node:
338: *
339: * number_pixels : Number of pixels whose color is contained
340: * in the RGB cube which this node represents;
341: *
342: * unique : Number of pixels whose color is not represented
343: * in a node at lower depth in the tree; initially, n2 = 0
344: * for all nodes except leaves of the tree.
345: *
346: * total_red/green/blue : Sums of the red, green, and blue
347: * component values for all pixels not classified at a lower
348: * depth. The combination of these sums and n2 will
349: * ultimately characterize the mean color of a set of pixels
350: * represented by this node.
351: */
352: void classification() {
353: int pixels[][] = this .pixels;
354:
355: int width = pixels.length;
356: int height = pixels[0].length;
357:
358: // convert to indexed color
359: for (int x = width; x-- > 0;) {
360: for (int y = height; y-- > 0;) {
361: int pixel = pixels[x][y];
362: int red = (pixel >> 16) & 0xFF;
363: int green = (pixel >> 8) & 0xFF;
364: int blue = (pixel >> 0) & 0xFF;
365:
366: // a hard limit on the number of nodes in the tree
367: if (nodes > MAX_NODES) {
368: System.out.println("pruning");
369: root.pruneLevel();
370: --depth;
371: }
372:
373: // walk the tree to depth, increasing the
374: // number_pixels count for each node
375: Node node = root;
376: for (int level = 1; level <= depth; ++level) {
377: int id = (((red > node.mid_red ? 1 : 0) << 0)
378: | ((green > node.mid_green ? 1 : 0) << 1) | ((blue > node.mid_blue ? 1
379: : 0) << 2));
380: if (node.child[id] == null) {
381: new Node(node, id, level);
382: }
383: node = node.child[id];
384: node.number_pixels += SHIFT[level];
385: }
386:
387: ++node.unique;
388: node.total_red += red;
389: node.total_green += green;
390: node.total_blue += blue;
391: }
392: }
393: }
394:
395: /*
396: * reduction repeatedly prunes the tree until the number of
397: * nodes with unique > 0 is less than or equal to the maximum
398: * number of colors allowed in the output image.
399: *
400: * When a node to be pruned has offspring, the pruning
401: * procedure invokes itself recursively in order to prune the
402: * tree from the leaves upward. The statistics of the node
403: * being pruned are always added to the corresponding data in
404: * that node's parent. This retains the pruned node's color
405: * characteristics for later averaging.
406: */
407: void reduction() {
408: int threshold = 1;
409: while (colors > max_colors) {
410: colors = 0;
411: threshold = root.reduce(threshold, Integer.MAX_VALUE);
412: }
413: }
414:
415: /**
416: * The result of a closest color search.
417: */
418: static class Search {
419: int distance;
420: int color_number;
421: }
422:
423: /*
424: * Procedure assignment generates the output image from the
425: * pruned tree. The output image consists of two parts: (1) A
426: * color map, which is an array of color descriptions (RGB
427: * triples) for each color present in the output image; (2) A
428: * pixel array, which represents each pixel as an index into
429: * the color map array.
430: *
431: * First, the assignment phase makes one pass over the pruned
432: * color description tree to establish the image's color map.
433: * For each node with n2 > 0, it divides Sr, Sg, and Sb by n2.
434: * This produces the mean color of all pixels that classify no
435: * lower than this node. Each of these colors becomes an entry
436: * in the color map.
437: *
438: * Finally, the assignment phase reclassifies each pixel in
439: * the pruned tree to identify the deepest node containing the
440: * pixel's color. The pixel's value in the pixel array becomes
441: * the index of this node's mean color in the color map.
442: */
443: void assignment() {
444: colormap = new int[colors];
445:
446: colors = 0;
447: root.colormap();
448:
449: int pixels[][] = this .pixels;
450:
451: int width = pixels.length;
452: int height = pixels[0].length;
453:
454: Search search = new Search();
455:
456: // convert to indexed color
457: for (int x = width; x-- > 0;) {
458: for (int y = height; y-- > 0;) {
459: int pixel = pixels[x][y];
460: int red = (pixel >> 16) & 0xFF;
461: int green = (pixel >> 8) & 0xFF;
462: int blue = (pixel >> 0) & 0xFF;
463:
464: // walk the tree to find the cube containing that color
465: Node node = root;
466: for (;;) {
467: int id = (((red > node.mid_red ? 1 : 0) << 0)
468: | ((green > node.mid_green ? 1 : 0) << 1) | ((blue > node.mid_blue ? 1
469: : 0) << 2));
470: if (node.child[id] == null) {
471: break;
472: }
473: node = node.child[id];
474: }
475:
476: if (QUICK) {
477: // if QUICK is set, just use that
478: // node. Strictly speaking, this isn't
479: // necessarily best match.
480: pixels[x][y] = node.color_number;
481: } else {
482: // Find the closest color.
483: search.distance = Integer.MAX_VALUE;
484: node.parent.closestColor(red, green, blue,
485: search);
486: pixels[x][y] = search.color_number;
487: }
488: }
489: }
490: }
491:
492: /**
493: * A single Node in the tree.
494: */
495: static class Node {
496: Cube cube;
497:
498: // parent node
499: Node parent;
500:
501: // child nodes
502: Node child[];
503: int nchild;
504:
505: // our index within our parent
506: int id;
507: // our level within the tree
508: int level;
509: // our color midpoint
510: int mid_red;
511: int mid_green;
512: int mid_blue;
513:
514: // the pixel count for this node and all children
515: int number_pixels;
516:
517: // the pixel count for this node
518: int unique;
519: // the sum of all pixels contained in this node
520: int total_red;
521: int total_green;
522: int total_blue;
523:
524: // used to build the colormap
525: int color_number;
526:
527: Node(Cube cube) {
528: this .cube = cube;
529: this .parent = this ;
530: this .child = new Node[8];
531: this .id = 0;
532: this .level = 0;
533:
534: this .number_pixels = Integer.MAX_VALUE;
535:
536: this .mid_red = (MAX_RGB + 1) >> 1;
537: this .mid_green = (MAX_RGB + 1) >> 1;
538: this .mid_blue = (MAX_RGB + 1) >> 1;
539: }
540:
541: Node(Node parent, int id, int level) {
542: this .cube = parent.cube;
543: this .parent = parent;
544: this .child = new Node[8];
545: this .id = id;
546: this .level = level;
547:
548: // add to the cube
549: ++cube.nodes;
550: if (level == cube.depth) {
551: ++cube.colors;
552: }
553:
554: // add to the parent
555: ++parent.nchild;
556: parent.child[id] = this ;
557:
558: // figure out our midpoint
559: int bi = (1 << (MAX_TREE_DEPTH - level)) >> 1;
560: mid_red = parent.mid_red + ((id & 1) > 0 ? bi : -bi);
561: mid_green = parent.mid_green
562: + ((id & 2) > 0 ? bi : -bi);
563: mid_blue = parent.mid_blue + ((id & 4) > 0 ? bi : -bi);
564: }
565:
566: /**
567: * Remove this child node, and make sure our parent
568: * absorbs our pixel statistics.
569: */
570: void pruneChild() {
571: --parent.nchild;
572: parent.unique += unique;
573: parent.total_red += total_red;
574: parent.total_green += total_green;
575: parent.total_blue += total_blue;
576: parent.child[id] = null;
577: --cube.nodes;
578: cube = null;
579: parent = null;
580: }
581:
582: /**
583: * Prune the lowest layer of the tree.
584: */
585: void pruneLevel() {
586: if (nchild != 0) {
587: for (int id = 0; id < 8; id++) {
588: if (child[id] != null) {
589: child[id].pruneLevel();
590: }
591: }
592: }
593: if (level == cube.depth) {
594: pruneChild();
595: }
596: }
597:
598: /**
599: * Remove any nodes that have fewer than threshold
600: * pixels. Also, as long as we're walking the tree:
601: *
602: * - figure out the color with the fewest pixels
603: * - recalculate the total number of colors in the tree
604: */
605: int reduce(int threshold, int next_threshold) {
606: if (nchild != 0) {
607: for (int id = 0; id < 8; id++) {
608: if (child[id] != null) {
609: next_threshold = child[id].reduce(
610: threshold, next_threshold);
611: }
612: }
613: }
614: if (number_pixels <= threshold) {
615: pruneChild();
616: } else {
617: if (unique != 0) {
618: cube.colors++;
619: }
620: if (number_pixels < next_threshold) {
621: next_threshold = number_pixels;
622: }
623: }
624: return next_threshold;
625: }
626:
627: /*
628: * colormap traverses the color cube tree and notes each
629: * colormap entry. A colormap entry is any node in the
630: * color cube tree where the number of unique colors is
631: * not zero.
632: */
633: void colormap() {
634: if (nchild != 0) {
635: for (int id = 0; id < 8; id++) {
636: if (child[id] != null) {
637: child[id].colormap();
638: }
639: }
640: }
641: if (unique != 0) {
642: int r = ((total_red + (unique >> 1)) / unique);
643: int g = ((total_green + (unique >> 1)) / unique);
644: int b = ((total_blue + (unique >> 1)) / unique);
645: cube.colormap[cube.colors] = (((0xFF) << 24)
646: | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | ((b & 0xFF) << 0));
647: color_number = cube.colors++;
648: }
649: }
650:
651: /* ClosestColor traverses the color cube tree at a
652: * particular node and determines which colormap entry
653: * best represents the input color.
654: */
655: void closestColor(int red, int green, int blue,
656: Search search) {
657: if (nchild != 0) {
658: for (int id = 0; id < 8; id++) {
659: if (child[id] != null) {
660: child[id].closestColor(red, green, blue,
661: search);
662: }
663: }
664: }
665:
666: if (unique != 0) {
667: int color = cube.colormap[color_number];
668: int distance = distance(color, red, green, blue);
669: if (distance < search.distance) {
670: search.distance = distance;
671: search.color_number = color_number;
672: }
673: }
674: }
675:
676: /**
677: * Figure out the distance between this node and som color.
678: */
679: final static int distance(int color, int r, int g, int b) {
680: return (SQUARES[((color >> 16) & 0xFF) - r + MAX_RGB]
681: + SQUARES[((color >> 8) & 0xFF) - g + MAX_RGB] + SQUARES[((color >> 0) & 0xFF)
682: - b + MAX_RGB]);
683: }
684:
685: public String toString() {
686: StringBuffer buf = new StringBuffer();
687: if (parent == this ) {
688: buf.append("root");
689: } else {
690: buf.append("node");
691: }
692: buf.append(' ');
693: buf.append(level);
694: buf.append(" [");
695: buf.append(mid_red);
696: buf.append(',');
697: buf.append(mid_green);
698: buf.append(',');
699: buf.append(mid_blue);
700: buf.append(']');
701: return new String(buf);
702: }
703: }
704: }
705: }
|