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.Rectangle;
036import java.awt.image.BufferedImage;
037import java.awt.image.Kernel;
038
039import lucee.runtime.engine.ThreadLocalPageContext;
040import lucee.runtime.exp.FunctionException;
041import lucee.runtime.exp.PageException;
042import lucee.runtime.img.ImageUtil;
043import lucee.runtime.img.math.Function2D;
044import lucee.runtime.img.math.ImageFunction2D;
045import lucee.runtime.img.vecmath.Color4f;
046import lucee.runtime.img.vecmath.Vector3f;
047import lucee.runtime.type.KeyImpl;
048import lucee.runtime.type.Struct;
049import lucee.runtime.type.util.CollectionUtil;
050
051public class ShadeFilter extends WholeImageFilter  implements DynFiltering {
052        
053        public final static int COLORS_FROM_IMAGE = 0;
054        public final static int COLORS_CONSTANT = 1;
055
056        public final static int BUMPS_FROM_IMAGE = 0;
057        public final static int BUMPS_FROM_IMAGE_ALPHA = 1;
058        public final static int BUMPS_FROM_MAP = 2;
059        public final static int BUMPS_FROM_BEVEL = 3;
060
061        private float bumpHeight;
062        private float bumpSoftness;
063        private float viewDistance = 10000.0f;
064        //private int colorSource = COLORS_FROM_IMAGE;
065        private int bumpSource = BUMPS_FROM_IMAGE;
066        private Function2D bumpFunction;
067        private BufferedImage environmentMap;
068        private int[] envPixels;
069        private int envWidth = 1, envHeight = 1;
070        //private Vector3f l;
071        private Vector3f v;
072        //private Vector3f n;
073        //private Color4f shadedColor;
074        //private Color4f diffuse_color;
075        //private Color4f specular_color;
076        private Vector3f tmpv, tmpv2;
077
078        public ShadeFilter() {
079                bumpHeight = 1.0f;
080                bumpSoftness = 5.0f;
081                //l = new Vector3f();
082                v = new Vector3f();
083                //n = new Vector3f();
084                //shadedColor = new Color4f();
085                //diffuse_color = new Color4f();
086                //specular_color = new Color4f();
087                tmpv = new Vector3f();
088                tmpv2 = new Vector3f();
089        }
090
091        public void setBumpFunction(Function2D bumpFunction) {
092                this.bumpFunction = bumpFunction;
093        }
094
095        public Function2D getBumpFunction() {
096                return bumpFunction;
097        }
098
099        public void setBumpHeight(float bumpHeight) {
100                this.bumpHeight = bumpHeight;
101        }
102
103        public float getBumpHeight() {
104                return bumpHeight;
105        }
106
107        public void setBumpSoftness(float bumpSoftness) {
108                this.bumpSoftness = bumpSoftness;
109        }
110
111        public float getBumpSoftness() {
112                return bumpSoftness;
113        }
114
115        public void setEnvironmentMap(BufferedImage environmentMap) {
116                this.environmentMap = environmentMap;
117                if (environmentMap != null) {
118                        envWidth = environmentMap.getWidth();
119                        envHeight = environmentMap.getHeight();
120                        envPixels = getRGB( environmentMap, 0, 0, envWidth, envHeight, null );
121                } else {
122                        envWidth = envHeight = 1;
123                        envPixels = null;
124                }
125        }
126
127        public BufferedImage getEnvironmentMap() {
128                return environmentMap;
129        }
130
131        public void setBumpSource(int bumpSource) {
132                this.bumpSource = bumpSource;
133        }
134
135        public int getBumpSource() {
136                return bumpSource;
137        }
138
139        protected final static float r255 = 1.0f/255.0f;
140
141        protected void setFromRGB( Color4f c, int argb ) {
142                c.set( ((argb >> 16) & 0xff) * r255, ((argb >> 8) & 0xff) * r255, (argb & 0xff) * r255, ((argb >> 24) & 0xff) * r255 );
143        }
144        
145        protected int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ) {
146                int index = 0;
147                int[] outPixels = new int[width * height];
148                float width45 = Math.abs(6.0f * bumpHeight);
149                boolean invertBumps = bumpHeight < 0;
150                Vector3f position = new Vector3f(0.0f, 0.0f, 0.0f);
151                Vector3f viewpoint = new Vector3f(width / 2.0f, height / 2.0f, viewDistance);
152                Vector3f normal = new Vector3f();
153                Color4f c = new Color4f();
154                Function2D bump = bumpFunction;
155
156                if (bumpSource == BUMPS_FROM_IMAGE || bumpSource == BUMPS_FROM_IMAGE_ALPHA || bumpSource == BUMPS_FROM_MAP || bump == null) {
157                        if ( bumpSoftness != 0 ) {
158                                int bumpWidth = width;
159                                int bumpHeight = height;
160                                int[] bumpPixels = inPixels;
161                                if ( bumpSource == BUMPS_FROM_MAP && bumpFunction instanceof ImageFunction2D ) {
162                                        ImageFunction2D if2d = (ImageFunction2D)bumpFunction;
163                                        bumpWidth = if2d.getWidth();
164                                        bumpHeight = if2d.getHeight();
165                                        bumpPixels = if2d.getPixels();
166                                }
167                                Kernel kernel = GaussianFilter.makeKernel( bumpSoftness );
168                                int [] tmpPixels = new int[bumpWidth * bumpHeight];
169                                int [] softPixels = new int[bumpWidth * bumpHeight];
170                                GaussianFilter.convolveAndTranspose( kernel, bumpPixels, tmpPixels, bumpWidth, bumpHeight, true, false, false, ConvolveFilter.CLAMP_EDGES);
171                                GaussianFilter.convolveAndTranspose( kernel, tmpPixels, softPixels, bumpHeight, bumpWidth, true, false, false, ConvolveFilter.CLAMP_EDGES);
172                                bump = new ImageFunction2D(softPixels, bumpWidth, bumpHeight, ImageFunction2D.CLAMP, bumpSource == BUMPS_FROM_IMAGE_ALPHA);
173                        } else
174                                bump = new ImageFunction2D(inPixels, width, height, ImageFunction2D.CLAMP, bumpSource == BUMPS_FROM_IMAGE_ALPHA);
175                }
176
177                Vector3f v1 = new Vector3f();
178                Vector3f v2 = new Vector3f();
179                Vector3f n = new Vector3f();
180
181                // Loop through each source pixel
182                for (int y = 0; y < height; y++) {
183                        float ny = y;
184                        position.y = y;
185                        for (int x = 0; x < width; x++) {
186                                float nx = x;
187                                
188                                // Calculate the normal at this point
189                                if (bumpSource != BUMPS_FROM_BEVEL) {
190                                        // Complicated and slower method
191                                        // Calculate four normals using the gradients in +/- X/Y directions
192                                        int count = 0;
193                                        normal.x = normal.y = normal.z = 0;
194                                        float m0 = width45*bump.evaluate(nx, ny);
195                                        float m1 = x > 0 ? width45*bump.evaluate(nx - 1.0f, ny)-m0 : -2;
196                                        float m2 = y > 0 ? width45*bump.evaluate(nx, ny - 1.0f)-m0 : -2;
197                                        float m3 = x < width-1 ? width45*bump.evaluate(nx + 1.0f, ny)-m0 : -2;
198                                        float m4 = y < height-1 ? width45*bump.evaluate(nx, ny + 1.0f)-m0 : -2;
199                                        
200                                        if (m1 != -2 && m4 != -2) {
201                                                v1.x = -1.0f; v1.y = 0.0f; v1.z = m1;
202                                                v2.x = 0.0f; v2.y = 1.0f; v2.z = m4;
203                                                n.cross(v1, v2);
204                                                n.normalize();
205                                                if (n.z < 0.0)
206                                                        n.z = -n.z;
207                                                normal.add(n);
208                                                count++;
209                                        }
210
211                                        if (m1 != -2 && m2 != -2) {
212                                                v1.x = -1.0f; v1.y = 0.0f; v1.z = m1;
213                                                v2.x = 0.0f; v2.y = -1.0f; v2.z = m2;
214                                                n.cross(v1, v2);
215                                                n.normalize();
216                                                if (n.z < 0.0)
217                                                        n.z = -n.z;
218                                                normal.add(n);
219                                                count++;
220                                        }
221
222                                        if (m2 != -2 && m3 != -2) {
223                                                v1.x = 0.0f; v1.y = -1.0f; v1.z = m2;
224                                                v2.x = 1.0f; v2.y = 0.0f; v2.z = m3;
225                                                n.cross(v1, v2);
226                                                n.normalize();
227                                                if (n.z < 0.0)
228                                                        n.z = -n.z;
229                                                normal.add(n);
230                                                count++;
231                                        }
232
233                                        if (m3 != -2 && m4 != -2) {
234                                                v1.x = 1.0f; v1.y = 0.0f; v1.z = m3;
235                                                v2.x = 0.0f; v2.y = 1.0f; v2.z = m4;
236                                                n.cross(v1, v2);
237                                                n.normalize();
238                                                if (n.z < 0.0)
239                                                        n.z = -n.z;
240                                                normal.add(n);
241                                                count++;
242                                        }
243
244                                        // Average the four normals
245                                        normal.x /= count;
246                                        normal.y /= count;
247                                        normal.z /= count;
248                                }
249
250/* For testing - generate a sphere bump map
251                                double dx = x-120;
252                                double dy = y-80;
253                                double r2 = dx*dx+dy*dy;
254//                              double r = Math.sqrt( r2 );
255//                              double t = Math.atan2( dy, dx );
256                                if ( r2 < 80*80 ) {
257                                        double z = Math.sqrt( 80*80 - r2 );
258                                        normal.x = (float)dx;
259                                        normal.y = (float)dy;
260                                        normal.z = (float)z;
261                                        normal.normalize();
262                                } else {
263                                        normal.x = 0;
264                                        normal.y = 0;
265                                        normal.z = 1;
266                                }
267*/
268
269                                if (invertBumps) {
270                                        normal.x = -normal.x;
271                                        normal.y = -normal.y;
272                                }
273                                position.x = x;
274
275                                if (normal.z >= 0) {
276                                        // Get the material colour at this point
277                                        if (environmentMap != null) {
278                                                //FIXME-too much normalizing going on here
279                                                tmpv2.set(viewpoint);
280                                                tmpv2.sub(position);
281                                                tmpv2.normalize();
282                                                tmpv.set(normal);
283                                                tmpv.normalize();
284
285                                                // Reflect
286                                                tmpv.scale( 2.0f*tmpv.dot(tmpv2) );
287                                                tmpv.sub(v);
288                                                
289                                                tmpv.normalize();
290                                                setFromRGB(c, getEnvironmentMapP(normal, inPixels, width, height));//FIXME-interpolate()
291                                                int alpha = inPixels[index] & 0xff000000;
292                                                int rgb = ((int)(c.x * 255) << 16) | ((int)(c.y * 255) << 8) | (int)(c.z * 255);
293                                                outPixels[index++] = alpha | rgb;
294                                        } else
295                                                outPixels[index++] = 0;
296                                } else
297                                        outPixels[index++] = 0;
298                        }
299                }
300                return outPixels;
301        }
302
303        private int getEnvironmentMapP(Vector3f normal, int[] inPixels, int width, int height) {
304                if (environmentMap != null) {
305                        float x = 0.5f * (1 + normal.x);
306                        float y = 0.5f * (1 + normal.y);
307                        x = ImageMath.clamp(x * envWidth, 0, envWidth-1);
308                        y = ImageMath.clamp(y * envHeight, 0, envHeight-1);
309                        int ix = (int)x;
310                        int iy = (int)y;
311
312                        float xWeight = x-ix;
313                        float yWeight = y-iy;
314                        int i = envWidth*iy + ix;
315                        int dx = ix == envWidth-1 ? 0 : 1;
316                        int dy = iy == envHeight-1 ? 0 : envWidth;
317                        return ImageMath.bilinearInterpolate( xWeight, yWeight, envPixels[i], envPixels[i+dx], envPixels[i+dy], envPixels[i+dx+dy] );
318                }
319                return 0;
320        }
321        
322        public String toString() {
323                return "Stylize/Shade...";
324        }
325
326        public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src);
327                Object o;
328                if((o=parameters.removeEL(KeyImpl.init("BumpFunction")))!=null)setBumpFunction(ImageFilterUtil.toFunction2D(o,"BumpFunction"));
329                if((o=parameters.removeEL(KeyImpl.init("BumpHeight")))!=null)setBumpHeight(ImageFilterUtil.toFloatValue(o,"BumpHeight"));
330                if((o=parameters.removeEL(KeyImpl.init("BumpSoftness")))!=null)setBumpSoftness(ImageFilterUtil.toFloatValue(o,"BumpSoftness"));
331                if((o=parameters.removeEL(KeyImpl.init("EnvironmentMap")))!=null)setEnvironmentMap(ImageFilterUtil.toBufferedImage(o,"EnvironmentMap"));
332                if((o=parameters.removeEL(KeyImpl.init("BumpSource")))!=null)setBumpSource(ImageFilterUtil.toIntValue(o,"BumpSource"));
333
334                // check for arguments not supported
335                if(parameters.size()>0) {
336                        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 [BumpFunction, BumpHeight, BumpSoftness, EnvironmentMap, BumpSource]");
337                }
338
339                return filter(src, dst);
340        }
341}