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