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