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