001    /*
002     * Palette
003     *
004     * Copyright (c) 2000, 2001, 2002, 2003 Marco Schmidt.
005     * All rights reserved.
006     */
007    
008    package net.sourceforge.jiu.data;
009    
010    import net.sourceforge.jiu.data.RGBIndex;
011    
012    /**
013     * This class represents a palette, a list of RGB colors.
014     * An RGB color here has three int values for its red, green and blue
015     * intensity.
016     * Each intensity value must be larger than or equal to zero and
017     * smaller than or equal to the maximum intensity value that can be
018     * given to the constructor {@link #Palette(int, int)}.
019     * This maximum value is typically 255.
020     * Note that the number of entries in a palette is restricted only
021     * by the element index type <code>int</code> so that palettes with
022     * more than 256 entries are no problem.
023     * When accessing (reading or writing) samples of this palette, use
024     * the constants {@link #INDEX_RED}, {@link #INDEX_GREEN} and {@link #INDEX_BLUE} of
025     * this class to define a color channel.
026     * @author Marco Schmidt
027     * @see net.sourceforge.jiu.data.PalettedImage
028     */
029    public class Palette implements RGBIndex
030    {
031            private int[][] data;
032            private int numEntries;
033            private int maxValue;
034    
035            /**
036             * Create a palette with the given number of entries and a maximum value
037             * for each sample.
038             * @param numEntries the number of entries to be accessible in this palette
039             * @param maxValue the maximum value to be allowed for each sample
040             */
041            public Palette(int numEntries, int maxValue)
042            {
043                    if (numEntries < 1)
044                    {
045                            throw new IllegalArgumentException("Error -- numEntries must be larger than 0.");
046                    }
047                    this.numEntries = numEntries;
048                    this.maxValue = maxValue;
049                    data = new int[3][];
050                    for (int i = 0; i < 3; i++)
051                    {
052                            data[i] = new int[numEntries];
053                    }
054            }
055    
056            /**
057             * Create a palette with the given number of entries and a maximum value
058             * of <code>255</code>.
059             * @param numEntries the number of entries to be accessible in this palette
060             */
061            public Palette(int numEntries)
062            {
063                    this(numEntries, 255);
064            }
065    
066            /**
067             * Creates a copy of this palette, allocating a new Palette object
068             * and copying each RGB triplet to the new palette.
069             * Then returns the new palette.
070             * Thus, a "deep" copy of this Palette object is created,
071             * not a "shallow" one.
072             *
073             * @return newly-created palette
074             */
075            public Object clone()
076            {
077                    Palette result = new Palette(getNumEntries(), getMaxValue());
078                    for (int i = 0; i < getNumEntries(); i++)
079                    {
080                            result.putSample(INDEX_RED, i, getSample(INDEX_RED, i));
081                            result.putSample(INDEX_GREEN, i, getSample(INDEX_GREEN, i));
082                            result.putSample(INDEX_BLUE, i, getSample(INDEX_BLUE, i));
083                    }
084                    return result;
085            }
086    
087            /**
088             * Returns the amount of memory in bytes allocated for this palette.
089             *
090             */
091            public long getAllocatedMemory()
092            {
093                    long result = 0;
094                    if (data != null)
095                    {
096                            for (int i = 0; i < data.length; i++)
097                            {
098                                    if (data[i] != null)
099                                    {
100                                            result += data[i].length;
101                                    }
102                            }
103                    }
104                    return result;
105            }
106    
107            /**
108             * Returns the maximum value allowed for a sample.
109             * @return the maximum sample value
110             */
111            public int getMaxValue()
112            {
113                    return maxValue;
114            }
115    
116            /**
117             * Returns the number of entries in this palette.
118             * @return the number of entries in this palette
119             */
120            public int getNumEntries()
121            {
122                    return numEntries;
123            }
124    
125            /**
126             * Returns one of the samples of this palette.
127             * @param entryIndex the index of the color to be addressed, must be from
128             *  <code>0</code> to <code>getNumEntries() - 1</code>
129             * @param channelIndex one of the three channels; must be {@link #INDEX_RED},
130             *  {@link #INDEX_GREEN} or {@link #INDEX_BLUE}
131             * @return the requested sample
132             */
133            public int getSample(int channelIndex, int entryIndex)
134            {
135                    try
136                    {
137                            return data[channelIndex][entryIndex];
138                    }
139                    catch (ArrayIndexOutOfBoundsException aioobe)
140                    {
141                            throw new IllegalArgumentException("Entry must be from 0 to " + (numEntries - 1) +
142                                    ", channel from 0 to 2.");
143                    }
144            }
145    
146            /**
147             * Returns all samples of one channel as an int array.
148             * @param channelIndex index of the channel, one of the {@link RGBIndex} constants
149             * @return array with samples
150             */
151            public int[] getSamples(int channelIndex)
152            {
153                    if (channelIndex < 0 || channelIndex > 2)
154                    {
155                            throw new IllegalArgumentException("Invalid channel index, must be from 0 to 2.");
156                    }
157                    int[] result = new int[data[channelIndex].length];
158                    System.arraycopy(data[channelIndex], 0, result, 0, result.length);
159                    return result;
160            }
161    
162            /**
163             * Checks if all entries of this palette are either black or white.
164             * An entry is black if all three intensitites (red, green and blue) are
165             * <code>0</code>, it is white if they are all equal to 
166             * {@link #getMaxValue()}.
167             * No particular order of entries (e.g. first color black, second white)
168             * is demanded and no specific number of entries (e.g. 2).
169             * This means that a palette is black and white if it contains ten entries
170             * that are all black.
171             *
172             * @return if the palette contains only the colors black and white
173             */
174            public boolean isBlackAndWhite()
175            {
176                    int i = 0;
177                    while (i < numEntries)
178                    {
179                            if (data[INDEX_RED][i] != data[INDEX_GREEN][i] ||
180                                data[INDEX_GREEN][i] != data[INDEX_BLUE][i] ||
181                                (data[INDEX_BLUE][i] != 0 && data[INDEX_BLUE][i] != maxValue))
182                            {
183                                    return false;
184                            }
185                            i++;
186                    }
187                    return true;
188            }
189    
190            /**
191             * Checks if this palette is gray, i.e., checks if all entries are
192             * gray. This is the case if for all entries red, green and blue
193             * have the same intensity.
194             * @return if the palette contains only shades of gray
195             */
196            public boolean isGray()
197            {
198                    int i = 0;
199                    while (i < numEntries)
200                    {
201                            if (data[INDEX_RED][i] != data[INDEX_GREEN][i] ||
202                                data[INDEX_GREEN][i] != data[INDEX_BLUE][i])
203                            {
204                                    return false;
205                            }
206                            i++;
207                    }
208                    return true;
209            }
210    
211            public void put(int entryIndex, int red, int green, int blue)
212            {
213                    putSample(INDEX_RED, entryIndex, red);
214                    putSample(INDEX_GREEN, entryIndex, green);
215                    putSample(INDEX_BLUE, entryIndex, blue);
216            }
217    
218            /**
219             * Sets one sample of one color entry in the palette to a new value.
220             * @param channelIndex 
221             */
222            public void putSample(int channelIndex, int entryIndex, int newValue)
223            {
224                    if (newValue < 0 || newValue > maxValue)
225                    {
226                            throw new IllegalArgumentException("Value must be from 0 to " +
227                                    maxValue + "; argument is " + newValue + ".");
228                    }
229                    try
230                    {
231                            data[channelIndex][entryIndex] = newValue;
232                    }
233                    catch (ArrayIndexOutOfBoundsException aioobe)
234                    {
235                            throw new IllegalArgumentException("Entry must be from 0 to " + (numEntries - 1) +
236                                    ", channel from 0 to 2.");
237                    }
238            }
239    }