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.AlphaComposite;
018    import java.awt.Graphics2D;
019    import java.awt.image.BufferedImage;
020    import java.awt.image.BufferedImageOp;
021    import java.beans.BeanInfo;
022    import java.beans.IntrospectionException;
023    import java.beans.Introspector;
024    import java.beans.PropertyDescriptor;
025    import java.lang.reflect.Method;
026    
027    import railo.runtime.engine.ThreadLocalPageContext;
028    import railo.runtime.exp.FunctionException;
029    import railo.runtime.exp.PageException;
030    import railo.runtime.img.ImageUtil;
031    import railo.runtime.type.KeyImpl;
032    import railo.runtime.type.List;
033    import railo.runtime.type.Struct;
034    
035    /**
036     * A filter which uses another filter to perform a transition.
037     * e.g. to create a blur transition, you could write: new TransitionFilter( new BoxBlurFilter(), "radius", 0, 100 );
038     */
039    public class TransitionFilter extends AbstractBufferedImageOp  implements DynFiltering {
040            
041            private float transition = 0;
042            private BufferedImage destination;
043        private String property;
044        private Method method;
045    
046        /**
047         * The filter used for the transition.
048         */
049        protected BufferedImageOp filter;
050    
051        /**
052         * The start value for the filter property.
053         */
054        protected float minValue;
055    
056        /**
057         * The end value for the filter property.
058         */
059        protected float maxValue;
060    
061    
062        /**
063         * Construct a TransitionFilter.
064         * @param filter the filter to use
065         * @param property the filter property which is changed over the transition
066         * @param minValue the start value for the filter property
067         * @param maxValue the end value for the filter property
068         */
069            public TransitionFilter( BufferedImageOp filter, String property, float minValue, float maxValue ) {
070                    this.filter = filter;
071                    this.property = property;
072                    this.minValue = minValue;
073                    this.maxValue = maxValue;
074                    try {
075                            BeanInfo info = Introspector.getBeanInfo( filter.getClass() );
076                PropertyDescriptor[] pds = info.getPropertyDescriptors();
077                for ( int i = 0; i < pds.length; i++ ) {
078                    PropertyDescriptor pd = pds[i];
079                    if ( property.equals( pd.getName() ) ) {
080                        method = pd.getWriteMethod();
081                        break;
082                    }
083                }
084                if ( method == null )
085                    throw new IllegalArgumentException( "No such property in object: "+property );
086                    }
087                    catch (IntrospectionException e) {
088                throw new IllegalArgumentException( e.toString() );
089                    }
090            }
091    
092            /**
093             * Set the transition of the image in the range 0..1.
094             * @param transition the transition
095         * @min-value 0
096         * @max-value 1
097         * @see #getTransition
098             */
099            public void setTransition( float transition ) {
100                    this.transition = transition;
101            }
102            
103            /**
104             * Get the transition of the image.
105             * @return the transition
106         * @see #setTransition
107             */
108            public float getTransition() {
109                    return transition;
110            }
111            
112        /**
113         * Set the destination image.
114         * @param destination the destination image
115         * @see #getDestination
116         */
117            public void setDestination( BufferedImage destination ) {
118                    this.destination = destination;
119            }
120            
121        /**
122         * Get the destination image.
123         * @return the destination image
124         * @see #setDestination
125         */
126            public BufferedImage getDestination() {
127                    return destination;
128            }
129            
130    /*
131            public void setFilter( BufferedImageOp filter ) {
132                    this.filter = filter;
133            }
134            
135            public int getFilter() {
136                    return filter;
137            }
138    */
139            
140        /**
141         * Prepare the filter for the transiton at a given time.
142         * The default implementation sets the given filter property, but you could override this method to make other changes.
143         * @param transition the transition time in the range 0 - 1
144         */
145            public void prepareFilter( float transition ) {
146            try {
147                method.invoke( filter, new Object[] { new Float( transition ) } );
148            }
149            catch ( Exception e ) {
150                throw new IllegalArgumentException("Error setting value for property: "+property);
151            }
152            }
153            
154        public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
155            if ( dst == null )
156                dst = createCompatibleDestImage( src, null );
157                    if ( destination == null )
158                            return dst;
159    
160                    float itransition = 1-transition;
161    
162                    Graphics2D g = dst.createGraphics();
163                    if ( transition != 1 ) {
164                float t = minValue + transition * ( maxValue-minValue );
165                            prepareFilter( t );
166                g.drawImage( src, filter, 0, 0 );
167                    }
168                    if ( transition != 0 ) {
169                g.setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, transition ) );
170                float t = minValue + itransition * ( maxValue-minValue );
171                            prepareFilter( t );
172                g.drawImage( destination, filter, 0, 0 );
173                    }
174                    g.dispose();
175    
176            return dst;
177        }
178    
179            public String toString() {
180                    return "Transitions/Transition...";
181            }
182            public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src);
183                    Object o;
184                    if((o=parameters.removeEL(KeyImpl.init("Transition")))!=null)setTransition(ImageFilterUtil.toFloatValue(o,"Transition"));
185                    if((o=parameters.removeEL(KeyImpl.init("destination")))!=null)setDestination(ImageFilterUtil.toBufferedImage(o,"destination"));
186    
187                    // check for arguments not supported
188                    if(parameters.size()>0) {
189                            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 [Transition]");
190                    }
191    
192                    return filter(src, dst);
193            }
194    }