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