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