001    /*
002     * HueSaturationValue
003     * 
004     * Copyright (c) 2001, 2002 Marco Schmidt <marcoschmidt@users.sourceforge.net>
005     * All rights reserved.
006     */
007    
008    package net.sourceforge.jiu.color.adjustment;
009    
010    import net.sourceforge.jiu.data.Palette;
011    import net.sourceforge.jiu.data.Paletted8Image;
012    import net.sourceforge.jiu.data.PixelImage;
013    import net.sourceforge.jiu.data.RGB24Image;
014    import net.sourceforge.jiu.data.RGBIndex;
015    import net.sourceforge.jiu.ops.ImageToImageOperation;
016    import net.sourceforge.jiu.ops.MissingParameterException;
017    import net.sourceforge.jiu.ops.WrongParameterException;
018    
019    /**
020     * Adjusts saturation and value of a color image, optionally hue as well.
021     * <p>
022     * Supported image types: {@link RGB24Image}, {@link Paletted8Image}.
023     * @author Marco Schmidt
024     * @since 0.5.0
025     */
026    public class HueSaturationValue extends ImageToImageOperation implements RGBIndex
027    {
028            private float hue;
029            private boolean modifyHue;
030            private float sMult;
031            private boolean sNegative;
032            private float vMult;
033            private boolean vNegative;
034    
035            private final void adjust(int[] orig, int[] adjusted, float maxSample)
036            {
037                    // get r-g-b as values from 0.0f to 1.0f
038                    float r = orig[INDEX_RED] / maxSample;
039                    float g = orig[INDEX_GREEN] / maxSample;
040                    float b = orig[INDEX_BLUE] / maxSample;
041                    // (1) compute h-s-v
042                    float max = Math.max(Math.max(r, g), b);
043                    float min = Math.min(Math.min(r, g), b);
044                    float v = max;
045                    float s;
046                    if (max != 0.0f)
047                    {
048                            s = (max - min) / max;
049                    }
050                    else
051                    {
052                            s = 0.0f;
053                    }
054                    float h;
055                    if (s == 0.0f)
056                    {
057                            h = Float.NaN;
058                    }
059                    else
060                    {
061                            float delta = max - min;
062                            if (r == max)
063                            {
064                                    h = (g - b) / delta;
065                            }
066                            else
067                            if (g == max)
068                            {
069                                    h = 2.0f + (b - r) / delta;
070                            }
071                            else
072                            {
073                                    h = 4.0f + (r - g) / delta;
074                            }
075                            h *= 60.0f;
076                            if (h < 0.0f)
077                            {
078                                    h += 360.0f;
079                            }
080                    }
081                    // (2) adjust h-s-v
082                    if (modifyHue)
083                    {
084                            h = hue;
085                    }
086                    if (sNegative)
087                    {
088                            s *= sMult;
089                    }
090                    else
091                    {
092                            s += (1.0f - s) * sMult;
093                    }
094                    if (vNegative)
095                    {
096                            v *= vMult;
097                    }
098                    else
099                    {
100                            v += (1.0f - v) * vMult;
101                    }
102                    // (3) convert back to r-g-b
103                    if (s == 0.0f)
104                    {
105                            if (h == Float.NaN)
106                            {
107                                    int value = (int)(v * maxSample);
108                                    adjusted[INDEX_RED] = value;
109                                    adjusted[INDEX_GREEN] = value;
110                                    adjusted[INDEX_BLUE] = value;
111                                    return;
112                            }
113                            else
114                            {
115                                    return;
116                            }
117                    }
118                    if (h == 360.0f)
119                    {
120                            h = 0.0f;
121                    }
122                    h /= 60.0f;
123                    int i = (int)Math.floor(h);
124                    float f = h - i;
125                    float p = v * (1 - s);
126                    float q = v * (1 - (s * f));
127                    float t = v * (1 - (s * (1 - f)));
128                    switch(i)
129                    {
130                            case(0):
131                            {
132                                    adjusted[INDEX_RED] = (int)(v * maxSample);
133                                    adjusted[INDEX_GREEN] = (int)(t * maxSample);
134                                    adjusted[INDEX_BLUE] = (int)(p * maxSample);
135                                    break;
136                            }
137                            case(1):
138                            {
139                                    adjusted[INDEX_RED] = (int)(q * maxSample);
140                                    adjusted[INDEX_GREEN] = (int)(v * maxSample);
141                                    adjusted[INDEX_BLUE] = (int)(p * maxSample);
142                                    break;
143                            }
144                            case(2):
145                            {
146                                    adjusted[INDEX_RED] = (int)(p * maxSample);
147                                    adjusted[INDEX_GREEN] = (int)(v * maxSample);
148                                    adjusted[INDEX_BLUE] = (int)(t * maxSample);
149                                    break;
150                            }
151                            case(3):
152                            {
153                                    adjusted[INDEX_RED] = (int)(p * maxSample);
154                                    adjusted[INDEX_GREEN] = (int)(q * maxSample);
155                                    adjusted[INDEX_BLUE] = (int)(v * maxSample);
156                                    break;
157                            }
158                            case(4):
159                            {
160                                    adjusted[INDEX_RED] = (int)(t * maxSample);
161                                    adjusted[INDEX_GREEN] = (int)(p * maxSample);
162                                    adjusted[INDEX_BLUE] = (int)(v * maxSample);
163                                    break;
164                            }
165                            case(5):
166                            {
167                                    adjusted[INDEX_RED] = (int)(v * maxSample);
168                                    adjusted[INDEX_GREEN] = (int)(p * maxSample);
169                                    adjusted[INDEX_BLUE] = (int)(q * maxSample);
170                                    break;
171                            }
172                    }
173            }
174    
175            private void process(Paletted8Image in, Paletted8Image out)
176            {
177                    Palette inPal = in.getPalette();
178                    Palette outPal = out.getPalette();
179                    int[] orig = new int[3];
180                    int[] adjusted = new int[3];
181                    final int MAX = inPal.getMaxValue();
182                    final int WIDTH = in.getWidth();
183                    final int HEIGHT = in.getHeight();
184                    for (int i = 0; i < inPal.getNumEntries(); i++)
185                    {
186                            orig[INDEX_RED] = inPal.getSample(INDEX_RED, i);
187                            orig[INDEX_GREEN] = inPal.getSample(INDEX_GREEN, i);
188                            orig[INDEX_BLUE] = inPal.getSample(INDEX_BLUE, i);
189                            adjust(orig, adjusted, MAX);
190                            outPal.putSample(INDEX_RED, i, adjusted[INDEX_RED]);
191                            outPal.putSample(INDEX_GREEN, i, adjusted[INDEX_GREEN]);
192                            outPal.putSample(INDEX_BLUE, i, adjusted[INDEX_BLUE]);
193                    }
194                    for (int y = 0; y < HEIGHT; y++)
195                    {
196                            for (int x = 0; x < WIDTH; x++)
197                            {
198                                    out.putSample(0, x, y, in.getSample(0, x, y));
199                            }
200                            setProgress(y, HEIGHT);
201                    }
202            }
203    
204            private void process(RGB24Image in, RGB24Image out)
205            {
206                    final int MAX = in.getMaxSample(0);
207                    final int WIDTH = in.getWidth();
208                    final int HEIGHT = in.getHeight();
209                    int[] orig = new int[3];
210                    int[] adjusted = new int[3];
211                    for (int y = 0; y < HEIGHT; y++)
212                    {
213                            for (int x = 0; x < WIDTH; x++)
214                            {
215                                    orig[INDEX_RED] = in.getSample(INDEX_RED, x, y);
216                                    orig[INDEX_GREEN] = in.getSample(INDEX_GREEN, x, y);
217                                    orig[INDEX_BLUE] = in.getSample(INDEX_BLUE, x, y);
218                                    adjust(orig, adjusted, MAX);
219                                    out.putSample(INDEX_RED, x, y, adjusted[INDEX_RED]);
220                                    out.putSample(INDEX_GREEN, x, y, adjusted[INDEX_GREEN]);
221                                    out.putSample(INDEX_BLUE, x, y, adjusted[INDEX_BLUE]);
222                            }
223                            setProgress(y, HEIGHT);
224                    }
225            }
226    
227            public void process() throws 
228                    MissingParameterException, 
229                    WrongParameterException
230            {
231                    PixelImage in = getInputImage();
232                    if (in == null)
233                    {
234                            throw new MissingParameterException("Input image missing.");
235                    }
236                    PixelImage out = getOutputImage();
237                    if (out == null)
238                    {
239                            out = in.createCompatibleImage(in.getWidth(), in.getHeight());
240                            setOutputImage(out);
241                    }
242                    if (in instanceof RGB24Image)
243                    {
244                            process((RGB24Image)in, (RGB24Image)out);
245                    }
246                    else
247                    if (in instanceof Paletted8Image)
248                    {
249                            process((Paletted8Image)in, (Paletted8Image)out);
250                    }
251                    else
252                    {
253                            throw new WrongParameterException("Input image type not supported.");
254                    }
255            }
256    
257            /**
258             * Set the values for the adjustment of hue, saturation and value (brightness).
259             * Saturation and value must be from the interval -100 to 100 (also see {@link #setSaturationValue(int, int)}).
260             * Hue must be from the interval 0 to 359.
261             * @param hue the hue to be used for the complete image, between 0 and 359
262             * @param saturation change of saturation, between -100 and 100
263             * @param value change of saturation, between -100 and 100
264             * @throws IllegalArgumentException if one of the arguments does not stay within
265             *  the valid interval
266             */
267            public void setHueSaturationValue(int hue, int saturation, int value)
268            {
269                    if (hue < 0 || hue >= 360)
270                    {
271                            throw new IllegalArgumentException("Hue must be from 0..359; got " + hue);
272                    }
273                    modifyHue = true;
274                    this.hue = hue;
275                    setSv(saturation, value);
276            }
277    
278            /**
279             * Set the amount of change to saturation and value (brightness) for this operation,
280             * between -100 and 100.
281             * Calling this method also tells the operation not to modify the hue of the image.
282             * @param saturation change of saturation, between -100 and 100
283             * @param value change of saturation, between -100 and 100
284             * @throws IllegalArgumentException if one of the two arguments does not stay within
285             *  the -100 .. 100 interval
286             */
287            public void setSaturationValue(int saturation, int value)
288            {
289                    modifyHue = false;
290                    setSv(saturation, value);
291            }
292    
293            private void setSv(int saturation, int value)
294            {
295                    if (saturation < -100 || saturation > 100)
296                    {
297                            throw new IllegalArgumentException("Saturation must be from -100..100; got " + saturation);
298                    }
299                    sNegative = (saturation < 0);
300                    if (sNegative)
301                    {
302                            sMult = (100.0f + saturation) / 100.0f;
303                    }
304                    else
305                    if (saturation > 0)
306                    {
307                            sMult = ((float)saturation) / 100.0f;
308                    }
309                    else
310                    {
311                            sMult = 0.0f;
312                    }
313                    if (value < -100 || value > 100)
314                    {
315                            throw new IllegalArgumentException("Saturation must be from -100..100; got " + value);
316                    }
317                    vNegative = (value < 0);
318                    if (vNegative)
319                    {
320                            vMult = (100.0f + value) / 100.0f;
321                    }
322                    else
323                    if (value > 0)
324                    {
325                            vMult = ((float)value) / 100.0f;
326                    }
327                    else
328                    {
329                            vMult = 0.0f;
330                    }
331            }
332    }