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