001    /*
002     * MedianCutNode
003     * 
004     * Copyright (c) 2001, 2002, 2003 Marco Schmidt.
005     * All rights reserved.
006     */
007    
008    package net.sourceforge.jiu.color.quantization;
009    
010    import net.sourceforge.jiu.color.quantization.RGBColor;
011    import net.sourceforge.jiu.data.RGBIndex;
012    
013    /**
014     * An instance of this node class represents a cuboid part 
015     * of the color cube representing the three-dimensional RGB color space.
016     * @author Marco Schmidt
017     * @see MedianCutQuantizer
018     */
019    public class MedianCutNode implements RGBIndex
020    {
021            private int axis;
022            private boolean axisDetermined;
023            private int diff;
024            private int index1;
025            private int index2;
026            private MedianCutNode leftSuccessor;
027            private int[] max;
028            private int medianValue;
029            private int middleIndex;
030            private int[] min;
031            private int paletteIndex;
032            private MedianCutNode parent;
033            private int[] reprColor;
034            private MedianCutNode rightSuccessor;
035    
036            /**
037             * Creates a node for a Median Cut tree of nodes with index values for
038             * some external color array and the parent node.
039             * This parent is null for the root node.
040             * @param parent the parent node of this new node, should be null only for the root node
041             * @param index1 the index value of the first element of colors in the color list
042             * @param index2 the index value of the last element of colors in the color list; must be larger than or equal to index1
043             * @throws IllegalArgumentException if index1 is larger than index2
044             */
045            public MedianCutNode(MedianCutNode parent, int index1, int index2)
046            {
047                    if (index1 > index2)
048                    {
049                            throw new IllegalArgumentException("MedianCutNode constructor, index1 must be smaller than or equal to index2.");
050                    }
051                    this.parent = parent;
052                    this.index1 = index1;
053                    this.index2 = index2;
054                    determineMiddleIndex();
055                    leftSuccessor = null;
056                    rightSuccessor = null;
057                    diff = 0;
058                    axisDetermined = false;
059                    max = new int[3];
060                    min = new int[3];
061                    paletteIndex = -1;
062            }
063    
064            /**
065             * Returns if this node can be split into two.
066             * This is true if and only if this is a leaf and if the color
067             * list index values represent an interval of at least length 2.
068             * @return if this node can be split into two nodes
069             */
070            public boolean canBeSplit()
071            {
072                    return isLeaf() && index1 != index2;
073            }
074    
075            /**
076             * Computes the distance in RGB color space between the representative color of this node and the
077             * argument node and returns it as non-negative value.
078             */
079            public double computeRgbDistance(MedianCutNode node)
080            {
081                    int[] c = node.reprColor;
082                    return RGBColor.computeDistance(reprColor[INDEX_RED], reprColor[INDEX_GREEN], reprColor[INDEX_BLUE], c[INDEX_RED], c[INDEX_GREEN], c[INDEX_BLUE]);
083            }
084    
085            /**
086             * Computes the middle index value of this node.
087             * It uses the index values given to this node's constructor, index1 and index2.
088             */
089            private void determineMiddleIndex()
090            {
091                    if (index1 == index2)
092                    {
093                            middleIndex = index1;
094                    }
095                    else
096                    {
097                            middleIndex = (index1 + index2) / 2;
098                    }
099            }
100    
101            /**
102             * Returns the axis of the channel whose samples are most widely
103             * distributed among the colors that belong to this node.
104             * @return index of axis, one of the {@link RGBIndex} constants
105             * @throws IllegalArgumentException if that axis has not been determined
106             */
107            public int getAxisOfLargestDistribution()
108            {
109                    if (axisDetermined)
110                    {
111                            return axis;
112                    }
113                    else
114                    {
115                            throw new IllegalArgumentException("The axis has not been determined and can thus not be returned.");
116                    }
117            }
118    
119            public int getDifferenceOfLargestDistribution()
120            {
121                    if (axisDetermined)
122                    {
123                            return diff;
124                    }
125                    else
126                    {
127                            throw new IllegalArgumentException("The axis has not been determined and can thus not be returned.");
128                    }
129            }
130    
131            public int getLeftIndex()
132            {
133                    return index1;
134            }
135    
136            /**
137             * Returns left successor node (or null if this node is a leaf).
138             */
139            public MedianCutNode getLeftSuccessor()
140            {
141                    return leftSuccessor;
142            }
143    
144            public int getMaxColorSample(int index)
145            {
146                    return max[index];
147            }
148    
149            public int getMedianValue()
150            {
151                    return medianValue;
152            }
153    
154            public int getMiddleIndex()
155            {
156                    return middleIndex;
157            }
158    
159            public int getMinColorSample(int index)
160            {
161                    return min[index];
162            }
163    
164            public int getNumColors()
165            {
166                    return index2 - index1 + 1;
167            }
168    
169            public int getPaletteIndex()
170            {
171                    return paletteIndex;
172            }
173    
174            /**
175             * Returns parent node (or null if this node is the root node).
176             */
177            public MedianCutNode getParentNode()
178            {
179                    return parent;
180            }
181    
182            public int[] getRepresentativeColor()
183            {
184                    return reprColor;
185            }
186    
187            public int getRightIndex()
188            {
189                    return index2;
190            }
191    
192            /**
193             * Returns right successor node (or null if this node is a leaf).
194             */
195            public MedianCutNode getRightSuccessor()
196            {
197                    return rightSuccessor;
198            }
199    
200            public MedianCutNode getSuccessor(int[] rgb)
201            {
202                    if (rgb[axis] <= medianValue)
203                    {
204                            return leftSuccessor;
205                    }
206                    else
207                    {
208                            return rightSuccessor;
209                    }
210            }
211    
212            public boolean isAxisDetermined()
213            {
214                    return axisDetermined;
215            }
216    
217            /**
218             * Returns if this node is a leaf by checking if both successors are null.
219             * Note that the case of one successor being null and the other non-null
220             * should never happen.
221             * @return if this node is a leaf (true)
222             */
223            public boolean isLeaf()
224            {
225                    return (leftSuccessor == null && rightSuccessor == null);
226            }
227    
228            public void setLargestDistribution(int newAxis, int newDifference)
229            {
230                    if (newAxis != INDEX_RED && newAxis != INDEX_GREEN && newAxis != INDEX_BLUE)
231                    {
232                            throw new IllegalArgumentException("Axis must be either INDEX_RED, INDEX_GREEN or INDEX_BLUE.");
233                    }
234                    axis = newAxis;
235                    diff = newDifference;
236                    axisDetermined = true;
237            }
238    
239            public void setMaxColor(int red, int green, int blue)
240            {
241                    max[INDEX_RED] = red;
242                    max[INDEX_GREEN] = green;
243                    max[INDEX_BLUE] = blue;
244            }
245    
246            public void setMaxColorSample(int index, int value)
247            {
248                    max[index] = value;
249            }
250    
251            public void setMedianValue(int newMedianValue)
252            {
253                    medianValue = newMedianValue;
254            }
255    
256            public void setMinColor(int red, int green, int blue)
257            {
258                    min[INDEX_RED] = red;
259                    min[INDEX_GREEN] = green;
260                    min[INDEX_BLUE] = blue;
261            }
262    
263            public void setMinColorSample(int index, int value)
264            {
265                    min[index] = value;
266            }
267    
268            public void setPaletteIndex(int newPaletteIndex)
269            {
270                    paletteIndex = newPaletteIndex;
271            }
272    
273            public void setRepresentativeColor(int[] aRepresentativeColor)
274            {
275                    if (aRepresentativeColor != null && aRepresentativeColor.length != 3)
276                    {
277                            throw new IllegalArgumentException("Representative color array argument must have a length of 3.");
278                    }
279                    reprColor = aRepresentativeColor;
280            }
281    
282            /**
283             * Sets the successor nodes for this node.
284             * The successors must be either both null or both initialized.
285             * They must not be equal.
286             * @param left the left successor node
287             * @param right the left successor node
288             */
289            public void setSuccessors(MedianCutNode left, MedianCutNode right)
290            {
291                    if ((left == null && right != null) ||
292                        (left != null && right == null))
293                    {
294                            throw new IllegalArgumentException("The successor nodes must be either both null or both initialized    .");
295                    }
296                    if (left != null && left == right)
297                    {
298                            throw new IllegalArgumentException("The successor nodes must not be the same.");
299                    }
300                    leftSuccessor = left;
301                    rightSuccessor = right;
302            }
303    }