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