001    /*
002     * LookupTableOperation
003     * 
004     * Copyright (c) 2001, 2002 Marco Schmidt <marcoschmidt@users.sourceforge.net>
005     * All rights reserved.
006     */
007    
008    package net.sourceforge.jiu.ops;
009    
010    import net.sourceforge.jiu.data.PixelImage;
011    import net.sourceforge.jiu.data.IntegerImage;
012    import net.sourceforge.jiu.ops.ImageToImageOperation;
013    import net.sourceforge.jiu.ops.MissingParameterException;
014    import net.sourceforge.jiu.ops.WrongParameterException;
015    
016    /**
017     * An operation that replaces samples with values taken from a lookup table.
018     * Operations where each pixel is treated independently from its neighbors
019     * and where a pixel value is always mapped to the same new pixel value
020     * can be implemented this way.
021     *
022     * @author Marco Schmidt
023     * @since 0.6.0
024     */
025    public abstract class LookupTableOperation extends ImageToImageOperation
026    {
027            private int[][] intTables;
028            private int numTables;
029    
030            /**
031             * Creates a LookupTableOperation for one lookup table.
032             */
033            public LookupTableOperation()
034            {
035                    this(1);
036            }
037    
038            /**
039             * Creates an object of this class, calling the super constructor with two <code>null</code>
040             * arguments and allocates space for the argument number of lookup tables.
041             * @param numTables number of tables to be used in this operation
042             */
043            public LookupTableOperation(int numTables)
044            {
045                    super(null, null);
046                    if (numTables < 1)
047                    {
048                            throw new IllegalArgumentException("The number of tables must be at least 1; got " + numTables);
049                    }
050                    intTables = new int[numTables][];
051                    this.numTables = numTables;
052            }
053    
054            /**
055             * Returns the number of tables in this operation.
056             * @return number of tables
057             */
058            public int getNumTables()
059            {
060                    return numTables;
061            }
062    
063            /**
064             * Returns one of the internal <code>int</code> lookup tables.
065             * @param channelIndex the zero-based index of the table to be returned;
066             *   from 0 to getNumTables() - 1
067             * @return the channelIndex'th table
068             */
069            public int[] getTable(int channelIndex)
070            {
071                    return intTables[channelIndex];
072            }
073    
074            public void prepareImages() throws
075                    MissingParameterException,
076                    WrongParameterException
077            {
078                    ensureInputImageIsAvailable();
079                    PixelImage in = getInputImage();
080                    if (!(in instanceof IntegerImage))
081                    {
082                            throw new WrongParameterException("Input image must be of type IntegerImage.");
083                    }
084                    PixelImage out = getOutputImage();
085                    if (out == null)
086                    {
087                            out = in.createCompatibleImage(in.getWidth(), in.getHeight());
088                            setOutputImage(out);
089                    }
090                    else
091                    {
092                            if (in.getNumChannels() != out.getNumChannels())
093                            {
094                                    throw new WrongParameterException("Output image must have same number of channels as input image.");
095                            }
096                            ensureImagesHaveSameResolution();
097                    }
098            }
099    
100            public void process() throws
101                    MissingParameterException,
102                    WrongParameterException
103            {
104                    prepareImages();
105                    process((IntegerImage)getInputImage(), (IntegerImage)getOutputImage());
106            }
107    
108            private void process(IntegerImage in, IntegerImage out)
109            {
110                    boolean useFirstTableOnly = getNumTables() < in.getNumChannels();
111                    final int TOTAL_ITEMS = in.getHeight() * in.getNumChannels();
112                    int processedItems = 0;
113                    for (int channelIndex = 0; channelIndex < in.getNumChannels(); channelIndex++)
114                    {
115                            int tableIndex;
116                            if (useFirstTableOnly)
117                            {
118                                    tableIndex = 0;
119                            }
120                            else
121                            {
122                                    tableIndex = channelIndex;
123                            }
124                            process(in, out, channelIndex, tableIndex, processedItems, TOTAL_ITEMS);
125                            processedItems += in.getHeight();
126                    }
127            }
128    
129            private void process(IntegerImage in, IntegerImage out, final int CHANNEL_INDEX,
130                    int tableIndex, int processedItems, final int TOTAL_ITEMS)
131            {
132                    final int[] TABLE = getTable(tableIndex);
133                    final int WIDTH = in.getWidth();
134                    final int HEIGHT = in.getHeight();
135                    for (int y = 0; y < HEIGHT; y++)
136                    {
137                            for (int x = 0; x < WIDTH; x++)
138                            {
139                                    out.putSample(CHANNEL_INDEX, x, y, TABLE[in.getSample(CHANNEL_INDEX, x, y)]);
140                            }
141                            setProgress(processedItems++, TOTAL_ITEMS);
142                    }
143            }
144    
145            /**
146             * Resets the number of tables to be used in this operation to the 
147             * argument and drops all actual table data initialized so far.
148             * After a call to this method, {@link #getTable} will return
149             * <code>null</code> as long as no new table data is provided
150             * via {@link #setTable} or {@link #setTables}.
151             * @param numberOfTables the new number of tables for this operation, must be <code>1</code> or larger
152             * @throws IllegalArgumentException if the number is zero or smaller
153             */
154            public void setNumTables(int numberOfTables)
155            {
156                    if (numberOfTables < 1)
157                    {
158                            throw new IllegalArgumentException("Number of tables argument must be larger than zero.");
159                    }
160                    numTables = numberOfTables;
161                    intTables = new int[numTables][];
162            }
163    
164            /**
165             * Provides a new lookup table for one of the channels.
166             * @param channelIndex the index of the channel for which a table is provided; must be at least <code>0</code> and smaller than {@link #getNumTables}
167             * @param tableData the actual table to be used for lookup
168             * @throws IllegalArgumentException if the channel index is not in the valid interval (see above)
169             */
170            public void setTable(int channelIndex, int[] tableData)
171            {
172                    if (channelIndex < 0)
173                    {
174                            throw new IllegalArgumentException("The channelIndex argument must be at least 0; got " + channelIndex);
175                    }
176                    if (channelIndex >= getNumTables())
177                    {
178                            throw new IllegalArgumentException("The channelIndex argument must be smaller than the number of tables " + 
179                                    getNumTables() + "; got " + channelIndex);
180                    }
181                    intTables[channelIndex] = tableData;
182            }
183    
184            /**
185             * Sets the tables for all channels to the argument table.
186             * Useful when the same table can be used for all channels.
187             * @param tableData the data that will be used as lookup table for all channels
188             */
189            public void setTables(int[] tableData)
190            {
191                    for (int i = 0; i < getNumTables(); i++)
192                    {
193                            setTable(i, tableData);
194                    }
195            }
196    }