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.image.BufferedImage;
036import java.util.Random;
037
038import lucee.runtime.engine.ThreadLocalPageContext;
039import lucee.runtime.exp.FunctionException;
040import lucee.runtime.exp.PageException;
041import lucee.runtime.img.ImageUtil;
042import lucee.runtime.img.math.CellularFunction2D;
043import lucee.runtime.img.math.FBM;
044import lucee.runtime.img.math.Function2D;
045import lucee.runtime.img.math.Noise;
046import lucee.runtime.img.math.RidgedFBM;
047import lucee.runtime.img.math.SCNoise;
048import lucee.runtime.img.math.VLNoise;
049import lucee.runtime.type.KeyImpl;
050import lucee.runtime.type.Struct;
051import lucee.runtime.type.util.CollectionUtil;
052
053/**
054 * A filter which produces textures from fractal Brownian motion.
055 */
056public class FBMFilter extends PointFilter implements Cloneable, DynFiltering {
057
058        public final static int NOISE = 0;
059        public final static int RIDGED = 1;
060        public final static int VLNOISE = 2;
061        public final static int SCNOISE = 3;
062        public final static int CELLULAR = 4;
063
064        private float scale = 32;
065        private float stretch = 1.0f;
066        private float angle = 0.0f;
067        private float amount = 1.0f;
068        private float H = 1.0f;
069        private float octaves = 4.0f;
070        private float lacunarity = 2.0f;
071        private float gain = 0.5f;
072        private float bias = 0.5f;
073        private int operation;
074        private float m00 = 1.0f;
075        private float m01 = 0.0f;
076        private float m10 = 0.0f;
077        private float m11 = 1.0f;
078        private float min;
079        private float max;
080        private Colormap colormap = new Gradient();
081        //private boolean ridged;
082        private FBM fBm;
083        protected Random random = new Random();
084        private int basisType = NOISE;
085        private Function2D basis;
086
087        public FBMFilter() {
088                setBasisType(NOISE);
089        }
090
091        /**
092         * Set the amount of effect.
093         * @param amount the amount
094     * @min-value 0
095     * @max-value 1
096     * @see #getAmount
097         */
098        public void setAmount(float amount) {
099                this.amount = amount;
100        }
101
102        /**
103         * Get the amount of texture.
104         * @return the amount
105     * @see #setAmount
106         */
107        public float getAmount() {
108                return amount;
109        }
110
111        public void setOperation(int operation) {
112                this.operation = operation;
113        }
114        
115        public int getOperation() {
116                return operation;
117        }
118        
119        /**
120     * Specifies the scale of the texture.
121     * @param scale the scale of the texture.
122     * @min-value 1
123     * @max-value 300+
124     * @see #getScale
125     */
126        public void setScale(float scale) {
127                this.scale = scale;
128        }
129
130        /**
131     * Returns the scale of the texture.
132     * @return the scale of the texture.
133     * @see #setScale
134     */
135        public float getScale() {
136                return scale;
137        }
138
139        /**
140     * Specifies the stretch factor of the texture.
141     * @param stretch the stretch factor of the texture.
142     * @min-value 1
143     * @max-value 50+
144     * @see #getStretch
145     */
146        public void setStretch(float stretch) {
147                this.stretch = stretch;
148        }
149
150        /**
151     * Returns the stretch factor of the texture.
152     * @return the stretch factor of the texture.
153     * @see #setStretch
154     */
155        public float getStretch() {
156                return stretch;
157        }
158
159        /**
160     * Specifies the angle of the texture.
161     * @param angle the angle of the texture.
162     * @angle
163     * @see #getAngle
164     */
165        public void setAngle(float angle) {
166                this.angle = angle;
167                float cos = (float)Math.cos(this.angle);
168                float sin = (float)Math.sin(this.angle);
169                m00 = cos;
170                m01 = sin;
171                m10 = -sin;
172                m11 = cos;
173        }
174
175        /**
176     * Returns the angle of the texture.
177     * @return the angle of the texture.
178     * @see #setAngle
179     */
180        public float getAngle() {
181                return angle;
182        }
183
184        public void setOctaves(float octaves) {
185                this.octaves = octaves;
186        }
187
188        public float getOctaves() {
189                return octaves;
190        }
191
192        public void setH(float H) {
193                this.H = H;
194        }
195
196        public float getH() {
197                return H;
198        }
199
200        public void setLacunarity(float lacunarity) {
201                this.lacunarity = lacunarity;
202        }
203
204        public float getLacunarity() {
205                return lacunarity;
206        }
207
208        public void setGain(float gain) {
209                this.gain = gain;
210        }
211
212        public float getGain() {
213                return gain;
214        }
215
216        public void setBias(float bias) {
217                this.bias = bias;
218        }
219
220        public float getBias() {
221                return bias;
222        }
223
224    /**
225     * Set the colormap to be used for the filter.
226     * @param colormap the colormap
227     * @see #getColormap
228     */
229        public void setColormap(Colormap colormap) {
230                this.colormap = colormap;
231        }
232        
233    /**
234     * Get the colormap to be used for the filter.
235     * @return the colormap
236     * @see #setColormap
237     */
238        public Colormap getColormap() {
239                return colormap;
240        }
241        
242        public void setBasisType(int basisType) {
243                this.basisType = basisType;
244                switch (basisType) {
245                default:
246                case NOISE:
247                        basis = new Noise();
248                        break;
249                case RIDGED:
250                        basis = new RidgedFBM();
251                        break;
252                case VLNOISE:
253                        basis = new VLNoise();
254                        break;
255                case SCNOISE:
256                        basis = new SCNoise();
257                        break;
258                case CELLULAR:
259                        basis = new CellularFunction2D();
260                        break;
261                }
262        }
263
264        public int getBasisType() {
265                return basisType;
266        }
267
268        public void setBasis(Function2D basis) {
269                this.basis = basis;
270        }
271
272        public Function2D getBasis() {
273                return basis;
274        }
275
276        protected FBM makeFBM(float H, float lacunarity, float octaves) {
277                FBM fbm = new FBM(H, lacunarity, octaves, basis);
278                float[] minmax = Noise.findRange(fbm, null);
279                min = minmax[0];
280                max = minmax[1];
281                return fbm;
282        }
283        
284        public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
285                fBm = makeFBM(H, lacunarity, octaves);
286                return super.filter( src, dst );
287        }
288
289        public int filterRGB(int x, int y, int rgb) {
290                float nx = m00*x + m01*y;
291                float ny = m10*x + m11*y;
292                nx /= scale;
293                ny /= scale * stretch;
294                float f = fBm.evaluate(nx, ny);
295                // Normalize to 0..1
296                f = (f-min)/(max-min);
297                f = ImageMath.gain(f, gain);
298                f = ImageMath.bias(f, bias);
299                f *= amount;
300                int a = rgb & 0xff000000;
301                int v;
302                if (colormap != null)
303                        v = colormap.getColor(f);
304                else {
305                        v = PixelUtils.clamp((int)(f*255));
306                        int r = v << 16;
307                        int g = v << 8;
308                        int b = v;
309                        v = a|r|g|b;
310                }
311                if (operation != PixelUtils.REPLACE)
312                        v = PixelUtils.combinePixels(rgb, v, operation);
313                return v;
314        }
315
316        public String toString() {
317                return "Texture/Fractal Brownian Motion...";
318        }
319        
320        public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src);
321                Object o;
322                if((o=parameters.removeEL(KeyImpl.init("Colormap")))!=null)setColormap(ImageFilterUtil.toColormap(o,"Colormap"));
323                if((o=parameters.removeEL(KeyImpl.init("Amount")))!=null)setAmount(ImageFilterUtil.toFloatValue(o,"Amount"));
324                if((o=parameters.removeEL(KeyImpl.init("Stretch")))!=null)setStretch(ImageFilterUtil.toFloatValue(o,"Stretch"));
325                if((o=parameters.removeEL(KeyImpl.init("Angle")))!=null)setAngle(ImageFilterUtil.toFloatValue(o,"Angle"));
326                if((o=parameters.removeEL(KeyImpl.init("BasisType")))!=null)setBasisType(ImageFilterUtil.toIntValue(o,"BasisType"));
327                if((o=parameters.removeEL(KeyImpl.init("Operation")))!=null)setOperation(ImageFilterUtil.toIntValue(o,"Operation"));
328                if((o=parameters.removeEL(KeyImpl.init("Octaves")))!=null)setOctaves(ImageFilterUtil.toFloatValue(o,"Octaves"));
329                if((o=parameters.removeEL(KeyImpl.init("H")))!=null)setH(ImageFilterUtil.toFloatValue(o,"H"));
330                if((o=parameters.removeEL(KeyImpl.init("Lacunarity")))!=null)setLacunarity(ImageFilterUtil.toFloatValue(o,"Lacunarity"));
331                if((o=parameters.removeEL(KeyImpl.init("Gain")))!=null)setGain(ImageFilterUtil.toFloatValue(o,"Gain"));
332                if((o=parameters.removeEL(KeyImpl.init("Bias")))!=null)setBias(ImageFilterUtil.toFloatValue(o,"Bias"));
333                if((o=parameters.removeEL(KeyImpl.init("Basis")))!=null)setBasis(ImageFilterUtil.toFunction2D(o,"Basis"));
334                if((o=parameters.removeEL(KeyImpl.init("Scale")))!=null)setScale(ImageFilterUtil.toFloatValue(o,"Scale"));
335                if((o=parameters.removeEL(KeyImpl.init("Dimensions")))!=null){
336                        int[] dim=ImageFilterUtil.toDimensions(o,"Dimensions");
337                        setDimensions(dim[0],dim[1]);
338                }
339
340                // check for arguments not supported
341                if(parameters.size()>0) {
342                        throw new FunctionException(ThreadLocalPageContext.get(), "ImageFilter", 3, "parameters", "the parameter"+(parameters.size()>1?"s":"")+" ["+CollectionUtil.getKeyList(parameters,", ")+"] "+(parameters.size()>1?"are":"is")+" not allowed, only the following parameters are supported [Colormap, Amount, Stretch, Angle, BasisType, Operation, Octaves, H, Lacunarity, Gain, Bias, Basis, Scale, Dimensions]");
343                }
344
345                return filter(src, dst);
346        }
347}