001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019/*
020*
021
022Licensed under the Apache License, Version 2.0 (the "License");
023you may not use this file except in compliance with the License.
024You may obtain a copy of the License at
025
026   http://www.apache.org/licenses/LICENSE-2.0
027
028Unless required by applicable law or agreed to in writing, software
029distributed under the License is distributed on an "AS IS" BASIS,
030WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
031See the License for the specific language governing permissions and
032limitations under the License.
033*/
034
035package lucee.runtime.img.filter;import java.awt.geom.AffineTransform;
036import java.awt.geom.Point2D;
037import java.awt.image.BufferedImage;
038
039import lucee.runtime.engine.ThreadLocalPageContext;
040import lucee.runtime.exp.FunctionException;
041import lucee.runtime.exp.PageException;
042import lucee.runtime.img.ImageUtil;
043import lucee.runtime.type.KeyImpl;
044import lucee.runtime.type.Struct;
045import lucee.runtime.type.util.CollectionUtil;
046
047/**
048 * A filter which produces motion blur the slow, but higher-quality way.
049 */
050public class MotionBlurFilter extends AbstractBufferedImageOp  implements DynFiltering {
051
052        private float angle = 0.0f;
053        private float falloff = 1.0f;
054        private float distance = 1.0f;
055        private float zoom = 0.0f;
056        private float rotation = 0.0f;
057        private boolean wrapEdges = false;
058        private boolean premultiplyAlpha = true;
059
060    /**
061     * Construct a MotionBlurFilter.
062     */
063        public MotionBlurFilter() {
064        }
065
066    /**
067     * Construct a MotionBlurFilter.
068     * @param distance the distance of blur.
069     * @param angle the angle of blur.
070     * @param rotation the angle of rotation.
071     * @param zoom the zoom factor.
072     */
073        public MotionBlurFilter( float distance, float angle, float rotation, float zoom ) {
074        this.distance = distance;
075        this.angle = angle;
076        this.rotation = rotation;
077        this.zoom = zoom;
078    }
079    
080        /**
081     * Specifies the angle of blur.
082     * @param angle the angle of blur.
083     * @angle
084     * @see #getAngle
085     */
086        public void setAngle( float angle ) {
087                this.angle = angle;
088        }
089
090        /**
091     * Returns the angle of blur.
092     * @return the angle of blur.
093     * @see #setAngle
094     */
095        public float getAngle() {
096                return angle;
097        }
098        
099        /**
100     * Set the distance of blur.
101     * @param distance the distance of blur.
102     * @see #getDistance
103     */
104        public void setDistance( float distance ) {
105                this.distance = distance;
106        }
107
108        /**
109     * Get the distance of blur.
110     * @return the distance of blur.
111     * @see #setDistance
112     */
113        public float getDistance() {
114                return distance;
115        }
116        
117        /**
118     * Set the blur rotation.
119     * @param rotation the angle of rotation.
120     * @see #getRotation
121     */
122        public void setRotation( float rotation ) {
123                this.rotation = rotation;
124        }
125
126        /**
127     * Get the blur rotation.
128     * @return the angle of rotation.
129     * @see #setRotation
130     */
131        public float getRotation() {
132                return rotation;
133        }
134        
135        public void setZoom( float zoom ) {
136                this.zoom = zoom;
137        }
138
139        /**
140     * Get the blur zoom.
141     * @return the zoom factor.
142     * @see #setZoom
143     */
144        public float getZoom() {
145                return zoom;
146        }
147        
148        /**
149     * Set whether to wrap at the image edges.
150     * @param wrapEdges true if it should wrap.
151     * @see #getWrapEdges
152     */
153        public void setWrapEdges(boolean wrapEdges) {
154                this.wrapEdges = wrapEdges;
155        }
156
157        /**
158     * Get whether to wrap at the image edges.
159     * @return true if it should wrap.
160     * @see #setWrapEdges
161     */
162        public boolean getWrapEdges() {
163                return wrapEdges;
164        }
165
166    /**
167     * Set whether to premultiply the alpha channel.
168     * @param premultiplyAlpha true to premultiply the alpha
169     * @see #getPremultiplyAlpha
170     */
171        public void setPremultiplyAlpha( boolean premultiplyAlpha ) {
172                this.premultiplyAlpha = premultiplyAlpha;
173        }
174
175    /**
176     * Get whether to premultiply the alpha channel.
177     * @return true to premultiply the alpha
178     * @see #setPremultiplyAlpha
179     */
180        public boolean getPremultiplyAlpha() {
181                return premultiplyAlpha;
182        }
183
184    public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
185        int width = src.getWidth();
186        int height = src.getHeight();
187
188        if ( dst == null )
189            dst = createCompatibleDestImage( src, null );
190
191        int[] inPixels = new int[width*height];
192        int[] outPixels = new int[width*height];
193        getRGB( src, 0, 0, width, height, inPixels );
194
195                //float sinAngle = (float)Math.sin(angle);
196                //float cosAngle = (float)Math.cos(angle);
197
198                //float total;
199                int cx = width/2;
200                int cy = height/2;
201                int index = 0;
202
203        float imageRadius = (float)Math.sqrt( cx*cx + cy*cy );
204        float translateX = (float)(distance * Math.cos( angle ));
205        float translateY = (float)(distance * -Math.sin( angle ));
206                float maxDistance = distance + Math.abs(rotation*imageRadius) + zoom*imageRadius;
207                int repetitions = (int)maxDistance;
208                AffineTransform t = new AffineTransform();
209                Point2D.Float p = new Point2D.Float();
210
211        if ( premultiplyAlpha )
212                        ImageMath.premultiply( inPixels, 0, inPixels.length );
213                for (int y = 0; y < height; y++) {
214                        for (int x = 0; x < width; x++) {
215                                int a = 0, r = 0, g = 0, b = 0;
216                                int count = 0;
217                                for (int i = 0; i < repetitions; i++) {
218                                        int newX = x, newY = y;
219                                        float f = (float)i/repetitions;
220
221                                        p.x = x;
222                                        p.y = y;
223                                        t.setToIdentity();
224                                        t.translate( cx+f*translateX, cy+f*translateY );
225                                        float s = 1-zoom*f;
226                                        t.scale( s, s );
227                                        if ( rotation != 0 )
228                                                t.rotate( -rotation*f );
229                                        t.translate( -cx, -cy );
230                                        t.transform( p, p );
231                                        newX = (int)p.x;
232                                        newY = (int)p.y;
233
234                                        if (newX < 0 || newX >= width) {
235                                                if ( wrapEdges )
236                                                        newX = ImageMath.mod( newX, width );
237                                                else
238                                                        break;
239                                        }
240                                        if (newY < 0 || newY >= height) {
241                                                if ( wrapEdges )
242                                                        newY = ImageMath.mod( newY, height );
243                                                else
244                                                        break;
245                                        }
246
247                                        count++;
248                                        int rgb = inPixels[newY*width+newX];
249                                        a += (rgb >> 24) & 0xff;
250                                        r += (rgb >> 16) & 0xff;
251                                        g += (rgb >> 8) & 0xff;
252                                        b += rgb & 0xff;
253                                }
254                                if (count == 0) {
255                                        outPixels[index] = inPixels[index];
256                                } else {
257                                        a = PixelUtils.clamp((a/count));
258                                        r = PixelUtils.clamp((r/count));
259                                        g = PixelUtils.clamp((g/count));
260                                        b = PixelUtils.clamp((b/count));
261                                        outPixels[index] = (a << 24) | (r << 16) | (g << 8) | b;
262                                }
263                                index++;
264                        }
265                }
266        if ( premultiplyAlpha )
267                        ImageMath.unpremultiply( outPixels, 0, inPixels.length );
268
269        setRGB( dst, 0, 0, width, height, outPixels );
270        return dst;
271    }
272
273        public String toString() {
274                return "Blur/Motion Blur...";
275        }
276        public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src);
277                Object o;
278                if((o=parameters.removeEL(KeyImpl.init("PremultiplyAlpha")))!=null)setPremultiplyAlpha(ImageFilterUtil.toBooleanValue(o,"PremultiplyAlpha"));
279                if((o=parameters.removeEL(KeyImpl.init("Angle")))!=null)setAngle(ImageFilterUtil.toFloatValue(o,"Angle"));
280                if((o=parameters.removeEL(KeyImpl.init("Distance")))!=null)setDistance(ImageFilterUtil.toFloatValue(o,"Distance"));
281                if((o=parameters.removeEL(KeyImpl.init("Rotation")))!=null)setRotation(ImageFilterUtil.toFloatValue(o,"Rotation"));
282                if((o=parameters.removeEL(KeyImpl.init("Zoom")))!=null)setZoom(ImageFilterUtil.toFloatValue(o,"Zoom"));
283                if((o=parameters.removeEL(KeyImpl.init("WrapEdges")))!=null)setWrapEdges(ImageFilterUtil.toBooleanValue(o,"WrapEdges"));
284
285                // check for arguments not supported
286                if(parameters.size()>0) {
287                        throw new FunctionException(ThreadLocalPageContext.get(), "ImageFilter", 3, "parameters", "the parameter"+(parameters.size()>1?"s":"")+" ["+CollectionUtil.getKeyList(parameters,", ")+"] "+(parameters.size()>1?"are":"is")+" not allowed, only the following parameters are supported [PremultiplyAlpha, Angle, Distance, Rotation, Zoom, WrapEdges]");
288                }
289
290                return filter(src, dst);
291        }
292}
293