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