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