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.RenderingHints;
038import java.awt.geom.Point2D;
039import java.awt.image.BufferedImage;
040
041import lucee.runtime.engine.ThreadLocalPageContext;
042import lucee.runtime.exp.FunctionException;
043import lucee.runtime.exp.PageException;
044import lucee.runtime.img.ImageUtil;
045import lucee.runtime.type.KeyImpl;
046import lucee.runtime.type.Struct;
047import lucee.runtime.type.util.CollectionUtil;
048
049/**
050 * A filter which priduces a video feedback effect by repeated transformations.
051 */
052public class FeedbackFilter extends AbstractBufferedImageOp  implements DynFiltering {
053    private float centreX = 0.5f, centreY = 0.5f;
054    private float distance;
055    private float angle;
056    private float rotation;
057    private float zoom;
058    private float startAlpha = 1;
059    private float endAlpha = 1;
060    private int iterations;
061
062    /**
063     * Construct a FeedbackFilter.
064     */
065    public FeedbackFilter() {
066        }
067        
068    /**
069     * Construct a FeedbackFilter.
070     * @param distance the distance to move on each iteration
071     * @param angle the angle to move on each iteration
072     * @param rotation the amount to rotate on each iteration
073     * @param zoom the amount to scale on each iteration
074     */
075        public FeedbackFilter( float distance, float angle, float rotation, float zoom ) {
076        this.distance = distance;
077        this.angle = angle;
078        this.rotation = rotation;
079        this.zoom = zoom;
080    }
081    
082        /**
083     * Specifies the angle of each iteration.
084     * @param angle the angle of each iteration.
085     * @angle
086     * @see #getAngle
087     */
088        public void setAngle( float angle ) {
089                this.angle = angle;
090        }
091
092        /**
093     * Returns the angle of each iteration.
094     * @return the angle of each iteration.
095     * @see #setAngle
096     */
097        public float getAngle() {
098                return angle;
099        }
100        
101        /**
102     * Specifies the distance to move on each iteration.
103     * @param distance the distance
104     * @see #getDistance
105     */
106        public void setDistance( float distance ) {
107                this.distance = distance;
108        }
109
110        /**
111     * Get the distance to move on each iteration.
112     * @return the distance
113     * @see #setDistance
114     */
115        public float getDistance() {
116                return distance;
117        }
118        
119        /**
120     * Specifies the amount of rotation on each iteration.
121     * @param rotation the angle of rotation
122     * @angle
123     * @see #getRotation
124     */
125        public void setRotation( float rotation ) {
126                this.rotation = rotation;
127        }
128
129        /**
130     * Returns the amount of rotation on each iteration.
131     * @return the angle of rotation
132     * @angle
133     * @see #setRotation
134     */
135        public float getRotation() {
136                return rotation;
137        }
138        
139        /**
140     * Specifies the amount to scale on each iteration.
141     * @param zoom the zoom factor
142     * @see #getZoom
143     */
144        public void setZoom( float zoom ) {
145                this.zoom = zoom;
146        }
147
148        /**
149     * Returns the amount to scale on each iteration.
150     * @return the zoom factor
151     * @see #setZoom
152     */
153        public float getZoom() {
154                return zoom;
155        }
156        
157        /**
158     * Set the alpha value at the first iteration.
159     * @param startAlpha the alpha value
160     * @min-value 0
161     * @max-value 1
162     * @see #getStartAlpha
163     */
164        public void setStartAlpha( float startAlpha ) {
165                this.startAlpha = startAlpha;
166        }
167
168        /**
169     * Get the alpha value at the first iteration.
170     * @return the alpha value
171     * @see #setStartAlpha
172     */
173        public float getStartAlpha() {
174                return startAlpha;
175        }
176        
177        /**
178     * Set the alpha value at the last iteration.
179     * @param endAlpha the alpha value
180     * @min-value 0
181     * @max-value 1
182     * @see #getEndAlpha
183     */
184        public void setEndAlpha( float endAlpha ) {
185                this.endAlpha = endAlpha;
186        }
187
188        /**
189     * Get the alpha value at the last iteration.
190     * @return the alpha value
191     * @see #setEndAlpha
192     */
193        public float getEndAlpha() {
194                return endAlpha;
195        }
196        
197        /**
198         * Set the centre of the effect in the X direction as a proportion of the image size.
199         * @param centreX the center
200     * @see #getCentreX
201         */
202        public void setCentreX( float centreX ) {
203                this.centreX = centreX;
204        }
205
206        /**
207         * Get the centre of the effect in the X direction as a proportion of the image size.
208         * @return the center
209     * @see #setCentreX
210         */
211        public float getCentreX() {
212                return centreX;
213        }
214        
215        /**
216         * Set the centre of the effect in the Y direction as a proportion of the image size.
217         * @param centreY the center
218     * @see #getCentreY
219         */
220        public void setCentreY( float centreY ) {
221                this.centreY = centreY;
222        }
223
224        /**
225         * Get the centre of the effect in the Y direction as a proportion of the image size.
226         * @return the center
227     * @see #setCentreY
228         */
229        public float getCentreY() {
230                return centreY;
231        }
232        
233        /**
234         * Set the centre of the effect as a proportion of the image size.
235         * @param centre the center
236     * @see #getCentre
237         */
238        public void setCentre( Point2D centre ) {
239                this.centreX = (float)centre.getX();
240                this.centreY = (float)centre.getY();
241        }
242
243        /**
244         * Get the centre of the effect as a proportion of the image size.
245         * @return the center
246     * @see #setCentre
247         */
248        public Point2D getCentre() {
249                return new Point2D.Float( centreX, centreY );
250        }
251        
252        /**
253     * Set the number of iterations.
254     * @param iterations the number of iterations
255     * @min-value 0
256     * @see #getIterations
257     */
258        public void setIterations( int iterations ) {
259                this.iterations = iterations;
260        }
261
262        /**
263     * Get the number of iterations.
264     * @return the number of iterations
265     * @see #setIterations
266     */
267        public int getIterations() {
268                return iterations;
269        }
270        
271    public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
272        if ( dst == null )
273            dst = createCompatibleDestImage( src, null );
274        float cx = src.getWidth() * centreX;
275        float cy = src.getHeight() * centreY;
276        //float imageRadius = (float)Math.sqrt( cx*cx + cy*cy );
277        float translateX = (float)(distance * Math.cos( angle ));
278        float translateY = (float)(distance * -Math.sin( angle ));
279        float scale = (float)Math.exp(zoom);
280        float rotate = rotation;
281
282        if ( iterations == 0 ) {
283            Graphics2D g = dst.createGraphics();
284            g.drawRenderedImage( src, null );
285            g.dispose();
286            return dst;
287        }
288        
289                Graphics2D g = dst.createGraphics();
290                g.drawImage( src, null, null );
291        for ( int i = 0; i < iterations; i++ ) {
292                        g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
293                        g.setRenderingHint( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR );
294                        g.setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, ImageMath.lerp( (float)i/(iterations-1), startAlpha, endAlpha ) ) );
295
296            g.translate( cx+translateX, cy+translateY );
297            g.scale( scale, scale );  // The .0001 works round a bug on Windows where drawImage throws an ArrayIndexOutofBoundException
298            if ( rotation != 0 )
299                g.rotate( rotate );
300            g.translate( -cx, -cy );
301
302            g.drawImage( src, null, null );
303        }
304                g.dispose();
305        return dst;
306    }
307    
308        public String toString() {
309                return "Effects/Feedback...";
310        }
311        public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src);
312                Object o;
313                if((o=parameters.removeEL(KeyImpl.init("Iterations")))!=null)setIterations(ImageFilterUtil.toIntValue(o,"Iterations"));
314                if((o=parameters.removeEL(KeyImpl.init("Angle")))!=null)setAngle(ImageFilterUtil.toFloatValue(o,"Angle"));
315                if((o=parameters.removeEL(KeyImpl.init("CentreX")))!=null)setCentreX(ImageFilterUtil.toFloatValue(o,"CentreX"));
316                if((o=parameters.removeEL(KeyImpl.init("CentreY")))!=null)setCentreY(ImageFilterUtil.toFloatValue(o,"CentreY"));
317                //if((o=parameters.removeEL(KeyImpl.init("Centre")))!=null)setCentre(ImageFilterUtil.toPoint2D(o,"Centre"));
318                if((o=parameters.removeEL(KeyImpl.init("Distance")))!=null)setDistance(ImageFilterUtil.toFloatValue(o,"Distance"));
319                if((o=parameters.removeEL(KeyImpl.init("Rotation")))!=null)setRotation(ImageFilterUtil.toFloatValue(o,"Rotation"));
320                if((o=parameters.removeEL(KeyImpl.init("Zoom")))!=null)setZoom(ImageFilterUtil.toFloatValue(o,"Zoom"));
321                if((o=parameters.removeEL(KeyImpl.init("StartAlpha")))!=null)setStartAlpha(ImageFilterUtil.toFloatValue(o,"StartAlpha"));
322                if((o=parameters.removeEL(KeyImpl.init("EndAlpha")))!=null)setEndAlpha(ImageFilterUtil.toFloatValue(o,"EndAlpha"));
323
324                // check for arguments not supported
325                if(parameters.size()>0) {
326                        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 [Iterations, Angle, CentreX, CentreY, Centre, Distance, Rotation, Zoom, StartAlpha, EndAlpha]");
327                }
328
329                return filter(src, dst);
330        }
331}