001/*
002Copyright 2006 Jerry Huxtable
003
004Licensed under the Apache License, Version 2.0 (the "License");
005you may not use this file except in compliance with the License.
006You may obtain a copy of the License at
007
008   http://www.apache.org/licenses/LICENSE-2.0
009
010Unless required by applicable law or agreed to in writing, software
011distributed under the License is distributed on an "AS IS" BASIS,
012WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013See the License for the specific language governing permissions and
014limitations under the License.
015*/
016
017package com.jhlabs.image;
018
019import java.awt.*;
020import java.awt.geom.*;
021import java.awt.image.*;
022
023/**
024 * A filter which priduces a video feedback effect by repeated transformations.
025 */
026public class FeedbackFilter extends AbstractBufferedImageOp {
027    private float centreX = 0.5f, centreY = 0.5f;
028    private float distance;
029    private float angle;
030    private float rotation;
031    private float zoom;
032    private float startAlpha = 1;
033    private float endAlpha = 1;
034    private int iterations;
035
036    /**
037     * Construct a FeedbackFilter.
038     */
039    public FeedbackFilter() {
040        }
041        
042    /**
043     * Construct a FeedbackFilter.
044     * @param distance the distance to move on each iteration
045     * @param angle the angle to move on each iteration
046     * @param rotation the amount to rotate on each iteration
047     * @param zoom the amount to scale on each iteration
048     */
049        public FeedbackFilter( float distance, float angle, float rotation, float zoom ) {
050        this.distance = distance;
051        this.angle = angle;
052        this.rotation = rotation;
053        this.zoom = zoom;
054    }
055    
056        /**
057     * Specifies the angle of each iteration.
058     * @param angle the angle of each iteration.
059     * @angle
060     * @see #getAngle
061     */
062        public void setAngle( float angle ) {
063                this.angle = angle;
064        }
065
066        /**
067     * Returns the angle of each iteration.
068     * @return the angle of each iteration.
069     * @see #setAngle
070     */
071        public float getAngle() {
072                return angle;
073        }
074        
075        /**
076     * Specifies the distance to move on each iteration.
077     * @param distance the distance
078     * @see #getDistance
079     */
080        public void setDistance( float distance ) {
081                this.distance = distance;
082        }
083
084        /**
085     * Get the distance to move on each iteration.
086     * @return the distance
087     * @see #setDistance
088     */
089        public float getDistance() {
090                return distance;
091        }
092        
093        /**
094     * Specifies the amount of rotation on each iteration.
095     * @param rotation the angle of rotation
096     * @angle
097     * @see #getRotation
098     */
099        public void setRotation( float rotation ) {
100                this.rotation = rotation;
101        }
102
103        /**
104     * Returns the amount of rotation on each iteration.
105     * @return the angle of rotation
106     * @angle
107     * @see #setRotation
108     */
109        public float getRotation() {
110                return rotation;
111        }
112        
113        /**
114     * Specifies the amount to scale on each iteration.
115     * @param zoom the zoom factor
116     * @see #getZoom
117     */
118        public void setZoom( float zoom ) {
119                this.zoom = zoom;
120        }
121
122        /**
123     * Returns the amount to scale on each iteration.
124     * @return the zoom factor
125     * @see #setZoom
126     */
127        public float getZoom() {
128                return zoom;
129        }
130        
131        /**
132     * Set the alpha value at the first iteration.
133     * @param startAlpha the alpha value
134     * @min-value 0
135     * @max-value 1
136     * @see #getStartAlpha
137     */
138        public void setStartAlpha( float startAlpha ) {
139                this.startAlpha = startAlpha;
140        }
141
142        /**
143     * Get the alpha value at the first iteration.
144     * @return the alpha value
145     * @see #setStartAlpha
146     */
147        public float getStartAlpha() {
148                return startAlpha;
149        }
150        
151        /**
152     * Set the alpha value at the last iteration.
153     * @param endAlpha the alpha value
154     * @min-value 0
155     * @max-value 1
156     * @see #getEndAlpha
157     */
158        public void setEndAlpha( float endAlpha ) {
159                this.endAlpha = endAlpha;
160        }
161
162        /**
163     * Get the alpha value at the last iteration.
164     * @return the alpha value
165     * @see #setEndAlpha
166     */
167        public float getEndAlpha() {
168                return endAlpha;
169        }
170        
171        /**
172         * Set the centre of the effect in the X direction as a proportion of the image size.
173         * @param centreX the center
174     * @see #getCentreX
175         */
176        public void setCentreX( float centreX ) {
177                this.centreX = centreX;
178        }
179
180        /**
181         * Get the centre of the effect in the X direction as a proportion of the image size.
182         * @return the center
183     * @see #setCentreX
184         */
185        public float getCentreX() {
186                return centreX;
187        }
188        
189        /**
190         * Set the centre of the effect in the Y direction as a proportion of the image size.
191         * @param centreY the center
192     * @see #getCentreY
193         */
194        public void setCentreY( float centreY ) {
195                this.centreY = centreY;
196        }
197
198        /**
199         * Get the centre of the effect in the Y direction as a proportion of the image size.
200         * @return the center
201     * @see #setCentreY
202         */
203        public float getCentreY() {
204                return centreY;
205        }
206        
207        /**
208         * Set the centre of the effect as a proportion of the image size.
209         * @param centre the center
210     * @see #getCentre
211         */
212        public void setCentre( Point2D centre ) {
213                this.centreX = (float)centre.getX();
214                this.centreY = (float)centre.getY();
215        }
216
217        /**
218         * Get the centre of the effect as a proportion of the image size.
219         * @return the center
220     * @see #setCentre
221         */
222        public Point2D getCentre() {
223                return new Point2D.Float( centreX, centreY );
224        }
225        
226        /**
227     * Set the number of iterations.
228     * @param iterations the number of iterations
229     * @min-value 0
230     * @see #getIterations
231     */
232        public void setIterations( int iterations ) {
233                this.iterations = iterations;
234        }
235
236        /**
237     * Get the number of iterations.
238     * @return the number of iterations
239     * @see #setIterations
240     */
241        public int getIterations() {
242                return iterations;
243        }
244        
245    public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
246        if ( dst == null )
247            dst = createCompatibleDestImage( src, null );
248        float cx = (float)src.getWidth() * centreX;
249        float cy = (float)src.getHeight() * centreY;
250        float imageRadius = (float)Math.sqrt( cx*cx + cy*cy );
251        float translateX = (float)(distance * Math.cos( angle ));
252        float translateY = (float)(distance * -Math.sin( angle ));
253        float scale = (float)Math.exp(zoom);
254        float rotate = rotation;
255
256        if ( iterations == 0 ) {
257            Graphics2D g = dst.createGraphics();
258            g.drawRenderedImage( src, null );
259            g.dispose();
260            return dst;
261        }
262        
263                Graphics2D g = dst.createGraphics();
264                g.drawImage( src, null, null );
265        for ( int i = 0; i < iterations; i++ ) {
266                        g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
267                        g.setRenderingHint( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR );
268                        g.setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, ImageMath.lerp( (float)i/(iterations-1), startAlpha, endAlpha ) ) );
269
270            g.translate( cx+translateX, cy+translateY );
271            g.scale( scale, scale );  // The .0001 works round a bug on Windows where drawImage throws an ArrayIndexOutofBoundException
272            if ( rotation != 0 )
273                g.rotate( rotate );
274            g.translate( -cx, -cy );
275
276            g.drawImage( src, null, null );
277        }
278                g.dispose();
279        return dst;
280    }
281    
282        public String toString() {
283                return "Effects/Feedback...";
284        }
285}