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 produces motion blur the faster, but lower-quality way.
025 */
026public class MotionBlurOp extends AbstractBufferedImageOp {
027
028    private float centreX = 0.5f, centreY = 0.5f;
029    private float distance;
030    private float angle;
031    private float rotation;
032    private float zoom;
033
034    /**
035     * Construct a MotionBlurOp.
036     */
037    public MotionBlurOp() {
038        }
039        
040    /**
041     * Construct a MotionBlurOp.
042     * @param distance the distance of blur.
043     * @param angle the angle of blur.
044     * @param rotation the angle of rotation.
045     * @param zoom the zoom factor.
046     */
047        public MotionBlurOp( float distance, float angle, float rotation, float zoom ) {
048        this.distance = distance;
049        this.angle = angle;
050        this.rotation = rotation;
051        this.zoom = zoom;
052    }
053    
054        /**
055     * Specifies the angle of blur.
056     * @param angle the angle of blur.
057     * @angle
058     * @see #getAngle
059     */
060        public void setAngle( float angle ) {
061                this.angle = angle;
062        }
063
064        /**
065     * Returns the angle of blur.
066     * @return the angle of blur.
067     * @see #setAngle
068     */
069        public float getAngle() {
070                return angle;
071        }
072        
073        /**
074     * Set the distance of blur.
075     * @param distance the distance of blur.
076     * @see #getDistance
077     */
078        public void setDistance( float distance ) {
079                this.distance = distance;
080        }
081
082        /**
083     * Get the distance of blur.
084     * @return the distance of blur.
085     * @see #setDistance
086     */
087        public float getDistance() {
088                return distance;
089        }
090        
091        /**
092     * Set the blur rotation.
093     * @param rotation the angle of rotation.
094     * @see #getRotation
095     */
096        public void setRotation( float rotation ) {
097                this.rotation = rotation;
098        }
099
100        /**
101     * Get the blur rotation.
102     * @return the angle of rotation.
103     * @see #setRotation
104     */
105        public float getRotation() {
106                return rotation;
107        }
108        
109        /**
110     * Set the blur zoom.
111     * @param zoom the zoom factor.
112     * @see #getZoom
113     */
114        public void setZoom( float zoom ) {
115                this.zoom = zoom;
116        }
117
118        /**
119     * Get the blur zoom.
120     * @return the zoom factor.
121     * @see #setZoom
122     */
123        public float getZoom() {
124                return zoom;
125        }
126        
127        /**
128         * Set the centre of the effect in the X direction as a proportion of the image size.
129         * @param centreX the center
130     * @see #getCentreX
131         */
132        public void setCentreX( float centreX ) {
133                this.centreX = centreX;
134        }
135
136        /**
137         * Get the centre of the effect in the X direction as a proportion of the image size.
138         * @return the center
139     * @see #setCentreX
140         */
141        public float getCentreX() {
142                return centreX;
143        }
144        
145        /**
146         * Set the centre of the effect in the Y direction as a proportion of the image size.
147         * @param centreY the center
148     * @see #getCentreY
149         */
150        public void setCentreY( float centreY ) {
151                this.centreY = centreY;
152        }
153
154        /**
155         * Get the centre of the effect in the Y direction as a proportion of the image size.
156         * @return the center
157     * @see #setCentreY
158         */
159        public float getCentreY() {
160                return centreY;
161        }
162        
163        /**
164         * Set the centre of the effect as a proportion of the image size.
165         * @param centre the center
166     * @see #getCentre
167         */
168        public void setCentre( Point2D centre ) {
169                this.centreX = (float)centre.getX();
170                this.centreY = (float)centre.getY();
171        }
172
173        /**
174         * Get the centre of the effect as a proportion of the image size.
175         * @return the center
176     * @see #setCentre
177         */
178        public Point2D getCentre() {
179                return new Point2D.Float( centreX, centreY );
180        }
181        
182    private int log2( int n ) {
183        int m = 1;
184        int log2n = 0;
185
186        while (m < n) {
187            m *= 2;
188            log2n++;
189        }
190        return log2n;
191    }
192
193    public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
194        if ( dst == null )
195            dst = createCompatibleDestImage( src, null );
196        BufferedImage tsrc = src;
197        float cx = (float)src.getWidth() * centreX;
198        float cy = (float)src.getHeight() * centreY;
199        float imageRadius = (float)Math.sqrt( cx*cx + cy*cy );
200        float translateX = (float)(distance * Math.cos( angle ));
201        float translateY = (float)(distance * -Math.sin( angle ));
202        float scale = zoom;
203        float rotate = rotation;
204        float maxDistance = distance + Math.abs(rotation*imageRadius) + zoom*imageRadius;
205        int steps = log2((int)maxDistance);
206
207                translateX /= maxDistance;
208                translateY /= maxDistance;
209                scale /= maxDistance;
210                rotate /= maxDistance;
211                
212        if ( steps == 0 ) {
213            Graphics2D g = dst.createGraphics();
214            g.drawRenderedImage( src, null );
215            g.dispose();
216            return dst;
217        }
218        
219        BufferedImage tmp = createCompatibleDestImage( src, null );
220        for ( int i = 0; i < steps; i++ ) {
221            Graphics2D g = tmp.createGraphics();
222            g.drawImage( tsrc, null, null );
223                        g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
224                        g.setRenderingHint( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR );
225                        g.setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, 0.5f ) );
226
227            g.translate( cx+translateX, cy+translateY );
228            g.scale( 1.0001+scale, 1.0001+scale );  // The .0001 works round a bug on Windows where drawImage throws an ArrayIndexOutofBoundException
229            if ( rotation != 0 )
230                g.rotate( rotate );
231            g.translate( -cx, -cy );
232
233            g.drawImage( dst, null, null );
234            g.dispose();
235            BufferedImage ti = dst;
236            dst = tmp;
237            tmp = ti;
238            tsrc = dst;
239
240            translateX *= 2;
241            translateY *= 2;
242            scale *= 2;
243            rotate *= 2;
244        }
245        return dst;
246    }
247    
248        public String toString() {
249                return "Blur/Faster Motion Blur...";
250        }
251}