001    /*
002     * GammaCorrection
003     * 
004     * Copyright (c) 2001, 2002, 2003 Marco Schmidt.
005     * All rights reserved.
006     */
007    
008    package net.sourceforge.jiu.color.adjustment;
009    
010    import net.sourceforge.jiu.data.GrayIntegerImage;
011    import net.sourceforge.jiu.data.IntegerImage;
012    import net.sourceforge.jiu.data.Palette;
013    import net.sourceforge.jiu.data.Paletted8Image;
014    import net.sourceforge.jiu.data.PixelImage;
015    import net.sourceforge.jiu.data.RGBIntegerImage;
016    import net.sourceforge.jiu.ops.LookupTableOperation;
017    import net.sourceforge.jiu.ops.MissingParameterException;
018    import net.sourceforge.jiu.ops.WrongParameterException;
019    
020    /**
021     * Corrects the gamma of an image.
022     * Works with {@link net.sourceforge.jiu.data.GrayIntegerImage}, 
023     * {@link net.sourceforge.jiu.data.RGBIntegerImage} and 
024     * {@link net.sourceforge.jiu.data.Paletted8Image}.
025     * Only the palette is manipulated for paletted images.
026     * <p>
027     * Changes intensity values by applying the formula
028     * <em>f(x) = MAX * (x / MAX)<sup>(1 / gamma)</sup></em> to each
029     * <em>x</em> from <em>[0 ; MAX]</em> to them.
030     * The <em>MAX</em> value is the maximum value allowed for an intensity value of the
031     * corresponding channel.
032     * It is determined by calling {@link net.sourceforge.jiu.data.IntegerImage#getMaxSample} on
033     * the input image.
034     * The <em>gamma</em> parameter must be given to a <code>GammaCorrection</code> operation
035     * before the call to {@link #process} is made.
036     * The valid interval for <em>gamma</em> is (0.0 ; {@link #MAX_GAMMA}] 
037     * (so 0.0 is not a valid value).
038     * Gamma values smaller than 1 will make the image darker, values 
039     * larger than 1 will make it brighter.
040     * <h3>Usage example</h3>
041     * <pre>
042     * GammaCorrection gamma = new GammaCorrection();
043     * gamma.setInputImage(image);
044     * gamma.setGamma(2.2);
045     * gamma.process();
046     * PixelImage correctedImage = gamma.getOutputImage();
047     * </pre>
048     * @author Marco Schmidt
049     */
050    public class GammaCorrection extends LookupTableOperation
051    {
052            /**
053             * The maximum allowed value for gamma.
054             */
055            public static final double MAX_GAMMA = 10.0;
056            private double gamma;
057    
058            /**
059             * Creates a lookup table that holds all new values for samples 0 to
060             * numSamples - 1.
061             */
062            private final int[] createLookupTable(int numSamples)
063            {
064                    if (numSamples < 1)
065                    {
066                            throw new IllegalArgumentException("Number of samples argument must be one or larger.");
067                    }
068                    double g = 1.0 / gamma;
069                    int[] result = new int[numSamples];
070                    final int MAX_SAMPLE = numSamples - 1;
071                    final double MAX = MAX_SAMPLE;
072                    for (int i = 0; i < numSamples; i++)
073                    {
074                            result[i] = (int)Math.round((MAX * Math.pow((i / MAX), g)));
075                            if (result[i] < 0)
076                            {
077                                    result[i] = 0;
078                            }
079                            if (result[i] > MAX_SAMPLE)
080                            {
081                                    result[i] = MAX_SAMPLE;
082                            }
083                    }
084                    return result;
085            }
086    
087            /**
088             * Returns the gamma value to be used for this operation.
089             * @return gamma value between 0 (not included) and {@link #MAX_GAMMA}
090             */
091            public double getGamma()
092            {
093                    return gamma;
094            }
095    
096            private void process(Paletted8Image in, Paletted8Image out)
097            {
098                    if (out == null)
099                    {
100                            out = (Paletted8Image)in.createCompatibleImage(in.getWidth(), in.getHeight());
101                            setOutputImage(out);
102                    }
103                    Palette palette = out.getPalette();
104                    int numSamples = palette.getMaxValue() + 1;
105                    final int[] LUT = createLookupTable(numSamples);
106                    for (int c = 0; c < 3; c++)
107                    {
108                            for (int i = 0; i < palette.getNumEntries(); i++)
109                            {
110                                    palette.putSample(c, i, LUT[palette.getSample(c, i)]);
111                            }
112                    }
113                    for (int y = 0; y < in.getHeight(); y++)
114                    {
115                            for (int x = 0; x < in.getWidth(); x++)
116                            {
117                                    out.putSample(x, y, in.getSample(x, y));
118                            }
119                            setProgress(y, in.getHeight());
120                    }
121            }
122    
123            public void process() throws
124                    MissingParameterException,
125                    WrongParameterException
126            {
127                    ensureInputImageIsAvailable();
128                    PixelImage in = getInputImage();
129                    if (in instanceof Paletted8Image)
130                    {
131                            process((Paletted8Image)getInputImage(), (Paletted8Image)getOutputImage());
132                    }
133                    else
134                    if (in instanceof GrayIntegerImage || in instanceof RGBIntegerImage)
135                    {
136                            setNumTables(in.getNumChannels());
137                            IntegerImage ii = (IntegerImage)in;
138                            for (int channelIndex = 0; channelIndex < in.getNumChannels(); channelIndex++)
139                            {
140                                    int numSamples = ii.getMaxSample(channelIndex) + 1;
141                                    int[] table = createLookupTable(numSamples);
142                                    setTable(channelIndex, table);
143                            }
144                            super.process();
145                    }
146                    else
147                    {
148                            throw new WrongParameterException("Unsupported image type: " + in.getClass().getName());
149                    }
150            }
151    
152            /**
153             * Sets a new gamma value to be used in this operation.
154             * @param newGamma the new gamma value must be > 0.0 and <= MAX_GAMMA
155             * @throws IllegalArgumentException if the argument is not in the described interval
156             */
157            public void setGamma(double newGamma)
158            {
159                    if (newGamma <= 0.0)
160                    {
161                            throw new IllegalArgumentException("Gamma must be larger than 0.0; got " + newGamma);
162                    }
163                    if (newGamma > MAX_GAMMA)
164                    {
165                            throw new IllegalArgumentException("Gamma must be at most " + MAX_GAMMA + "; got " + newGamma);
166                    }
167                    gamma = newGamma;
168            }
169    }