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.Rectangle; 020 import java.awt.geom.AffineTransform; 021 import java.awt.geom.Point2D; 022 import java.awt.geom.Rectangle2D; 023 import java.awt.image.BandCombineOp; 024 import java.awt.image.BufferedImage; 025 import java.awt.image.ColorModel; 026 027 import railo.runtime.engine.ThreadLocalPageContext; 028 import railo.runtime.exp.FunctionException; 029 import railo.runtime.exp.PageException; 030 import railo.runtime.img.ImageUtil; 031 import railo.runtime.type.KeyImpl; 032 import railo.runtime.type.Struct; 033 import railo.runtime.type.util.CollectionUtil; 034 035 /** 036 * A filter which draws a drop shadow based on the alpha channel of the image. 037 */ 038 public class ShadowFilter extends AbstractBufferedImageOp implements DynFiltering { 039 040 private float radius = 5; 041 private float angle = (float)Math.PI*6/4; 042 private float distance = 5; 043 private float opacity = 0.5f; 044 private boolean addMargins = false; 045 private boolean shadowOnly = false; 046 private int shadowColor = 0xff000000; 047 048 /** 049 * Construct a ShadowFilter. 050 */ 051 public ShadowFilter() { 052 } 053 054 /** 055 * Construct a ShadowFilter. 056 * @param radius the radius of the shadow 057 * @param xOffset the X offset of the shadow 058 * @param yOffset the Y offset of the shadow 059 * @param opacity the opacity of the shadow 060 */ 061 public ShadowFilter(float radius, float xOffset, float yOffset, float opacity) { 062 this.radius = radius; 063 this.angle = (float)Math.atan2(yOffset, xOffset); 064 this.distance = (float)Math.sqrt(xOffset*xOffset + yOffset*yOffset); 065 this.opacity = opacity; 066 } 067 068 /** 069 * Specifies the angle of the shadow. 070 * @param angle the angle of the shadow. 071 * @angle 072 * @see #getAngle 073 */ 074 public void setAngle(float angle) { 075 this.angle = angle; 076 } 077 078 /** 079 * Returns the angle of the shadow. 080 * @return the angle of the shadow. 081 * @see #setAngle 082 */ 083 public float getAngle() { 084 return angle; 085 } 086 087 /** 088 * Set the distance of the shadow. 089 * @param distance the distance. 090 * @see #getDistance 091 */ 092 public void setDistance(float distance) { 093 this.distance = distance; 094 } 095 096 /** 097 * Get the distance of the shadow. 098 * @return the distance. 099 * @see #setDistance 100 */ 101 public float getDistance() { 102 return distance; 103 } 104 105 /** 106 * Set the radius of the kernel, and hence the amount of blur. The bigger the radius, the longer this filter will take. 107 * @param radius the radius of the blur in pixels. 108 * @see #getRadius 109 */ 110 public void setRadius(float radius) { 111 this.radius = radius; 112 } 113 114 /** 115 * Get the radius of the kernel. 116 * @return the radius 117 * @see #setRadius 118 */ 119 public float getRadius() { 120 return radius; 121 } 122 123 /** 124 * Set the opacity of the shadow. 125 * @param opacity the opacity. 126 * @see #getOpacity 127 */ 128 public void setOpacity(float opacity) { 129 this.opacity = opacity; 130 } 131 132 /** 133 * Get the opacity of the shadow. 134 * @return the opacity. 135 * @see #setOpacity 136 */ 137 public float getOpacity() { 138 return opacity; 139 } 140 141 /** 142 * Set the color of the shadow. 143 * @param shadowColor the color. 144 * @see #getShadowColor 145 */ 146 public void setShadowColor(int shadowColor) { 147 this.shadowColor = shadowColor; 148 } 149 150 /** 151 * Get the color of the shadow. 152 * @return the color. 153 * @see #setShadowColor 154 */ 155 public int getShadowColor() { 156 return shadowColor; 157 } 158 159 /** 160 * Set whether to increase the size of the output image to accomodate the shadow. 161 * @param addMargins true to add margins. 162 * @see #getAddMargins 163 */ 164 public void setAddMargins(boolean addMargins) { 165 this.addMargins = addMargins; 166 } 167 168 /** 169 * Get whether to increase the size of the output image to accomodate the shadow. 170 * @return true to add margins. 171 * @see #setAddMargins 172 */ 173 public boolean getAddMargins() { 174 return addMargins; 175 } 176 177 /** 178 * Set whether to only draw the shadow without the original image. 179 * @param shadowOnly true to only draw the shadow. 180 * @see #getShadowOnly 181 */ 182 public void setShadowOnly(boolean shadowOnly) { 183 this.shadowOnly = shadowOnly; 184 } 185 186 /** 187 * Get whether to only draw the shadow without the original image. 188 * @return true to only draw the shadow. 189 * @see #setShadowOnly 190 */ 191 public boolean getShadowOnly() { 192 return shadowOnly; 193 } 194 195 public Rectangle2D getBounds2D( BufferedImage src ) { 196 Rectangle r = new Rectangle(0, 0, src.getWidth(), src.getHeight()); 197 if ( addMargins ) { 198 float xOffset = distance*(float)Math.cos(angle); 199 float yOffset = -distance*(float)Math.sin(angle); 200 r.width += (int)(Math.abs(xOffset)+2*radius); 201 r.height += (int)(Math.abs(yOffset)+2*radius); 202 } 203 return r; 204 } 205 206 public Point2D getPoint2D( Point2D srcPt, Point2D dstPt ) { 207 if ( dstPt == null ) 208 dstPt = new Point2D.Double(); 209 210 if ( addMargins ) { 211 float xOffset = distance*(float)Math.cos(angle); 212 float yOffset = -distance*(float)Math.sin(angle); 213 float topShadow = Math.max( 0, radius-yOffset ); 214 float leftShadow = Math.max( 0, radius-xOffset ); 215 dstPt.setLocation( srcPt.getX()+leftShadow, srcPt.getY()+topShadow ); 216 } else 217 dstPt.setLocation( srcPt.getX(), srcPt.getY() ); 218 219 return dstPt; 220 } 221 222 public BufferedImage filter( BufferedImage src, BufferedImage dst ) { 223 int width = src.getWidth(); 224 int height = src.getHeight(); 225 226 if ( dst == null ) { 227 if ( addMargins ) { 228 ColorModel cm = src.getColorModel(); 229 dst = new BufferedImage(cm, cm.createCompatibleWritableRaster(src.getWidth(), src.getHeight()), cm.isAlphaPremultiplied(), null); 230 } else 231 dst = createCompatibleDestImage( src, null ); 232 } 233 234 float shadowR = ((shadowColor >> 16) & 0xff) / 255f; 235 float shadowG = ((shadowColor >> 8) & 0xff) / 255f; 236 float shadowB = (shadowColor & 0xff) / 255f; 237 238 // Make a black mask from the image's alpha channel 239 float[][] extractAlpha = { 240 { 0, 0, 0, shadowR }, 241 { 0, 0, 0, shadowG }, 242 { 0, 0, 0, shadowB }, 243 { 0, 0, 0, opacity } 244 }; 245 BufferedImage shadow = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); 246 new BandCombineOp( extractAlpha, null ).filter( src.getRaster(), shadow.getRaster() ); 247 shadow = new GaussianFilter( radius ).filter( shadow, (BufferedImage)null ); 248 249 float xOffset = distance*(float)Math.cos(angle); 250 float yOffset = -distance*(float)Math.sin(angle); 251 252 Graphics2D g = dst.createGraphics(); 253 g.setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, opacity ) ); 254 if ( addMargins ) { 255 //float radius2 = radius/2; 256 float topShadow = Math.max( 0, radius-yOffset ); 257 float leftShadow = Math.max( 0, radius-xOffset ); 258 g.translate( topShadow, leftShadow ); 259 } 260 g.drawRenderedImage( shadow, AffineTransform.getTranslateInstance( xOffset, yOffset ) ); 261 if ( !shadowOnly ) { 262 g.setComposite( AlphaComposite.SrcOver ); 263 g.drawRenderedImage( src, null ); 264 } 265 g.dispose(); 266 267 return dst; 268 } 269 270 public String toString() { 271 return "Stylize/Drop Shadow..."; 272 } 273 public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src); 274 Object o; 275 if((o=parameters.removeEL(KeyImpl.init("Radius")))!=null)setRadius(ImageFilterUtil.toFloatValue(o,"Radius")); 276 if((o=parameters.removeEL(KeyImpl.init("Angle")))!=null)setAngle(ImageFilterUtil.toFloatValue(o,"Angle")); 277 if((o=parameters.removeEL(KeyImpl.init("Distance")))!=null)setDistance(ImageFilterUtil.toFloatValue(o,"Distance")); 278 if((o=parameters.removeEL(KeyImpl.init("Opacity")))!=null)setOpacity(ImageFilterUtil.toFloatValue(o,"Opacity")); 279 if((o=parameters.removeEL(KeyImpl.init("ShadowColor")))!=null)setShadowColor(ImageFilterUtil.toColorRGB(o,"ShadowColor")); 280 if((o=parameters.removeEL(KeyImpl.init("AddMargins")))!=null)setAddMargins(ImageFilterUtil.toBooleanValue(o,"AddMargins")); 281 if((o=parameters.removeEL(KeyImpl.init("ShadowOnly")))!=null)setShadowOnly(ImageFilterUtil.toBooleanValue(o,"ShadowOnly")); 282 283 // check for arguments not supported 284 if(parameters.size()>0) { 285 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 [Radius, Angle, Distance, Opacity, ShadowColor, AddMargins, ShadowOnly]"); 286 } 287 288 return filter(src, dst); 289 } 290 }