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.image.*; 020import java.awt.geom.*; 021 022/** 023 * A filter which produces motion blur the slow, but higher-quality way. 024 */ 025public class MotionBlurFilter extends AbstractBufferedImageOp { 026 027 private float angle = 0.0f; 028 private float falloff = 1.0f; 029 private float distance = 1.0f; 030 private float zoom = 0.0f; 031 private float rotation = 0.0f; 032 private boolean wrapEdges = false; 033 private boolean premultiplyAlpha = true; 034 035 /** 036 * Construct a MotionBlurFilter. 037 */ 038 public MotionBlurFilter() { 039 } 040 041 /** 042 * Construct a MotionBlurFilter. 043 * @param distance the distance of blur. 044 * @param angle the angle of blur. 045 * @param rotation the angle of rotation. 046 * @param zoom the zoom factor. 047 */ 048 public MotionBlurFilter( float distance, float angle, float rotation, float zoom ) { 049 this.distance = distance; 050 this.angle = angle; 051 this.rotation = rotation; 052 this.zoom = zoom; 053 } 054 055 /** 056 * Specifies the angle of blur. 057 * @param angle the angle of blur. 058 * @angle 059 * @see #getAngle 060 */ 061 public void setAngle( float angle ) { 062 this.angle = angle; 063 } 064 065 /** 066 * Returns the angle of blur. 067 * @return the angle of blur. 068 * @see #setAngle 069 */ 070 public float getAngle() { 071 return angle; 072 } 073 074 /** 075 * Set the distance of blur. 076 * @param distance the distance of blur. 077 * @see #getDistance 078 */ 079 public void setDistance( float distance ) { 080 this.distance = distance; 081 } 082 083 /** 084 * Get the distance of blur. 085 * @return the distance of blur. 086 * @see #setDistance 087 */ 088 public float getDistance() { 089 return distance; 090 } 091 092 /** 093 * Set the blur rotation. 094 * @param rotation the angle of rotation. 095 * @see #getRotation 096 */ 097 public void setRotation( float rotation ) { 098 this.rotation = rotation; 099 } 100 101 /** 102 * Get the blur rotation. 103 * @return the angle of rotation. 104 * @see #setRotation 105 */ 106 public float getRotation() { 107 return rotation; 108 } 109 110 /** 111 * Set the blur zoom. 112 * @param zoom the zoom factor. 113 * @see #getZoom 114 */ 115 public void setZoom( float zoom ) { 116 this.zoom = zoom; 117 } 118 119 /** 120 * Get the blur zoom. 121 * @return the zoom factor. 122 * @see #setZoom 123 */ 124 public float getZoom() { 125 return zoom; 126 } 127 128 /** 129 * Set whether to wrap at the image edges. 130 * @param wrapEdges true if it should wrap. 131 * @see #getWrapEdges 132 */ 133 public void setWrapEdges(boolean wrapEdges) { 134 this.wrapEdges = wrapEdges; 135 } 136 137 /** 138 * Get whether to wrap at the image edges. 139 * @return true if it should wrap. 140 * @see #setWrapEdges 141 */ 142 public boolean getWrapEdges() { 143 return wrapEdges; 144 } 145 146 /** 147 * Set whether to premultiply the alpha channel. 148 * @param premultiplyAlpha true to premultiply the alpha 149 * @see #getPremultiplyAlpha 150 */ 151 public void setPremultiplyAlpha( boolean premultiplyAlpha ) { 152 this.premultiplyAlpha = premultiplyAlpha; 153 } 154 155 /** 156 * Get whether to premultiply the alpha channel. 157 * @return true to premultiply the alpha 158 * @see #setPremultiplyAlpha 159 */ 160 public boolean getPremultiplyAlpha() { 161 return premultiplyAlpha; 162 } 163 164 public BufferedImage filter( BufferedImage src, BufferedImage dst ) { 165 int width = src.getWidth(); 166 int height = src.getHeight(); 167 168 if ( dst == null ) 169 dst = createCompatibleDestImage( src, null ); 170 171 int[] inPixels = new int[width*height]; 172 int[] outPixels = new int[width*height]; 173 getRGB( src, 0, 0, width, height, inPixels ); 174 175 float sinAngle = (float)Math.sin(angle); 176 float cosAngle = (float)Math.cos(angle); 177 178 float total; 179 int cx = width/2; 180 int cy = height/2; 181 int index = 0; 182 183 float imageRadius = (float)Math.sqrt( cx*cx + cy*cy ); 184 float translateX = (float)(distance * Math.cos( angle )); 185 float translateY = (float)(distance * -Math.sin( angle )); 186 float maxDistance = distance + Math.abs(rotation*imageRadius) + zoom*imageRadius; 187 int repetitions = (int)maxDistance; 188 AffineTransform t = new AffineTransform(); 189 Point2D.Float p = new Point2D.Float(); 190 191 if ( premultiplyAlpha ) 192 ImageMath.premultiply( inPixels, 0, inPixels.length ); 193 for (int y = 0; y < height; y++) { 194 for (int x = 0; x < width; x++) { 195 int a = 0, r = 0, g = 0, b = 0; 196 int count = 0; 197 for (int i = 0; i < repetitions; i++) { 198 int newX = x, newY = y; 199 float f = (float)i/repetitions; 200 201 p.x = x; 202 p.y = y; 203 t.setToIdentity(); 204 t.translate( cx+f*translateX, cy+f*translateY ); 205 float s = 1-zoom*f; 206 t.scale( s, s ); 207 if ( rotation != 0 ) 208 t.rotate( -rotation*f ); 209 t.translate( -cx, -cy ); 210 t.transform( p, p ); 211 newX = (int)p.x; 212 newY = (int)p.y; 213 214 if (newX < 0 || newX >= width) { 215 if ( wrapEdges ) 216 newX = ImageMath.mod( newX, width ); 217 else 218 break; 219 } 220 if (newY < 0 || newY >= height) { 221 if ( wrapEdges ) 222 newY = ImageMath.mod( newY, height ); 223 else 224 break; 225 } 226 227 count++; 228 int rgb = inPixels[newY*width+newX]; 229 a += (rgb >> 24) & 0xff; 230 r += (rgb >> 16) & 0xff; 231 g += (rgb >> 8) & 0xff; 232 b += rgb & 0xff; 233 } 234 if (count == 0) { 235 outPixels[index] = inPixels[index]; 236 } else { 237 a = PixelUtils.clamp((int)(a/count)); 238 r = PixelUtils.clamp((int)(r/count)); 239 g = PixelUtils.clamp((int)(g/count)); 240 b = PixelUtils.clamp((int)(b/count)); 241 outPixels[index] = (a << 24) | (r << 16) | (g << 8) | b; 242 } 243 index++; 244 } 245 } 246 if ( premultiplyAlpha ) 247 ImageMath.unpremultiply( outPixels, 0, inPixels.length ); 248 249 setRGB( dst, 0, 0, width, height, outPixels ); 250 return dst; 251 } 252 253 public String toString() { 254 return "Blur/Motion Blur..."; 255 } 256} 257