001    /*
002     * OnDemandHistogram3D
003     *
004     * Copyright (c) 2000, 2001, 2002 Marco Schmidt <marcoschmidt@users.sourceforge.net>
005     * All rights reserved.
006     */
007    
008    package net.sourceforge.jiu.color.data;
009    
010    import net.sourceforge.jiu.color.data.Histogram3D;
011    
012    /**
013     * A data class for a three-dimensional histogram, creating counters on demand only,
014     * not allocating counters for all possible entries at the beginning.
015     * The creation on demand happens to save space.
016     * A naive implementation can become huge - for eight bits per component,
017     * you'd need 2<sup>(8 + 8 + 8)</sup> = 2<sup>24</sup> = 16,777,216 
018     * int values (64 MB), while a typical 24 bit image uses only a fraction of
019     * these possible colors.
020     *
021     * @author Marco Schmidt
022     */
023    public class OnDemandHistogram3D implements Histogram3D
024    {
025            private int c1;
026            private int c2;
027            private int c3;
028            private int[] compOrder;
029            private int maxValue1;
030            private int maxValue2;
031            private int maxValue3;
032            private int[] maxValue;
033            private int[] vector;
034            private Object[] top;
035    
036            /**
037             * Creates a new object of this class with specified maximum values
038             * for all three indexes.
039             * @param maxValue1 the maximum value for the first index
040             * @param maxValue2 the maximum value for the second index
041             * @param maxValue3 the maximum value for the third index
042             * @param c1 the top level component for the internal tree
043             * @param c2 the second-level component for the internal tree
044             * @param c3 the third-level component for the internal tree
045             */
046            public OnDemandHistogram3D(
047                    int maxValue1, int maxValue2, int maxValue3,
048                    int c1, int c2, int c3) throws IllegalArgumentException
049            {
050                    if (maxValue1 < 1 || maxValue2 < 1 || maxValue3 < 1)
051                    {
052                            throw new IllegalArgumentException("The three maximum value arguments must all be larger than zero.");
053                    }
054                    this.maxValue1 = maxValue1;
055                    this.maxValue2 = maxValue2;
056                    this.maxValue3 = maxValue3;
057                    maxValue = new int[3];
058                    maxValue[0] = maxValue1;
059                    maxValue[1] = maxValue2;
060                    maxValue[2] = maxValue3;
061                    if (c1 < 0 || c1 > 2 || c2 < 0 || c2 > 2 || c3 < 0 || c3 > 2)
062                    {
063                            throw new IllegalArgumentException("Arguments for components must be from 0..2.");
064                    }
065                    if (c1 == c2 || c1 == c3 || c2 == c3)
066                    {
067                            throw new IllegalArgumentException("No two arguments for components are allowed to be equal.");
068                    }
069                    this.c1 = c1;
070                    this.c2 = c2;
071                    this.c3 = c3;
072                    compOrder = new int[3];
073                    compOrder[0] = c1;
074                    compOrder[1] = c2;
075                    compOrder[2] = c3;
076                    vector = new int[3];
077                    clear();
078            }
079    
080            /**
081             * Creates a new object of this class with maximum values as specified
082             * by the arguments and the component values 0, 1, 2.
083             * Simply calls <code>this(maxValue1, maxValue2, maxValue3, 0, 1, 2);</code>
084             * @param maxValue1 the maximum value for the first index
085             * @param maxValue2 the maximum value for the second index
086             * @param maxValue3 the maximum value for the third index
087             */
088            public OnDemandHistogram3D(int maxValue1, int maxValue2, int maxValue3)
089            {
090                    this(maxValue1, maxValue2, maxValue3, 0, 1, 2);
091            }
092    
093            /**
094             * Creates a new object of this class with the argument as maximum value for
095             * all three index positions and the component values 0, 1, 2.
096             * Simply calls <code>this(maxValue, maxValue, maxValue);</code>
097             * @param maxValue the maximum value for all indexes
098             */
099            public OnDemandHistogram3D(int maxValue)
100            {
101                    this(maxValue, maxValue, maxValue);
102            }
103    
104            /**
105             * Resets all counters to zero.
106             * As the Java VM's garbage collector is responsible for releasing unused
107             * memory, it is unclear when memory allocated for the histogram will be
108             * available again.
109             */
110            public void clear()
111            {
112                    top = createObjectArray(maxValue[c1] + 1);
113            }
114    
115            /**
116             * Creates an array of int values, initializes all values to 0 and returns
117             * the newly-allocated array.
118             * @param LENGTH the number of entries in the array to be allocated
119             * @return the resulting array
120             */
121            private int[] createIntArray(final int LENGTH)
122            {
123                    int[] result = new int[LENGTH];
124                    for (int i = 0; i < LENGTH; i++)
125                    {
126                            result[i] = 0;
127                    }
128                    return result;
129            }
130    
131            /**
132             * Creates an array of objects, initializes all values to null and
133             * returns this new array.
134             * @param LENGTH the number of entries of the new int array to be returned
135             */
136            private Object[] createObjectArray(final int LENGTH)
137            {
138                    Object[] result = new Object[LENGTH];
139                    for (int i = 0; i < LENGTH; i++)
140                    {
141                            result[i] = null;
142                    }
143                    return result;
144            }
145    
146            /**
147             * Returns counter for the argument color given by its red, green and
148             * blue intensity.
149             * @param index1 the first value of the index
150             * @param index2 
151             * @param index3 
152             * @throws IllegalArgumentException this exception is thrown if the 
153             *  argument color is not in the valid interval, i.e., if at least one
154             *  of the components is not from 0 .. max
155             */
156            public int getEntry(int index1, int index2, int index3) throws IllegalArgumentException
157            {
158                    if (index1 < 0 && index1 > maxValue1 &&
159                        index2 < 0 && index2 > maxValue2 &&
160                        index3 < 0 && index3 > maxValue3)
161                    {
162                            throw new IllegalArgumentException("Invalid value " + index1 + " " + index2 + " " + index3);
163                    }
164                    vector[0] = index1;
165                    vector[1] = index2;
166                    vector[2] = index3;
167                    Object[] second = (Object[])top[vector[compOrder[0]]];
168                    if (second == null)
169                    {
170                            return 0;
171                    }
172                    int[] counters = (int[])second[vector[compOrder[1]]];
173                    if (counters == null)
174                    {
175                            return 0;
176                    }
177                    return counters[vector[compOrder[2]]];
178            }
179    
180            public int getMaxValue(int index) throws IllegalArgumentException
181            {
182                    if (index >= 0 && index <= 2)
183                    {
184                            return maxValue[index];
185                    }
186                    else
187                    {
188                            throw new IllegalArgumentException("The index argument must be " +
189                                    "from 0 to 2; got " + index);
190                    }
191            }
192    
193            /**
194             * Returns the number of entries in this histogram with a counter value of one
195             * or higher (in other words: the number of colors that are in use).
196             * @return the number of unique colors
197             */
198            public int getNumUsedEntries()
199            {
200                    int result = 0;
201                    for (int i1 = 0; i1 <= maxValue[c1]; i1++)
202                    {
203                            if (top[i1] != null)
204                            {
205                                    Object[] second = (Object[])top[i1];
206                                    if (second != null)
207                                    {
208                                            for (int i2 = 0; i2 <= maxValue[c2]; i2++)
209                                            {
210                                                    if (second[i2] != null)
211                                                    {
212                                                            int[] third = (int[])second[i2];
213                                                            for (int i3 = 0; i3 <= maxValue[c3]; i3++)
214                                                            {
215                                                                    if (third[i3] != 0)
216                                                                    {
217                                                                            result++;
218                                                                    }
219                                                            }
220                                                    }
221                                            }
222                                    }
223                            }
224                    }
225                    return result;
226            }
227    
228            /**
229             * Increases the counter for the color given by the arguments red, green and blue.
230             * This method could be implemented by the following code snippet:
231             * <pre>
232             *  setEntry(red, green, blue, getEntry(red, green, blue) + 1);
233             * </pre>However, this is not done to avoid slow-downs by accessing a counter value twice.
234             *
235             * @param red the red intensity of the color entry whose counter will be increased
236             * @param green the green intensity of the color entry whose counter will be increased
237             * @param blue the blue intensity of the color entry whose counter will be increased
238             * @throws IllegalArgumentException if the argument color is not valid 
239             * (minimum is 0, maximum is defined for each component independently)
240             */
241            public void increaseEntry(int red, int green, int blue) throws IllegalArgumentException
242            {
243                    if (red < 0 || red > maxValue1 ||
244                        green < 0 || green > maxValue2 ||
245                        blue < 0 || blue > maxValue3)
246                    {
247                            throw new IllegalArgumentException("Invalid color value in increaseEntry(): " +
248                                    red + " " + green + " " + blue);
249                    }
250                    vector[0] = red;
251                    vector[1] = green;
252                    vector[2] = blue;
253                    int index1 = vector[compOrder[0]];
254                    Object[] second = (Object[])(top[index1]);
255                    if (second == null)
256                    {
257                            int num = maxValue[compOrder[1]] + 1;
258                            top[index1] = createObjectArray(num);
259                            second = (Object[])(top[index1]);
260                    }
261                    int index2 = vector[compOrder[1]];
262                    int[] counters = (int[])(second[index2]);
263                    if (counters == null)
264                    {
265                            int num = maxValue[compOrder[2]] + 1;
266                            second[index2] = createIntArray(num);
267                            counters = (int[])(second[index2]);
268                    }
269                    int index3 = vector[compOrder[2]];
270                    counters[index3]++;
271            }
272    
273            /**
274             * Sets one counter to a new value.
275             * @param r red component
276             * @param g green component
277             * @param b blue component
278             * @param newValue the new value for the counter
279             * @throws IllegalArgumentException if the components do not form a valid color 
280             */
281            public void setEntry(int r, int g, int b, int newValue) throws IllegalArgumentException
282            {
283                    if (r < 0 || r > maxValue1 ||
284                        g < 0 || g > maxValue2 ||
285                        b < 0 || b > maxValue3)
286                    {
287                            throw new IllegalArgumentException("Invalid index triplet: " +
288                                    r + " " + g + " " + b);
289                    }
290                    vector[0] = r;
291                    vector[1] = g;
292                    vector[2] = b;
293                    int index1 = vector[compOrder[0]];
294                    Object[] second = (Object[])(top[index1]);
295                    if (second == null)
296                    {
297                            int num = maxValue[compOrder[1]] + 1;
298                            top[index1] = createObjectArray(num);
299                            second = (Object[])(top[index1]);
300                    }
301                    int index2 = vector[compOrder[1]];
302                    int[] counters = (int[])(second[index2]);
303                    if (counters == null)
304                    {
305                            int num = maxValue[compOrder[2]] + 1;
306                            second[index2] = createIntArray(num);
307                            counters = (int[])(second[index2]);
308                    }
309                    int index3 = vector[compOrder[2]];
310                    counters[index3] = newValue;
311            }
312    }