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.AlphaComposite; 018 import java.awt.Graphics2D; 019 import java.awt.RenderingHints; 020 import java.awt.geom.Point2D; 021 import java.awt.image.BufferedImage; 022 023 import railo.runtime.engine.ThreadLocalPageContext; 024 import railo.runtime.exp.FunctionException; 025 import railo.runtime.exp.PageException; 026 import railo.runtime.img.ImageUtil; 027 import railo.runtime.type.KeyImpl; 028 import railo.runtime.type.Struct; 029 import railo.runtime.type.util.CollectionUtil; 030 031 /** 032 * A filter which priduces a video feedback effect by repeated transformations. 033 */ 034 public class FeedbackFilter extends AbstractBufferedImageOp implements DynFiltering { 035 private float centreX = 0.5f, centreY = 0.5f; 036 private float distance; 037 private float angle; 038 private float rotation; 039 private float zoom; 040 private float startAlpha = 1; 041 private float endAlpha = 1; 042 private int iterations; 043 044 /** 045 * Construct a FeedbackFilter. 046 */ 047 public FeedbackFilter() { 048 } 049 050 /** 051 * Construct a FeedbackFilter. 052 * @param distance the distance to move on each iteration 053 * @param angle the angle to move on each iteration 054 * @param rotation the amount to rotate on each iteration 055 * @param zoom the amount to scale on each iteration 056 */ 057 public FeedbackFilter( float distance, float angle, float rotation, float zoom ) { 058 this.distance = distance; 059 this.angle = angle; 060 this.rotation = rotation; 061 this.zoom = zoom; 062 } 063 064 /** 065 * Specifies the angle of each iteration. 066 * @param angle the angle of each iteration. 067 * @angle 068 * @see #getAngle 069 */ 070 public void setAngle( float angle ) { 071 this.angle = angle; 072 } 073 074 /** 075 * Returns the angle of each iteration. 076 * @return the angle of each iteration. 077 * @see #setAngle 078 */ 079 public float getAngle() { 080 return angle; 081 } 082 083 /** 084 * Specifies the distance to move on each iteration. 085 * @param distance the distance 086 * @see #getDistance 087 */ 088 public void setDistance( float distance ) { 089 this.distance = distance; 090 } 091 092 /** 093 * Get the distance to move on each iteration. 094 * @return the distance 095 * @see #setDistance 096 */ 097 public float getDistance() { 098 return distance; 099 } 100 101 /** 102 * Specifies the amount of rotation on each iteration. 103 * @param rotation the angle of rotation 104 * @angle 105 * @see #getRotation 106 */ 107 public void setRotation( float rotation ) { 108 this.rotation = rotation; 109 } 110 111 /** 112 * Returns the amount of rotation on each iteration. 113 * @return the angle of rotation 114 * @angle 115 * @see #setRotation 116 */ 117 public float getRotation() { 118 return rotation; 119 } 120 121 /** 122 * Specifies the amount to scale on each iteration. 123 * @param zoom the zoom factor 124 * @see #getZoom 125 */ 126 public void setZoom( float zoom ) { 127 this.zoom = zoom; 128 } 129 130 /** 131 * Returns the amount to scale on each iteration. 132 * @return the zoom factor 133 * @see #setZoom 134 */ 135 public float getZoom() { 136 return zoom; 137 } 138 139 /** 140 * Set the alpha value at the first iteration. 141 * @param startAlpha the alpha value 142 * @min-value 0 143 * @max-value 1 144 * @see #getStartAlpha 145 */ 146 public void setStartAlpha( float startAlpha ) { 147 this.startAlpha = startAlpha; 148 } 149 150 /** 151 * Get the alpha value at the first iteration. 152 * @return the alpha value 153 * @see #setStartAlpha 154 */ 155 public float getStartAlpha() { 156 return startAlpha; 157 } 158 159 /** 160 * Set the alpha value at the last iteration. 161 * @param endAlpha the alpha value 162 * @min-value 0 163 * @max-value 1 164 * @see #getEndAlpha 165 */ 166 public void setEndAlpha( float endAlpha ) { 167 this.endAlpha = endAlpha; 168 } 169 170 /** 171 * Get the alpha value at the last iteration. 172 * @return the alpha value 173 * @see #setEndAlpha 174 */ 175 public float getEndAlpha() { 176 return endAlpha; 177 } 178 179 /** 180 * Set the centre of the effect in the X direction as a proportion of the image size. 181 * @param centreX the center 182 * @see #getCentreX 183 */ 184 public void setCentreX( float centreX ) { 185 this.centreX = centreX; 186 } 187 188 /** 189 * Get the centre of the effect in the X direction as a proportion of the image size. 190 * @return the center 191 * @see #setCentreX 192 */ 193 public float getCentreX() { 194 return centreX; 195 } 196 197 /** 198 * Set the centre of the effect in the Y direction as a proportion of the image size. 199 * @param centreY the center 200 * @see #getCentreY 201 */ 202 public void setCentreY( float centreY ) { 203 this.centreY = centreY; 204 } 205 206 /** 207 * Get the centre of the effect in the Y direction as a proportion of the image size. 208 * @return the center 209 * @see #setCentreY 210 */ 211 public float getCentreY() { 212 return centreY; 213 } 214 215 /** 216 * Set the centre of the effect as a proportion of the image size. 217 * @param centre the center 218 * @see #getCentre 219 */ 220 public void setCentre( Point2D centre ) { 221 this.centreX = (float)centre.getX(); 222 this.centreY = (float)centre.getY(); 223 } 224 225 /** 226 * Get the centre of the effect as a proportion of the image size. 227 * @return the center 228 * @see #setCentre 229 */ 230 public Point2D getCentre() { 231 return new Point2D.Float( centreX, centreY ); 232 } 233 234 /** 235 * Set the number of iterations. 236 * @param iterations the number of iterations 237 * @min-value 0 238 * @see #getIterations 239 */ 240 public void setIterations( int iterations ) { 241 this.iterations = iterations; 242 } 243 244 /** 245 * Get the number of iterations. 246 * @return the number of iterations 247 * @see #setIterations 248 */ 249 public int getIterations() { 250 return iterations; 251 } 252 253 public BufferedImage filter( BufferedImage src, BufferedImage dst ) { 254 if ( dst == null ) 255 dst = createCompatibleDestImage( src, null ); 256 float cx = src.getWidth() * centreX; 257 float cy = src.getHeight() * centreY; 258 //float imageRadius = (float)Math.sqrt( cx*cx + cy*cy ); 259 float translateX = (float)(distance * Math.cos( angle )); 260 float translateY = (float)(distance * -Math.sin( angle )); 261 float scale = (float)Math.exp(zoom); 262 float rotate = rotation; 263 264 if ( iterations == 0 ) { 265 Graphics2D g = dst.createGraphics(); 266 g.drawRenderedImage( src, null ); 267 g.dispose(); 268 return dst; 269 } 270 271 Graphics2D g = dst.createGraphics(); 272 g.drawImage( src, null, null ); 273 for ( int i = 0; i < iterations; i++ ) { 274 g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); 275 g.setRenderingHint( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR ); 276 g.setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, ImageMath.lerp( (float)i/(iterations-1), startAlpha, endAlpha ) ) ); 277 278 g.translate( cx+translateX, cy+translateY ); 279 g.scale( scale, scale ); // The .0001 works round a bug on Windows where drawImage throws an ArrayIndexOutofBoundException 280 if ( rotation != 0 ) 281 g.rotate( rotate ); 282 g.translate( -cx, -cy ); 283 284 g.drawImage( src, null, null ); 285 } 286 g.dispose(); 287 return dst; 288 } 289 290 public String toString() { 291 return "Effects/Feedback..."; 292 } 293 public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src); 294 Object o; 295 if((o=parameters.removeEL(KeyImpl.init("Iterations")))!=null)setIterations(ImageFilterUtil.toIntValue(o,"Iterations")); 296 if((o=parameters.removeEL(KeyImpl.init("Angle")))!=null)setAngle(ImageFilterUtil.toFloatValue(o,"Angle")); 297 if((o=parameters.removeEL(KeyImpl.init("CentreX")))!=null)setCentreX(ImageFilterUtil.toFloatValue(o,"CentreX")); 298 if((o=parameters.removeEL(KeyImpl.init("CentreY")))!=null)setCentreY(ImageFilterUtil.toFloatValue(o,"CentreY")); 299 //if((o=parameters.removeEL(KeyImpl.init("Centre")))!=null)setCentre(ImageFilterUtil.toPoint2D(o,"Centre")); 300 if((o=parameters.removeEL(KeyImpl.init("Distance")))!=null)setDistance(ImageFilterUtil.toFloatValue(o,"Distance")); 301 if((o=parameters.removeEL(KeyImpl.init("Rotation")))!=null)setRotation(ImageFilterUtil.toFloatValue(o,"Rotation")); 302 if((o=parameters.removeEL(KeyImpl.init("Zoom")))!=null)setZoom(ImageFilterUtil.toFloatValue(o,"Zoom")); 303 if((o=parameters.removeEL(KeyImpl.init("StartAlpha")))!=null)setStartAlpha(ImageFilterUtil.toFloatValue(o,"StartAlpha")); 304 if((o=parameters.removeEL(KeyImpl.init("EndAlpha")))!=null)setEndAlpha(ImageFilterUtil.toFloatValue(o,"EndAlpha")); 305 306 // check for arguments not supported 307 if(parameters.size()>0) { 308 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 [Iterations, Angle, CentreX, CentreY, Centre, Distance, Rotation, Zoom, StartAlpha, EndAlpha]"); 309 } 310 311 return filter(src, dst); 312 } 313 }