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.image.*;
020import java.awt.geom.*;
021
022/**
023 * A filter which produces motion blur the slow, but higher-quality way.
024 */
025public class MotionBlurFilter extends AbstractBufferedImageOp {
026
027        private float angle = 0.0f;
028        private float falloff = 1.0f;
029        private float distance = 1.0f;
030        private float zoom = 0.0f;
031        private float rotation = 0.0f;
032        private boolean wrapEdges = false;
033        private boolean premultiplyAlpha = true;
034
035    /**
036     * Construct a MotionBlurFilter.
037     */
038        public MotionBlurFilter() {
039        }
040
041    /**
042     * Construct a MotionBlurFilter.
043     * @param distance the distance of blur.
044     * @param angle the angle of blur.
045     * @param rotation the angle of rotation.
046     * @param zoom the zoom factor.
047     */
048        public MotionBlurFilter( float distance, float angle, float rotation, float zoom ) {
049        this.distance = distance;
050        this.angle = angle;
051        this.rotation = rotation;
052        this.zoom = zoom;
053    }
054    
055        /**
056     * Specifies the angle of blur.
057     * @param angle the angle of blur.
058     * @angle
059     * @see #getAngle
060     */
061        public void setAngle( float angle ) {
062                this.angle = angle;
063        }
064
065        /**
066     * Returns the angle of blur.
067     * @return the angle of blur.
068     * @see #setAngle
069     */
070        public float getAngle() {
071                return angle;
072        }
073        
074        /**
075     * Set the distance of blur.
076     * @param distance the distance of blur.
077     * @see #getDistance
078     */
079        public void setDistance( float distance ) {
080                this.distance = distance;
081        }
082
083        /**
084     * Get the distance of blur.
085     * @return the distance of blur.
086     * @see #setDistance
087     */
088        public float getDistance() {
089                return distance;
090        }
091        
092        /**
093     * Set the blur rotation.
094     * @param rotation the angle of rotation.
095     * @see #getRotation
096     */
097        public void setRotation( float rotation ) {
098                this.rotation = rotation;
099        }
100
101        /**
102     * Get the blur rotation.
103     * @return the angle of rotation.
104     * @see #setRotation
105     */
106        public float getRotation() {
107                return rotation;
108        }
109        
110        /**
111     * Set the blur zoom.
112     * @param zoom the zoom factor.
113     * @see #getZoom
114     */
115        public void setZoom( float zoom ) {
116                this.zoom = zoom;
117        }
118
119        /**
120     * Get the blur zoom.
121     * @return the zoom factor.
122     * @see #setZoom
123     */
124        public float getZoom() {
125                return zoom;
126        }
127        
128        /**
129     * Set whether to wrap at the image edges.
130     * @param wrapEdges true if it should wrap.
131     * @see #getWrapEdges
132     */
133        public void setWrapEdges(boolean wrapEdges) {
134                this.wrapEdges = wrapEdges;
135        }
136
137        /**
138     * Get whether to wrap at the image edges.
139     * @return true if it should wrap.
140     * @see #setWrapEdges
141     */
142        public boolean getWrapEdges() {
143                return wrapEdges;
144        }
145
146    /**
147     * Set whether to premultiply the alpha channel.
148     * @param premultiplyAlpha true to premultiply the alpha
149     * @see #getPremultiplyAlpha
150     */
151        public void setPremultiplyAlpha( boolean premultiplyAlpha ) {
152                this.premultiplyAlpha = premultiplyAlpha;
153        }
154
155    /**
156     * Get whether to premultiply the alpha channel.
157     * @return true to premultiply the alpha
158     * @see #setPremultiplyAlpha
159     */
160        public boolean getPremultiplyAlpha() {
161                return premultiplyAlpha;
162        }
163
164    public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
165        int width = src.getWidth();
166        int height = src.getHeight();
167
168        if ( dst == null )
169            dst = createCompatibleDestImage( src, null );
170
171        int[] inPixels = new int[width*height];
172        int[] outPixels = new int[width*height];
173        getRGB( src, 0, 0, width, height, inPixels );
174
175                float sinAngle = (float)Math.sin(angle);
176                float cosAngle = (float)Math.cos(angle);
177
178                float total;
179                int cx = width/2;
180                int cy = height/2;
181                int index = 0;
182
183        float imageRadius = (float)Math.sqrt( cx*cx + cy*cy );
184        float translateX = (float)(distance * Math.cos( angle ));
185        float translateY = (float)(distance * -Math.sin( angle ));
186                float maxDistance = distance + Math.abs(rotation*imageRadius) + zoom*imageRadius;
187                int repetitions = (int)maxDistance;
188                AffineTransform t = new AffineTransform();
189                Point2D.Float p = new Point2D.Float();
190
191        if ( premultiplyAlpha )
192                        ImageMath.premultiply( inPixels, 0, inPixels.length );
193                for (int y = 0; y < height; y++) {
194                        for (int x = 0; x < width; x++) {
195                                int a = 0, r = 0, g = 0, b = 0;
196                                int count = 0;
197                                for (int i = 0; i < repetitions; i++) {
198                                        int newX = x, newY = y;
199                                        float f = (float)i/repetitions;
200
201                                        p.x = x;
202                                        p.y = y;
203                                        t.setToIdentity();
204                                        t.translate( cx+f*translateX, cy+f*translateY );
205                                        float s = 1-zoom*f;
206                                        t.scale( s, s );
207                                        if ( rotation != 0 )
208                                                t.rotate( -rotation*f );
209                                        t.translate( -cx, -cy );
210                                        t.transform( p, p );
211                                        newX = (int)p.x;
212                                        newY = (int)p.y;
213
214                                        if (newX < 0 || newX >= width) {
215                                                if ( wrapEdges )
216                                                        newX = ImageMath.mod( newX, width );
217                                                else
218                                                        break;
219                                        }
220                                        if (newY < 0 || newY >= height) {
221                                                if ( wrapEdges )
222                                                        newY = ImageMath.mod( newY, height );
223                                                else
224                                                        break;
225                                        }
226
227                                        count++;
228                                        int rgb = inPixels[newY*width+newX];
229                                        a += (rgb >> 24) & 0xff;
230                                        r += (rgb >> 16) & 0xff;
231                                        g += (rgb >> 8) & 0xff;
232                                        b += rgb & 0xff;
233                                }
234                                if (count == 0) {
235                                        outPixels[index] = inPixels[index];
236                                } else {
237                                        a = PixelUtils.clamp((int)(a/count));
238                                        r = PixelUtils.clamp((int)(r/count));
239                                        g = PixelUtils.clamp((int)(g/count));
240                                        b = PixelUtils.clamp((int)(b/count));
241                                        outPixels[index] = (a << 24) | (r << 16) | (g << 8) | b;
242                                }
243                                index++;
244                        }
245                }
246        if ( premultiplyAlpha )
247                        ImageMath.unpremultiply( outPixels, 0, inPixels.length );
248
249        setRGB( dst, 0, 0, width, height, outPixels );
250        return dst;
251    }
252
253        public String toString() {
254                return "Blur/Motion Blur...";
255        }
256}
257