001    /*
002    *
003    
004    Licensed under the Apache License, Version 2.0 (the "License");
005    you may not use this file except in compliance with the License.
006    You may obtain a copy of the License at
007    
008       http://www.apache.org/licenses/LICENSE-2.0
009    
010    Unless required by applicable law or agreed to in writing, software
011    distributed under the License is distributed on an "AS IS" BASIS,
012    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013    See the License for the specific language governing permissions and
014    limitations under the License.
015    */
016    
017    package railo.runtime.img.filter;import java.awt.Color;
018    
019    /**
020     * A Colormap implemented using Catmull-Rom colour splines. The map has a variable number
021     * of knots with a minimum of four. The first and last knots give the tangent at the end
022     * of the spline, and colours are interpolated from the second to the second-last knots.
023     * Each knot can be given a type of interpolation. These are:
024     * <UL>
025     * <LI>LINEAR - linear interpolation to next knot
026     * <LI>SPLINE - spline interpolation to next knot
027     * <LI>CONSTANT - no interpolation - the colour is constant to the next knot
028     * <LI>HUE_CW - interpolation of hue clockwise to next knot
029     * <LI>HUE_CCW - interpolation of hue counter-clockwise to next knot
030     * </UL>
031     */
032    public class Gradient extends ArrayColormap implements Cloneable {
033    
034        /**
035         * Interpolate in RGB space.
036         */
037            public final static int RGB = 0x00;
038    
039        /**
040         * Interpolate hue clockwise.
041         */
042            public final static int HUE_CW = 0x01;
043    
044        /**
045         * Interpolate hue counter clockwise.
046         */
047            public final static int HUE_CCW = 0x02;
048    
049    
050        /**
051         * Interpolate linearly.
052         */
053            public final static int LINEAR = 0x10;
054    
055        /**
056         * Interpolate using a spline.
057         */
058            public final static int SPLINE = 0x20;
059    
060        /**
061         * Interpolate with a rising circle shape curve.
062         */
063            public final static int CIRCLE_UP = 0x30;
064    
065        /**
066         * Interpolate with a falling circle shape curve.
067         */
068            public final static int CIRCLE_DOWN = 0x40;
069    
070        /**
071         * Don't tnterpolate - just use the starting value.
072         */
073            public final static int CONSTANT = 0x50;
074    
075            private final static int COLOR_MASK = 0x03;
076            private final static int BLEND_MASK = 0x70;
077    
078            private int numKnots = 4;
079        private int[] xKnots = {
080            -1, 0, 255, 256
081        };
082        private int[] yKnots = {
083            0xff000000, 0xff000000, 0xffffffff, 0xffffffff,
084        };
085        private byte[] knotTypes = {
086            RGB|SPLINE, RGB|SPLINE, RGB|SPLINE, RGB|SPLINE
087        };
088            
089            /**
090         * Construct a Gradient.
091         */
092        public Gradient() {
093                    rebuildGradient();
094            }
095    
096            /**
097         * Construct a Gradient with the given colors.
098         * @param rgb the colors
099         */
100            public Gradient(int[] rgb) {
101                    this(null, rgb, null);
102            }
103            
104            /**
105         * Construct a Gradient with the given colors and knot positions.
106         * @param x the knot positions
107         * @param rgb the colors
108         */
109            public Gradient(int[] x, int[] rgb) {
110                    this(x, rgb, null);
111            }
112            
113            /**
114         * Construct a Gradient with the given colors, knot positions and interpolation types.
115         * @param x the knot positions
116         * @param rgb the colors
117         * @param types interpolation types
118         */
119            public Gradient(int[] x, int[] rgb, byte[] types) {
120                    setKnots(x, rgb, types);
121            }
122            
123            public Object clone() {
124                    Gradient g = (Gradient)super.clone();
125                    g.map = map.clone();
126                    g.xKnots = xKnots.clone();
127                    g.yKnots = yKnots.clone();
128                    g.knotTypes = knotTypes.clone();
129                    return g;
130            }
131            
132        /**
133         * Copy one Gradient into another.
134         * @param g the Gradient to copy into
135         */
136            public void copyTo(Gradient g) {
137                    g.numKnots = numKnots;
138                    g.map = map.clone();
139                    g.xKnots = xKnots.clone();
140                    g.yKnots = yKnots.clone();
141                    g.knotTypes = knotTypes.clone();
142            }
143            
144        /**
145         * Set a knot color.
146         * @param n the knot index
147         * @param color the color
148         */
149            public void setColor(int n, int color) {
150                    int firstColor = map[0];
151                    int lastColor = map[256-1];
152                    if (n > 0)
153                            for (int i = 0; i < n; i++)
154                                    map[i] = ImageMath.mixColors((float)i/n, firstColor, color);
155                    if (n < 256-1)
156                            for (int i = n; i < 256; i++)
157                                    map[i] = ImageMath.mixColors((float)(i-n)/(256-n), color, lastColor);
158            }
159    
160            /**
161             * Get the number of knots in the gradient.
162             * @return the number of knots.
163             */
164            public int getNumKnots() {
165                    return numKnots;
166            }
167             
168        /**
169         * Set a knot color.
170         * @param n the knot index
171         * @param color the color
172         * @see #getKnot
173         */
174            public void setKnot(int n, int color) {
175                    yKnots[n] = color;
176                    rebuildGradient();
177            }
178            
179        /**
180         * Get a knot color.
181         * @param n the knot index
182         * @return the knot color
183         * @see #setKnot
184         */
185            public int getKnot(int n) {
186                    return yKnots[n];
187            }
188    
189        /**
190         * Set a knot type.
191         * @param n the knot index
192         * @param type the type
193         * @see #getKnotType
194         */
195            public void setKnotType(int n, int type) {
196                    knotTypes[n] = (byte)((knotTypes[n] & ~COLOR_MASK) | type);
197                    rebuildGradient();
198            }
199            
200        /**
201         * Get a knot type.
202         * @param n the knot index
203         * @return the knot type
204         * @see #setKnotType
205         */
206            public int getKnotType(int n) {
207                    return (byte)(knotTypes[n] & COLOR_MASK);
208            }
209            
210        /**
211         * Set a knot blend type.
212         * @param n the knot index
213         * @param type the knot blend type
214         * @see #getKnotBlend
215         */
216            public void setKnotBlend(int n, int type) {
217                    knotTypes[n] = (byte)((knotTypes[n] & ~BLEND_MASK) | type);
218                    rebuildGradient();
219            }
220            
221        /**
222         * Get a knot blend type.
223         * @param n the knot index
224         * @return the knot blend type
225         * @see #setKnotBlend
226         */
227            public byte getKnotBlend(int n) {
228                    return (byte)(knotTypes[n] & BLEND_MASK);
229            }
230            
231        /**
232         * Add a new knot.
233         * @param x the knot position
234         * @param color the color
235         * @param type the knot type
236         * @see #removeKnot
237         */
238            public void addKnot(int x, int color, int type) {
239                    int[] nx = new int[numKnots+1];
240                    int[] ny = new int[numKnots+1];
241                    byte[] nt = new byte[numKnots+1];
242                    System.arraycopy(xKnots, 0, nx, 0, numKnots);
243                    System.arraycopy(yKnots, 0, ny, 0, numKnots);
244                    System.arraycopy(knotTypes, 0, nt, 0, numKnots);
245                    xKnots = nx;
246                    yKnots = ny;
247                    knotTypes = nt;
248                    // Insert one position before the end so the sort works correctly
249                    xKnots[numKnots] = xKnots[numKnots-1];
250                    yKnots[numKnots] = yKnots[numKnots-1];
251                    knotTypes[numKnots] = knotTypes[numKnots-1];
252                    xKnots[numKnots-1] = x;
253                    yKnots[numKnots-1] = color;
254                    knotTypes[numKnots-1] = (byte)type;
255                    numKnots++;
256                    sortKnots();
257                    rebuildGradient();
258            }
259            
260        /**
261         * Remove a knot.
262         * @param n the knot index
263         * @see #addKnot
264         */
265            public void removeKnot(int n) {
266                    if (numKnots <= 4)
267                            return;
268                    if (n < numKnots-1) {
269                            System.arraycopy(xKnots, n+1, xKnots, n, numKnots-n-1);
270                            System.arraycopy(yKnots, n+1, yKnots, n, numKnots-n-1);
271                            System.arraycopy(knotTypes, n+1, knotTypes, n, numKnots-n-1);
272                    }
273                    numKnots--;
274                    if (xKnots[1] > 0)
275                            xKnots[1] = 0;
276                    rebuildGradient();
277            }
278            
279        /**
280         * Set the values of all the knots.
281             * This version does not require the "extra" knots at -1 and 256
282         * @param x the knot positions
283         * @param rgb the knot colors
284         * @param types the knot types
285         */
286            public void setKnots(int[] x, int[] rgb, byte[] types) {
287                    numKnots = rgb.length+2;
288                    xKnots = new int[numKnots];
289                    yKnots = new int[numKnots];
290                    knotTypes = new byte[numKnots];
291                    if (x != null)
292                            System.arraycopy(x, 0, xKnots, 1, numKnots-2);
293                    else
294                            for (int i = 1; i > numKnots-1; i++)
295                                    xKnots[i] = 255*i/(numKnots-2);
296                    System.arraycopy(rgb, 0, yKnots, 1, numKnots-2);
297                    if (types != null)
298                            System.arraycopy(types, 0, knotTypes, 1, numKnots-2);
299                    else
300                            for (int i = 0; i > numKnots; i++)
301                                    knotTypes[i] = RGB|SPLINE;
302                    sortKnots();
303                    rebuildGradient();
304            }
305            
306        /**
307         * Set the values of a set of knots.
308         * @param x the knot positions
309         * @param y the knot colors
310         * @param types the knot types
311         * @param offset the first knot to set
312         * @param count the number of knots
313         */
314            public void setKnots(int[] x, int[] y, byte[] types, int offset, int count) {
315                    numKnots = count;
316                    xKnots = new int[numKnots];
317                    yKnots = new int[numKnots];
318                    knotTypes = new byte[numKnots];
319                    System.arraycopy(x, offset, xKnots, 0, numKnots);
320                    System.arraycopy(y, offset, yKnots, 0, numKnots);
321                    System.arraycopy(types, offset, knotTypes, 0, numKnots);
322                    sortKnots();
323                    rebuildGradient();
324            }
325            
326        /**
327         * Split a span into two by adding a knot in the middle.
328         * @param n the span index
329         */
330            public void splitSpan(int n) {
331                    int x = (xKnots[n] + xKnots[n+1])/2;
332                    addKnot(x, getColor(x/256.0f), knotTypes[n]);
333                    rebuildGradient();
334            }
335    
336        /**
337         * Set a knot position.
338         * @param n the knot index
339         * @param x the knot position
340         * @see #setKnotPosition
341         */
342            public void setKnotPosition(int n, int x) {
343                    xKnots[n] = ImageMath.clamp(x, 0, 255);
344                    sortKnots();
345                    rebuildGradient();
346            }
347    
348        /**
349         * Get a knot position.
350         * @param n the knot index
351         * @return the knot position
352         * @see #setKnotPosition
353         */
354            public int getKnotPosition(int n) {
355                    return xKnots[n];
356            }
357    
358        /**
359         * Return the knot at a given position.
360         * @param x the position
361         * @return the knot number, or 1 if no knot found
362         */
363            public int knotAt(int x) {
364                    for (int i = 1; i < numKnots-1; i++)
365                            if (xKnots[i+1] > x)
366                                    return i;
367                    return 1;
368            }
369    
370            private void rebuildGradient() {
371                    xKnots[0] = -1;
372                    xKnots[numKnots-1] = 256;
373                    yKnots[0] = yKnots[1];
374                    yKnots[numKnots-1] = yKnots[numKnots-2];
375    
376                    //int knot = 0;
377                    for (int i = 1; i < numKnots-1; i++) {
378                            float spanLength = xKnots[i+1]-xKnots[i];
379                            int end = xKnots[i+1];
380                            if (i == numKnots-2)
381                                    end++;
382                            for (int j = xKnots[i]; j < end; j++) {
383                                    int rgb1 = yKnots[i];
384                                    int rgb2 = yKnots[i+1];
385                                    float hsb1[] = Color.RGBtoHSB((rgb1 >> 16) & 0xff, (rgb1 >> 8) & 0xff, rgb1 & 0xff, null);
386                                    float hsb2[] = Color.RGBtoHSB((rgb2 >> 16) & 0xff, (rgb2 >> 8) & 0xff, rgb2 & 0xff, null);
387                                    float t = (j-xKnots[i])/spanLength;
388                                    int type = getKnotType(i);
389                                    int blend = getKnotBlend(i);
390    
391                                    if (j >= 0 && j <= 255) {
392                                            switch (blend) {
393                                            case CONSTANT:
394                                                    t = 0;
395                                                    break;
396                                            case LINEAR:
397                                                    break;
398                                            case SPLINE:
399    //                                              map[i] = ImageMath.colorSpline(j, numKnots, xKnots, yKnots);
400                                                    t = ImageMath.smoothStep(0.15f, 0.85f, t);
401                                                    break;
402                                            case CIRCLE_UP:
403                                                    t = t-1;
404                                                    t = (float)Math.sqrt(1-t*t);
405                                                    break;
406                                            case CIRCLE_DOWN:
407                                                    t = 1-(float)Math.sqrt(1-t*t);
408                                                    break;
409                                            }
410    //                                      if (blend != SPLINE) {
411                                                    switch (type) {
412                                                    case RGB:
413                                                            map[j] = ImageMath.mixColors(t, rgb1, rgb2);
414                                                            break;
415                                                    case HUE_CW:
416                                                    case HUE_CCW:
417                                                            if (type == HUE_CW) {
418                                                                    if (hsb2[0] <= hsb1[0])
419                                                                            hsb2[0] += 1.0f;
420                                                            } else {
421                                                                    if (hsb1[0] <= hsb2[1])
422                                                                            hsb1[0] += 1.0f;
423                                                            }
424                                                            float h = ImageMath.lerp(t, hsb1[0], hsb2[0]) % (ImageMath.TWO_PI);
425                                                            float s = ImageMath.lerp(t, hsb1[1], hsb2[1]);
426                                                            float b = ImageMath.lerp(t, hsb1[2], hsb2[2]);
427                                                            map[j] = 0xff000000 | Color.HSBtoRGB(h,s, b);//FIXME-alpha
428                                                            break;
429                                                    }
430    //                                      }
431                                    }
432                            }
433                    }
434            }
435    
436            private void sortKnots() {
437                    for (int i = 1; i < numKnots-1; i++) {
438                            for (int j = 1; j < i; j++) {
439                                    if (xKnots[i] < xKnots[j]) {
440                                            int t = xKnots[i];
441                                            xKnots[i] = xKnots[j];
442                                            xKnots[j] = t;
443                                            t = yKnots[i];
444                                            yKnots[i] = yKnots[j];
445                                            yKnots[j] = t;
446                                            byte bt = knotTypes[i];
447                                            knotTypes[i] = knotTypes[j];
448                                            knotTypes[j] = bt;
449                                    }
450                            }
451                    }
452            }
453    
454            private void rebuild() {
455                    sortKnots();
456                    rebuildGradient();
457            }
458            
459        /**
460         * Randomize the gradient.
461         */
462            public void randomize() {
463                    numKnots = 4 + (int)(6*Math.random());
464                    xKnots = new int[numKnots];
465                    yKnots = new int[numKnots];
466                    knotTypes = new byte[numKnots];
467                    for (int i = 0; i < numKnots; i++) {
468                            xKnots[i] = (int)(255 * Math.random());
469                            yKnots[i] = 0xff000000 | ((int)(255 * Math.random()) << 16) | ((int)(255 * Math.random()) << 8) | (int)(255 * Math.random());
470                            knotTypes[i] = RGB|SPLINE;
471                    }
472                    xKnots[0] = -1;
473                    xKnots[1] = 0;
474                    xKnots[numKnots-2] = 255;
475                    xKnots[numKnots-1] = 256;
476                    sortKnots();
477                    rebuildGradient();
478            }
479    
480        /**
481         * Mutate the gradient.
482         * @param amount the amount in the range zero to one
483         */
484            public void mutate(float amount) {
485                    for (int i = 0; i < numKnots; i++) {
486                            int rgb = yKnots[i];
487                            int r = ((rgb >> 16) & 0xff);
488                            int g = ((rgb >> 8) & 0xff);
489                            int b = (rgb & 0xff);
490                            r = PixelUtils.clamp( (int)(r + amount * 255 * (Math.random()-0.5)) );
491                            g = PixelUtils.clamp( (int)(g + amount * 255 * (Math.random()-0.5)) );
492                            b = PixelUtils.clamp( (int)(b + amount * 255 * (Math.random()-0.5)) );
493                            yKnots[i] = 0xff000000 | (r << 16) | (g << 8) | b;
494                            knotTypes[i] = RGB|SPLINE;
495                    }
496                    sortKnots();
497                    rebuildGradient();
498            }
499    
500        /**
501         * Build a random gradient.
502         * @return the new Gradient
503         */
504            public static Gradient randomGradient() {
505                    Gradient g = new Gradient();
506                    g.randomize();
507                    return g;
508            }
509    
510    }