001/* 002Copyright 2006 Jerry Huxtable 003 004Licensed under the Apache License, Version 2.0 (the "License"); 005you may not use this file except in compliance with the License. 006You may obtain a copy of the License at 007 008 http://www.apache.org/licenses/LICENSE-2.0 009 010Unless required by applicable law or agreed to in writing, software 011distributed under the License is distributed on an "AS IS" BASIS, 012WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013See the License for the specific language governing permissions and 014limitations under the License. 015*/ 016 017package com.jhlabs.image; 018 019import java.awt.*; 020import java.awt.geom.*; 021import java.awt.image.*; 022 023/** 024 * A filter which priduces a video feedback effect by repeated transformations. 025 */ 026public class FeedbackFilter extends AbstractBufferedImageOp { 027 private float centreX = 0.5f, centreY = 0.5f; 028 private float distance; 029 private float angle; 030 private float rotation; 031 private float zoom; 032 private float startAlpha = 1; 033 private float endAlpha = 1; 034 private int iterations; 035 036 /** 037 * Construct a FeedbackFilter. 038 */ 039 public FeedbackFilter() { 040 } 041 042 /** 043 * Construct a FeedbackFilter. 044 * @param distance the distance to move on each iteration 045 * @param angle the angle to move on each iteration 046 * @param rotation the amount to rotate on each iteration 047 * @param zoom the amount to scale on each iteration 048 */ 049 public FeedbackFilter( float distance, float angle, float rotation, float zoom ) { 050 this.distance = distance; 051 this.angle = angle; 052 this.rotation = rotation; 053 this.zoom = zoom; 054 } 055 056 /** 057 * Specifies the angle of each iteration. 058 * @param angle the angle of each iteration. 059 * @angle 060 * @see #getAngle 061 */ 062 public void setAngle( float angle ) { 063 this.angle = angle; 064 } 065 066 /** 067 * Returns the angle of each iteration. 068 * @return the angle of each iteration. 069 * @see #setAngle 070 */ 071 public float getAngle() { 072 return angle; 073 } 074 075 /** 076 * Specifies the distance to move on each iteration. 077 * @param distance the distance 078 * @see #getDistance 079 */ 080 public void setDistance( float distance ) { 081 this.distance = distance; 082 } 083 084 /** 085 * Get the distance to move on each iteration. 086 * @return the distance 087 * @see #setDistance 088 */ 089 public float getDistance() { 090 return distance; 091 } 092 093 /** 094 * Specifies the amount of rotation on each iteration. 095 * @param rotation the angle of rotation 096 * @angle 097 * @see #getRotation 098 */ 099 public void setRotation( float rotation ) { 100 this.rotation = rotation; 101 } 102 103 /** 104 * Returns the amount of rotation on each iteration. 105 * @return the angle of rotation 106 * @angle 107 * @see #setRotation 108 */ 109 public float getRotation() { 110 return rotation; 111 } 112 113 /** 114 * Specifies the amount to scale on each iteration. 115 * @param zoom the zoom factor 116 * @see #getZoom 117 */ 118 public void setZoom( float zoom ) { 119 this.zoom = zoom; 120 } 121 122 /** 123 * Returns the amount to scale on each iteration. 124 * @return the zoom factor 125 * @see #setZoom 126 */ 127 public float getZoom() { 128 return zoom; 129 } 130 131 /** 132 * Set the alpha value at the first iteration. 133 * @param startAlpha the alpha value 134 * @min-value 0 135 * @max-value 1 136 * @see #getStartAlpha 137 */ 138 public void setStartAlpha( float startAlpha ) { 139 this.startAlpha = startAlpha; 140 } 141 142 /** 143 * Get the alpha value at the first iteration. 144 * @return the alpha value 145 * @see #setStartAlpha 146 */ 147 public float getStartAlpha() { 148 return startAlpha; 149 } 150 151 /** 152 * Set the alpha value at the last iteration. 153 * @param endAlpha the alpha value 154 * @min-value 0 155 * @max-value 1 156 * @see #getEndAlpha 157 */ 158 public void setEndAlpha( float endAlpha ) { 159 this.endAlpha = endAlpha; 160 } 161 162 /** 163 * Get the alpha value at the last iteration. 164 * @return the alpha value 165 * @see #setEndAlpha 166 */ 167 public float getEndAlpha() { 168 return endAlpha; 169 } 170 171 /** 172 * Set the centre of the effect in the X direction as a proportion of the image size. 173 * @param centreX the center 174 * @see #getCentreX 175 */ 176 public void setCentreX( float centreX ) { 177 this.centreX = centreX; 178 } 179 180 /** 181 * Get the centre of the effect in the X direction as a proportion of the image size. 182 * @return the center 183 * @see #setCentreX 184 */ 185 public float getCentreX() { 186 return centreX; 187 } 188 189 /** 190 * Set the centre of the effect in the Y direction as a proportion of the image size. 191 * @param centreY the center 192 * @see #getCentreY 193 */ 194 public void setCentreY( float centreY ) { 195 this.centreY = centreY; 196 } 197 198 /** 199 * Get the centre of the effect in the Y direction as a proportion of the image size. 200 * @return the center 201 * @see #setCentreY 202 */ 203 public float getCentreY() { 204 return centreY; 205 } 206 207 /** 208 * Set the centre of the effect as a proportion of the image size. 209 * @param centre the center 210 * @see #getCentre 211 */ 212 public void setCentre( Point2D centre ) { 213 this.centreX = (float)centre.getX(); 214 this.centreY = (float)centre.getY(); 215 } 216 217 /** 218 * Get the centre of the effect as a proportion of the image size. 219 * @return the center 220 * @see #setCentre 221 */ 222 public Point2D getCentre() { 223 return new Point2D.Float( centreX, centreY ); 224 } 225 226 /** 227 * Set the number of iterations. 228 * @param iterations the number of iterations 229 * @min-value 0 230 * @see #getIterations 231 */ 232 public void setIterations( int iterations ) { 233 this.iterations = iterations; 234 } 235 236 /** 237 * Get the number of iterations. 238 * @return the number of iterations 239 * @see #setIterations 240 */ 241 public int getIterations() { 242 return iterations; 243 } 244 245 public BufferedImage filter( BufferedImage src, BufferedImage dst ) { 246 if ( dst == null ) 247 dst = createCompatibleDestImage( src, null ); 248 float cx = (float)src.getWidth() * centreX; 249 float cy = (float)src.getHeight() * centreY; 250 float imageRadius = (float)Math.sqrt( cx*cx + cy*cy ); 251 float translateX = (float)(distance * Math.cos( angle )); 252 float translateY = (float)(distance * -Math.sin( angle )); 253 float scale = (float)Math.exp(zoom); 254 float rotate = rotation; 255 256 if ( iterations == 0 ) { 257 Graphics2D g = dst.createGraphics(); 258 g.drawRenderedImage( src, null ); 259 g.dispose(); 260 return dst; 261 } 262 263 Graphics2D g = dst.createGraphics(); 264 g.drawImage( src, null, null ); 265 for ( int i = 0; i < iterations; i++ ) { 266 g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); 267 g.setRenderingHint( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR ); 268 g.setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, ImageMath.lerp( (float)i/(iterations-1), startAlpha, endAlpha ) ) ); 269 270 g.translate( cx+translateX, cy+translateY ); 271 g.scale( scale, scale ); // The .0001 works round a bug on Windows where drawImage throws an ArrayIndexOutofBoundException 272 if ( rotation != 0 ) 273 g.rotate( rotate ); 274 g.translate( -cx, -cy ); 275 276 g.drawImage( src, null, null ); 277 } 278 g.dispose(); 279 return dst; 280 } 281 282 public String toString() { 283 return "Effects/Feedback..."; 284 } 285}