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    import java.awt.Image;
019    import java.awt.Rectangle;
020    import java.awt.image.BufferedImage;
021    import java.awt.image.Kernel;
022    import java.util.Vector;
023    
024    import railo.runtime.engine.ThreadLocalPageContext;
025    import railo.runtime.exp.FunctionException;
026    import railo.runtime.exp.PageException;
027    import railo.runtime.img.ImageUtil;
028    import railo.runtime.img.math.Function2D;
029    import railo.runtime.img.math.ImageFunction2D;
030    import railo.runtime.img.vecmath.Color4f;
031    import railo.runtime.img.vecmath.Vector3f;
032    import railo.runtime.type.KeyImpl;
033    import railo.runtime.type.Struct;
034    import railo.runtime.type.util.CollectionUtil;
035    
036    /**
037     * A filter which produces lighting and embossing effects.
038     */
039    public class LightFilter extends WholeImageFilter  implements DynFiltering {
040            
041        /**
042         * Take the output colors from the input image.
043         */
044            public final static int COLORS_FROM_IMAGE = 0;
045    
046        /**
047         * Use constant material color.
048         */
049            public final static int COLORS_CONSTANT = 1;
050    
051        /**
052         * Use the input image brightness as the bump map.
053         */
054            public final static int BUMPS_FROM_IMAGE = 0;
055    
056        /**
057         * Use the input image alpha as the bump map.
058         */
059            public final static int BUMPS_FROM_IMAGE_ALPHA = 1;
060    
061        /**
062         * Use a separate image alpha channel as the bump map.
063         */
064            public final static int BUMPS_FROM_MAP = 2;
065    
066        /**
067         * Use a custom function as the bump map.
068         */
069            public final static int BUMPS_FROM_BEVEL = 3;
070    
071            private float bumpHeight;
072            private float bumpSoftness;
073            private int bumpShape;
074            private float viewDistance = 10000.0f;
075            Material material;
076            private Vector lights;
077            private int colorSource = COLORS_FROM_IMAGE;
078            private int bumpSource = BUMPS_FROM_IMAGE;
079            private Function2D bumpFunction;
080            private Image environmentMap;
081            private int[] envPixels;
082            private int envWidth = 1, envHeight = 1;
083    
084            // Temporary variables used to avoid per-pixel memory allocation while filtering
085            private Vector3f l;
086            private Vector3f v;
087            private Vector3f n;
088            private Color4f shadedColor;
089            private Color4f diffuse_color;
090            private Color4f specular_color;
091            private Vector3f tmpv, tmpv2;
092    
093            public LightFilter() {
094                    lights = new Vector();
095                    addLight(new DistantLight());
096                    bumpHeight = 1.0f;
097                    bumpSoftness = 5.0f;
098                    bumpShape = 0;
099                    material = new Material();
100                    l = new Vector3f();
101                    v = new Vector3f();
102                    n = new Vector3f();
103                    shadedColor = new Color4f();
104                    diffuse_color = new Color4f();
105                    specular_color = new Color4f();
106                    tmpv = new Vector3f();
107                    tmpv2 = new Vector3f();
108            }
109    
110            public void setMaterial( Material material ) {
111                    this.material = material;
112            }
113    
114            public Material getMaterial() {
115                    return material;
116            }
117    
118            public void setBumpFunction(Function2D bumpFunction) {
119                    this.bumpFunction = bumpFunction;
120            }
121    
122            public Function2D getBumpFunction() {
123                    return bumpFunction;
124            }
125    
126            public void setBumpHeight(float bumpHeight) {
127                    this.bumpHeight = bumpHeight;
128            }
129    
130            public float getBumpHeight() {
131                    return bumpHeight;
132            }
133    
134            public void setBumpSoftness(float bumpSoftness) {
135                    this.bumpSoftness = bumpSoftness;
136            }
137    
138            public float getBumpSoftness() {
139                    return bumpSoftness;
140            }
141    
142            public void setBumpShape(int bumpShape) {
143                    this.bumpShape = bumpShape;
144            }
145    
146            public int getBumpShape() {
147                    return bumpShape;
148            }
149    
150            public void setViewDistance(float viewDistance) {
151                    this.viewDistance = viewDistance;
152            }
153    
154            public float getViewDistance() {
155                    return viewDistance;
156            }
157    
158            public void setEnvironmentMap(BufferedImage environmentMap) {
159                    this.environmentMap = environmentMap;
160                    if (environmentMap != null) {
161                            envWidth = environmentMap.getWidth();
162                            envHeight = environmentMap.getHeight();
163                            envPixels = getRGB( environmentMap, 0, 0, envWidth, envHeight, null );
164                    } else {
165                            envWidth = envHeight = 1;
166                            envPixels = null;
167                    }
168            }
169    
170            public Image getEnvironmentMap() {
171                    return environmentMap;
172            }
173    
174            public void setColorSource(int colorSource) {
175                    this.colorSource = colorSource;
176            }
177    
178            public int getColorSource() {
179                    return colorSource;
180            }
181    
182            public void setBumpSource(int bumpSource) {
183                    this.bumpSource = bumpSource;
184            }
185    
186            public int getBumpSource() {
187                    return bumpSource;
188            }
189    
190            public void setDiffuseColor(int diffuseColor) {
191                    material.diffuseColor = diffuseColor;
192            }
193    
194            public int getDiffuseColor() {
195                    return material.diffuseColor;
196            }
197    
198            public void addLight(Light light) {
199                    lights.addElement(light);
200            }
201            
202            public void removeLight(Light light) {
203                    lights.removeElement(light);
204            }
205            
206            public Vector getLights() {
207                    return lights;
208            }
209            
210            protected final static float r255 = 1.0f/255.0f;
211    
212            protected void setFromRGB( Color4f c, int argb ) {
213                    c.set( ((argb >> 16) & 0xff) * r255, ((argb >> 8) & 0xff) * r255, (argb & 0xff) * r255, ((argb >> 24) & 0xff) * r255 );
214            }
215            
216            protected int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ) {
217                    int index = 0;
218                    int[] outPixels = new int[width * height];
219                    float width45 = Math.abs(6.0f * bumpHeight);
220                    boolean invertBumps = bumpHeight < 0;
221                    Vector3f position = new Vector3f(0.0f, 0.0f, 0.0f);
222                    Vector3f viewpoint = new Vector3f(width / 2.0f, height / 2.0f, viewDistance);
223                    Vector3f normal = new Vector3f();
224                    Color4f envColor = new Color4f();
225                    Color4f diffuseColor = new Color4f( new Color(material.diffuseColor) );
226                    Color4f specularColor = new Color4f( new Color(material.specularColor) );
227                    Function2D bump = bumpFunction;
228    
229                    // Apply the bump softness
230                    if (bumpSource == BUMPS_FROM_IMAGE || bumpSource == BUMPS_FROM_IMAGE_ALPHA || bumpSource == BUMPS_FROM_MAP || bump == null) {
231                            if ( bumpSoftness != 0 ) {
232                                    int bumpWidth = width;
233                                    int bumpHeight = height;
234                                    int[] bumpPixels = inPixels;
235                                    if ( bumpSource == BUMPS_FROM_MAP && bumpFunction instanceof ImageFunction2D ) {
236                                            ImageFunction2D if2d = (ImageFunction2D)bumpFunction;
237                                            bumpWidth = if2d.getWidth();
238                                            bumpHeight = if2d.getHeight();
239                                            bumpPixels = if2d.getPixels();
240                                    }
241                                    int [] tmpPixels = new int[bumpWidth * bumpHeight];
242                                    int [] softPixels = new int[bumpWidth * bumpHeight];
243    /*
244                                    for (int i = 0; i < 3; i++ ) {
245                                            BoxBlurFilter.blur( bumpPixels, tmpPixels, bumpWidth, bumpHeight, (int)bumpSoftness );
246                                            BoxBlurFilter.blur( tmpPixels, softPixels, bumpHeight, bumpWidth, (int)bumpSoftness );
247                                    }
248    */
249                                    Kernel kernel = GaussianFilter.makeKernel( bumpSoftness );
250                                    GaussianFilter.convolveAndTranspose( kernel, bumpPixels, tmpPixels, bumpWidth, bumpHeight, true, false, false, GaussianFilter.WRAP_EDGES );
251                                    GaussianFilter.convolveAndTranspose( kernel, tmpPixels, softPixels, bumpHeight, bumpWidth, true, false, false, GaussianFilter.WRAP_EDGES );
252                                    bump = new ImageFunction2D(softPixels, bumpWidth, bumpHeight, ImageFunction2D.CLAMP, bumpSource == BUMPS_FROM_IMAGE_ALPHA);
253    final Function2D bbump = bump;
254    if ( bumpShape != 0 ) {
255            bump = new Function2D() {
256                    private Function2D original = bbump;
257    
258                    public float evaluate(float x, float y) {
259                            float v = original.evaluate( x, y );
260                            switch ( bumpShape ) {
261                            case 1:
262    //                              v = v > 0.5f ? 0.5f : v;
263                                    v *= ImageMath.smoothStep( 0.45f, 0.55f, v );
264                                    break;
265                            case 2:
266                                    v = v < 0.5f ? 0.5f : v;
267                                    break;
268                            case 3:
269                                    v = ImageMath.triangle( v );
270                                    break;
271                            case 4:
272                                    v = ImageMath.circleDown( v );
273                                    break;
274                            case 5:
275                                    v = ImageMath.gain( v, 0.75f );
276                                    break;
277                            }
278                            return v;
279                    }
280            };
281    }
282                            } else if ( bumpSource != BUMPS_FROM_MAP )
283                                    bump = new ImageFunction2D(inPixels, width, height, ImageFunction2D.CLAMP, bumpSource == BUMPS_FROM_IMAGE_ALPHA);
284                    }
285    
286                    float reflectivity = material.reflectivity;
287                    float areflectivity = (1-reflectivity);
288                    Vector3f v1 = new Vector3f();
289                    Vector3f v2 = new Vector3f();
290                    Vector3f n = new Vector3f();
291                    Light[] lightsArray = new Light[lights.size()];
292                    lights.copyInto(lightsArray);
293                    for (int i = 0; i < lightsArray.length; i++)
294                            lightsArray[i].prepare(width, height);
295    
296                    float[][] heightWindow = new float[3][width];
297                    for (int x = 0; x < width; x++)
298                            heightWindow[1][x] = width45*bump.evaluate(x, 0);
299    
300                    // Loop through each source pixel
301                    for (int y = 0; y < height; y++) {
302                            boolean y0 = y > 0;
303                            boolean y1 = y < height-1;
304                            position.y = y;
305                            for (int x = 0; x < width; x++)
306                                    heightWindow[2][x] = width45*bump.evaluate(x, y+1);
307                            for (int x = 0; x < width; x++) {
308                                    boolean x0 = x > 0;
309                                    boolean x1 = x < width-1;
310                                    
311                                    // Calculate the normal at this point
312                                    if (bumpSource != BUMPS_FROM_BEVEL) {
313                                            // Complicated and slower method
314                                            // Calculate four normals using the gradients in +/- X/Y directions
315                                            int count = 0;
316                                            normal.x = normal.y = normal.z = 0;
317                                            float m0 = heightWindow[1][x];
318                                            float m1 = x0 ? heightWindow[1][x-1]-m0 : 0;
319                                            float m2 = y0 ? heightWindow[0][x]-m0 : 0;
320                                            float m3 = x1 ? heightWindow[1][x+1]-m0 : 0;
321                                            float m4 = y1 ? heightWindow[2][x]-m0 : 0;
322    
323                                            if (x0 && y1) {
324                                                    v1.x = -1.0f; v1.y = 0.0f; v1.z = m1;
325                                                    v2.x = 0.0f; v2.y = 1.0f; v2.z = m4;
326                                                    n.cross(v1, v2);
327                                                    n.normalize();
328                                                    if (n.z < 0.0)
329                                                            n.z = -n.z;
330                                                    normal.add(n);
331                                                    count++;
332                                            }
333    
334                                            if (x0 && y0) {
335                                                    v1.x = -1.0f; v1.y = 0.0f; v1.z = m1;
336                                                    v2.x = 0.0f; v2.y = -1.0f; v2.z = m2;
337                                                    n.cross(v1, v2);
338                                                    n.normalize();
339                                                    if (n.z < 0.0)
340                                                            n.z = -n.z;
341                                                    normal.add(n);
342                                                    count++;
343                                            }
344    
345                                            if (y0 && x1) {
346                                                    v1.x = 0.0f; v1.y = -1.0f; v1.z = m2;
347                                                    v2.x = 1.0f; v2.y = 0.0f; v2.z = m3;
348                                                    n.cross(v1, v2);
349                                                    n.normalize();
350                                                    if (n.z < 0.0)
351                                                            n.z = -n.z;
352                                                    normal.add(n);
353                                                    count++;
354                                            }
355    
356                                            if (x1 && y1) {
357                                                    v1.x = 1.0f; v1.y = 0.0f; v1.z = m3;
358                                                    v2.x = 0.0f; v2.y = 1.0f; v2.z = m4;
359                                                    n.cross(v1, v2);
360                                                    n.normalize();
361                                                    if (n.z < 0.0)
362                                                            n.z = -n.z;
363                                                    normal.add(n);
364                                                    count++;
365                                            }
366    
367                                            // Average the four normals
368                                            normal.x /= count;
369                                            normal.y /= count;
370                                            normal.z /= count;
371                                    }
372                                    if (invertBumps) {
373                                            normal.x = -normal.x;
374                                            normal.y = -normal.y;
375                                    }
376                                    position.x = x;
377    
378                                    if (normal.z >= 0) {
379                                            // Get the material colour at this point
380                                            if (colorSource == COLORS_FROM_IMAGE)
381                                                    setFromRGB(diffuseColor, inPixels[index]);
382                                            else
383                                                    setFromRGB(diffuseColor, material.diffuseColor);
384                                            if (reflectivity != 0 && environmentMap != null) {
385                                                    //FIXME-too much normalizing going on here
386                                                    tmpv2.set(viewpoint);
387                                                    tmpv2.sub(position);
388                                                    tmpv2.normalize();
389                                                    tmpv.set(normal);
390                                                    tmpv.normalize();
391    
392                                                    // Reflect
393                                                    tmpv.scale( 2.0f*tmpv.dot(tmpv2) );
394                                                    tmpv.sub(v);
395                                                    
396                                                    tmpv.normalize();
397                                                    setFromRGB(envColor, getEnvironmentMap(tmpv, inPixels, width, height));//FIXME-interpolate()
398                                                    diffuseColor.x = reflectivity*envColor.x + areflectivity*diffuseColor.x;
399                                                    diffuseColor.y = reflectivity*envColor.y + areflectivity*diffuseColor.y;
400                                                    diffuseColor.z = reflectivity*envColor.z + areflectivity*diffuseColor.z;
401                                            }
402                                            // Shade the pixel
403                                            Color4f c = phongShade(position, viewpoint, normal, diffuseColor, specularColor, material, lightsArray);
404                                            int alpha = inPixels[index] & 0xff000000;
405                                            int rgb = ((int)(c.x * 255) << 16) | ((int)(c.y * 255) << 8) | (int)(c.z * 255);
406                                            outPixels[index++] = alpha | rgb;
407                                    } else
408                                            outPixels[index++] = 0;
409                            }
410                            float[] t = heightWindow[0];
411                            heightWindow[0] = heightWindow[1];
412                            heightWindow[1] = heightWindow[2];
413                            heightWindow[2] = t;
414                    }
415                    return outPixels;
416            }
417    
418            protected Color4f phongShade(Vector3f position, Vector3f viewpoint, Vector3f normal, Color4f diffuseColor, Color4f specularColor, Material material, Light[] lightsArray) {
419                    shadedColor.set(diffuseColor);
420                    shadedColor.scale(material.ambientIntensity);
421    
422                    for (int i = 0; i < lightsArray.length; i++) {
423                            Light light = lightsArray[i];
424                            n.set(normal);
425                            l.set(light.position);
426                            if (light.type != DISTANT)
427                                    l.sub(position);
428                            l.normalize();
429                            float nDotL = n.dot(l);
430                            if (nDotL >= 0.0) {
431                                    float dDotL = 0;
432                                    
433                                    v.set(viewpoint);
434                                    v.sub(position);
435                                    v.normalize();
436    
437                                    // Spotlight
438                                    if (light.type == SPOT) {
439                                            dDotL = light.direction.dot(l);
440                                            if (dDotL < light.cosConeAngle)
441                                                    continue;
442                                    }
443    
444                                    n.scale(2.0f * nDotL);
445                                    n.sub(l);
446                                    float rDotV = n.dot(v);
447    
448                                    float rv;
449                                    if (rDotV < 0.0)
450                                            rv = 0.0f;
451                                    else
452    //                                      rv = (float)Math.pow(rDotV, material.highlight);
453                                            rv = rDotV / (material.highlight - material.highlight*rDotV + rDotV);   // Fast approximation to pow
454    
455                                    // Spotlight
456                                    if (light.type == SPOT) {
457                                            dDotL = light.cosConeAngle/dDotL;
458                                            float e = dDotL;
459                                            e *= e;
460                                            e *= e;
461                                            e *= e;
462                                            e = (float)Math.pow(dDotL, light.focus*10)*(1 - e);
463                                            rv *= e;
464                                            nDotL *= e;
465                                    }
466                                    
467                                    diffuse_color.set(diffuseColor);
468                                    diffuse_color.scale(material.diffuseReflectivity);
469                                    diffuse_color.x *= light.realColor.x * nDotL;
470                                    diffuse_color.y *= light.realColor.y * nDotL;
471                                    diffuse_color.z *= light.realColor.z * nDotL;
472                                    specular_color.set(specularColor);
473                                    specular_color.scale(material.specularReflectivity);
474                                    specular_color.x *= light.realColor.x * rv;
475                                    specular_color.y *= light.realColor.y * rv;
476                                    specular_color.z *= light.realColor.z * rv;
477                                    diffuse_color.add(specular_color);
478                                    diffuse_color.clamp( 0, 1 );
479                                    shadedColor.add(diffuse_color);
480                            }
481                    }
482                    shadedColor.clamp( 0, 1 );
483                    return shadedColor;
484            }
485    
486            private int getEnvironmentMap(Vector3f normal, int[] inPixels, int width, int height) {
487                    if (environmentMap != null) {
488                            float angle = (float)Math.acos(-normal.y);
489    
490                            float x, y;
491                            y = angle/ImageMath.PI;
492    
493                            if (y == 0.0f || y == 1.0f)
494                                    x = 0.0f;
495                            else {
496                                    float f = normal.x/(float)Math.sin(angle);
497    
498                                    if (f > 1.0f)
499                                            f = 1.0f;
500                                    else if (f < -1.0f) 
501                                            f = -1.0f;
502    
503                                    x = (float)Math.acos(f)/ImageMath.PI;
504                            }
505                            // A bit of empirical scaling....
506                            x = ImageMath.clamp(x * envWidth, 0, envWidth-1);
507                            y = ImageMath.clamp(y * envHeight, 0, envHeight-1);
508                            int ix = (int)x;
509                            int iy = (int)y;
510    
511                            float xWeight = x-ix;
512                            float yWeight = y-iy;
513                            int i = envWidth*iy + ix;
514                            int dx = ix == envWidth-1 ? 0 : 1;
515                            int dy = iy == envHeight-1 ? 0 : envWidth;
516                            return ImageMath.bilinearInterpolate( xWeight, yWeight, envPixels[i], envPixels[i+dx], envPixels[i+dy], envPixels[i+dx+dy] );
517                    }
518                    return 0;
519            }
520            
521            public String toString() {
522                    return "Stylize/Light Effects...";
523            }
524    
525        /**
526         * A class representing material properties.
527         */
528            public static class Material {
529                    int diffuseColor;
530                    int specularColor;
531                    float ambientIntensity;
532                    float diffuseReflectivity;
533                    float specularReflectivity;
534                    float highlight;
535                    float reflectivity;
536                    float opacity = 1;
537    
538                    public Material() {
539                            ambientIntensity = 0.5f;
540                            diffuseReflectivity = 1.0f;
541                            specularReflectivity = 1.0f;
542                            highlight = 3.0f;
543                            reflectivity = 0.0f;
544                            diffuseColor = 0xff888888;
545                            specularColor = 0xffffffff;
546                    }
547    
548                    public void setDiffuseColor(int diffuseColor) {
549                            this.diffuseColor = diffuseColor;
550                    }
551    
552                    public int getDiffuseColor() {
553                            return diffuseColor;
554                    }
555    
556                    public void setOpacity( float opacity ) {
557                            this.opacity = opacity;
558                    }
559    
560                    public float getOpacity() {
561                            return opacity;
562                    }
563    
564            }
565    
566            public final static int AMBIENT = 0;
567            public final static int DISTANT = 1;
568            public final static int POINT = 2;
569            public final static int SPOT = 3;
570    
571        /**
572         * A class representing a light.
573         */
574            public static class Light implements Cloneable {
575    
576                    int type = AMBIENT;
577                    Vector3f position;
578                    Vector3f direction;
579                    Color4f realColor = new Color4f();
580                    int color = 0xffffffff;
581                    float intensity;
582                    float azimuth;
583                    float elevation;
584                    float focus = 0.5f;
585                    float centreX = 0.5f, centreY = 0.5f;
586                    float coneAngle = ImageMath.PI/6;
587                    float cosConeAngle;
588                    float distance = 100.0f;
589    
590                    public Light() {
591                            this(270*ImageMath.PI/180.0f, 0.5235987755982988f, 1.0f);
592                    }
593                    
594                    public Light(float azimuth, float elevation, float intensity) {
595                            this.azimuth = azimuth;
596                            this.elevation = elevation;
597                            this.intensity = intensity;
598                    }
599                    
600                    public void setAzimuth(float azimuth) {
601                            this.azimuth = azimuth;
602                    }
603    
604                    public float getAzimuth() {
605                            return azimuth;
606                    }
607    
608                    public void setElevation(float elevation) {
609                            this.elevation = elevation;
610                    }
611    
612                    public float getElevation() {
613                            return elevation;
614                    }
615    
616                    public void setDistance(float distance) {
617                            this.distance = distance;
618                    }
619    
620                    public float getDistance() {
621                            return distance;
622                    }
623    
624                    public void setIntensity(float intensity) {
625                            this.intensity = intensity;
626                    }
627    
628                    public float getIntensity() {
629                            return intensity;
630                    }
631    
632                    public void setConeAngle(float coneAngle) {
633                            this.coneAngle = coneAngle;
634                    }
635    
636                    public float getConeAngle() {
637                            return coneAngle;
638                    }
639    
640                    public void setFocus(float focus) {
641                            this.focus = focus;
642                    }
643    
644                    public float getFocus() {
645                            return focus;
646                    }
647    
648                    public void setColor(int color) {
649                            this.color = color;
650                    }
651    
652                    public int getColor() {
653                            return color;
654                    }
655    
656            /**
657             * Set the centre of the light in the X direction as a proportion of the image size.
658             * @param centreX the center
659             * @see #getCentreX
660             */
661                    public void setCentreX(float x) {
662                            centreX = x;
663                    }
664                    
665            /**
666             * Get the centre of the light in the X direction as a proportion of the image size.
667             * @return the center
668             * @see #setCentreX
669             */
670                    public float getCentreX() {
671                            return centreX;
672                    }
673    
674            /**
675             * Set the centre of the light in the Y direction as a proportion of the image size.
676             * @param centreY the center
677             * @see #getCentreY
678             */
679                    public void setCentreY(float y) {
680                            centreY = y;
681                    }
682                    
683            /**
684             * Get the centre of the light in the Y direction as a proportion of the image size.
685             * @return the center
686             * @see #setCentreY
687             */
688                    public float getCentreY() {
689                            return centreY;
690                    }
691    
692            /**
693             * Prepare the light for rendering.
694             * @param width the output image width
695             * @param height the output image height
696             */
697                    public void prepare(int width, int height) {
698                            float lx = (float)(Math.cos(azimuth) * Math.cos(elevation));
699                            float ly = (float)(Math.sin(azimuth) * Math.cos(elevation));
700                            float lz = (float)Math.sin(elevation);
701                            direction = new Vector3f(lx, ly, lz);
702                            direction.normalize();
703                            if (type != DISTANT) {
704                                    lx *= distance;
705                                    ly *= distance;
706                                    lz *= distance;
707                                    lx += width * centreX;
708                                    ly += height * centreY;
709                            }
710                            position = new Vector3f(lx, ly, lz);
711                            realColor.set( new Color(color) );
712                            realColor.scale(intensity);
713                            cosConeAngle = (float)Math.cos(coneAngle);
714                    }
715                    
716                    public Object clone() {
717                            try {
718                                    Light copy = (Light)super.clone();
719                                    return copy;
720                            }
721                            catch (CloneNotSupportedException e) {
722                                    return null;
723                            }
724                    }
725    
726                    public String toString() {
727                            return "Light";
728                    }
729    
730            }
731    
732            public class AmbientLight extends Light {
733                    public String toString() {
734                            return "Ambient Light";
735                    }
736            }
737    
738            public class PointLight extends Light {
739                    public PointLight() {
740                            type = POINT;
741                    }
742    
743                    public String toString() {
744                            return "Point Light";
745                    }
746            }
747    
748            public class DistantLight extends Light {
749                    public DistantLight() {
750                            type = DISTANT;
751                    }
752    
753                    public String toString() {
754                            return "Distant Light";
755                    }
756            }
757    
758            public class SpotLight extends Light {
759                    public SpotLight() {
760                            type = SPOT;
761                    }
762    
763                    public String toString() {
764                            return "Spotlight";
765                    }
766            }
767            public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src);
768                    Object o;
769                    if((o=parameters.removeEL(KeyImpl.init("ColorSource")))!=null)setColorSource(ImageFilterUtil.toColorRGB(o,"ColorSource"));
770                    if((o=parameters.removeEL(KeyImpl.init("Material")))!=null)setMaterial(ImageFilterUtil.toLightFilter$Material(o,"Material"));
771                    if((o=parameters.removeEL(KeyImpl.init("BumpFunction")))!=null)setBumpFunction(ImageFilterUtil.toFunction2D(o,"BumpFunction"));
772                    if((o=parameters.removeEL(KeyImpl.init("BumpHeight")))!=null)setBumpHeight(ImageFilterUtil.toFloatValue(o,"BumpHeight"));
773                    if((o=parameters.removeEL(KeyImpl.init("BumpSoftness")))!=null)setBumpSoftness(ImageFilterUtil.toFloatValue(o,"BumpSoftness"));
774                    if((o=parameters.removeEL(KeyImpl.init("BumpShape")))!=null)setBumpShape(ImageFilterUtil.toIntValue(o,"BumpShape"));
775                    if((o=parameters.removeEL(KeyImpl.init("ViewDistance")))!=null)setViewDistance(ImageFilterUtil.toFloatValue(o,"ViewDistance"));
776                    if((o=parameters.removeEL(KeyImpl.init("EnvironmentMap")))!=null)setEnvironmentMap(ImageFilterUtil.toBufferedImage(o,"EnvironmentMap"));
777                    if((o=parameters.removeEL(KeyImpl.init("BumpSource")))!=null)setBumpSource(ImageFilterUtil.toIntValue(o,"BumpSource"));
778                    if((o=parameters.removeEL(KeyImpl.init("DiffuseColor")))!=null)setDiffuseColor(ImageFilterUtil.toColorRGB(o,"DiffuseColor"));
779    
780                    // check for arguments not supported
781                    if(parameters.size()>0) {
782                            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 [ColorSource, Material, BumpFunction, BumpHeight, BumpSoftness, BumpShape, ViewDistance, EnvironmentMap, BumpSource, DiffuseColor]");
783                    }
784    
785                    return filter(src, dst);
786            }
787    }