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.Rectangle; 018 import java.awt.RenderingHints; 019 import java.awt.geom.Point2D; 020 import java.awt.geom.Rectangle2D; 021 import java.awt.image.BufferedImage; 022 import java.awt.image.BufferedImageOp; 023 import java.awt.image.ColorModel; 024 import java.util.Random; 025 026 import railo.runtime.engine.ThreadLocalPageContext; 027 import railo.runtime.exp.FunctionException; 028 import railo.runtime.exp.PageException; 029 import railo.runtime.img.ImageUtil; 030 import railo.runtime.type.KeyImpl; 031 import railo.runtime.type.Struct; 032 import railo.runtime.type.util.CollectionUtil; 033 /** 034 * A filter which produces an image simulating brushed metal. 035 */ 036 public class BrushedMetalFilter implements BufferedImageOp, DynFiltering { 037 038 private int radius = 10; 039 private float amount = 0.1f; 040 private int color = 0xff888888; 041 private float shine = 0.1f; 042 private boolean monochrome = true; 043 private Random randomNumbers; 044 045 /** 046 * Constructs a BrushedMetalFilter object. 047 */ 048 public BrushedMetalFilter() { 049 } 050 051 /** 052 * Constructs a BrushedMetalFilter object. 053 * 054 * @param color an int specifying the metal color 055 * @param radius an int specifying the blur size 056 * @param amount a float specifying the amount of texture 057 * @param monochrome a boolean -- true for monochrome texture 058 * @param shine a float specifying the shine to add 059 */ 060 public BrushedMetalFilter( int color, int radius, float amount, boolean monochrome, float shine) { 061 this.color = color; 062 this.radius = radius; 063 this.amount = amount; 064 this.monochrome = monochrome; 065 this.shine = shine; 066 } 067 068 public BufferedImage filter( BufferedImage src, BufferedImage dst ) { 069 int width = src.getWidth(); 070 int height = src.getHeight(); 071 072 if ( dst == null ) 073 dst = createCompatibleDestImage( src, null ); 074 075 int[] inPixels = new int[width]; 076 int[] outPixels = new int[width]; 077 078 randomNumbers = new Random(0); 079 int a = color & 0xff000000; 080 int r = (color >> 16) & 0xff; 081 int g = (color >> 8) & 0xff; 082 int b = color & 0xff; 083 for ( int y = 0; y < height; y++ ) { 084 for ( int x = 0; x < width; x++ ) { 085 int tr = r; 086 int tg = g; 087 int tb = b; 088 if ( shine != 0 ) { 089 int f = (int)(255*shine*Math.sin( (double)x/width*Math.PI )); 090 tr += f; 091 tg += f; 092 tb += f; 093 } 094 if (monochrome) { 095 int n = (int)(255 * (2*randomNumbers.nextFloat() - 1) * amount); 096 inPixels[x] = a | (clamp(tr+n) << 16) | (clamp(tg+n) << 8) | clamp(tb+n); 097 } else { 098 inPixels[x] = a | (random(tr) << 16) | (random(tg) << 8) | random(tb); 099 } 100 } 101 102 if ( radius != 0 ) { 103 blur( inPixels, outPixels, width, radius ); 104 setRGB( dst, 0, y, width, 1, outPixels ); 105 } else 106 setRGB( dst, 0, y, width, 1, inPixels ); 107 } 108 return dst; 109 } 110 111 private int random(int x) { 112 x += (int)(255*(2*randomNumbers.nextFloat() - 1) * amount); 113 if (x < 0) 114 x = 0; 115 else if (x > 0xff) 116 x = 0xff; 117 return x; 118 } 119 120 private static int clamp(int c) { 121 if (c < 0) 122 return 0; 123 if (c > 255) 124 return 255; 125 return c; 126 } 127 128 /** 129 * Return a mod b. This differs from the % operator with respect to negative numbers. 130 * @param a the dividend 131 * @param b the divisor 132 * @return a mod b 133 */ 134 private static int mod(int a, int b) { 135 int n = a/b; 136 137 a -= n*b; 138 if (a < 0) 139 return a + b; 140 return a; 141 } 142 143 public void blur( int[] in, int[] out, int width, int radius ) { 144 int widthMinus1 = width-1; 145 int r2 = 2*radius+1; 146 int tr = 0, tg = 0, tb = 0; 147 148 for ( int i = -radius; i <= radius; i++ ) { 149 int rgb = in[mod(i, width)]; 150 tr += (rgb >> 16) & 0xff; 151 tg += (rgb >> 8) & 0xff; 152 tb += rgb & 0xff; 153 } 154 155 for ( int x = 0; x < width; x++ ) { 156 out[x] = 0xff000000 | ((tr/r2) << 16) | ((tg/r2) << 8) | (tb/r2); 157 158 int i1 = x+radius+1; 159 if ( i1 > widthMinus1 ) 160 i1 = mod( i1, width ); 161 int i2 = x-radius; 162 if ( i2 < 0 ) 163 i2 = mod( i2, width ); 164 int rgb1 = in[i1]; 165 int rgb2 = in[i2]; 166 167 tr += ((rgb1 & 0xff0000)-(rgb2 & 0xff0000)) >> 16; 168 tg += ((rgb1 & 0xff00)-(rgb2 & 0xff00)) >> 8; 169 tb += (rgb1 & 0xff)-(rgb2 & 0xff); 170 } 171 } 172 173 /** 174 * Set the horizontal size of the blur. 175 * @param radius the radius of the blur in the horizontal direction 176 * @min-value 0 177 * @max-value 100+ 178 * @see #getRadius 179 */ 180 public void setRadius(int radius) { 181 this.radius = radius; 182 } 183 184 /** 185 * Get the horizontal size of the blur. 186 * @return the radius of the blur in the horizontal direction 187 * @see #setRadius 188 */ 189 public int getRadius() { 190 return radius; 191 } 192 193 /** 194 * Set the amount of noise to add in the range 0..1. 195 * @param amount the amount of noise 196 * @min-value 0 197 * @max-value 1 198 * @see #getAmount 199 */ 200 public void setAmount(float amount) { 201 this.amount = amount; 202 } 203 204 /** 205 * Get the amount of noise to add. 206 * @return the amount of noise 207 * @see #setAmount 208 */ 209 public float getAmount() { 210 return amount; 211 } 212 213 /** 214 * Set the amount of shine to add to the range 0..1. 215 * @param shine the amount of shine 216 * @min-value 0 217 * @max-value 1 218 * @see #getShine 219 */ 220 public void setShine( float shine ) { 221 this.shine = shine; 222 } 223 224 /** 225 * Get the amount of shine to add in the range 0..1. 226 * @return the amount of shine 227 * @see #setShine 228 */ 229 public float getShine() { 230 return shine; 231 } 232 233 /** 234 * Set the color of the metal. 235 * @param color the color in ARGB form 236 * @see #getColor 237 */ 238 public void setColor(int color) { 239 this.color = color; 240 } 241 242 /** 243 * Get the color of the metal. 244 * @return the color in ARGB form 245 * @see #setColor 246 */ 247 public int getColor() { 248 return color; 249 } 250 251 /** 252 * Set the type of noise to add. 253 * @param monochrome true for monochrome noise 254 * @see #getMonochrome 255 */ 256 public void setMonochrome(boolean monochrome) { 257 this.monochrome = monochrome; 258 } 259 260 /** 261 * Get the type of noise to add. 262 * @return true for monochrome noise 263 * @see #setMonochrome 264 */ 265 public boolean getMonochrome() { 266 return monochrome; 267 } 268 269 public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel dstCM) { 270 if ( dstCM == null ) 271 dstCM = src.getColorModel(); 272 return new BufferedImage(dstCM, dstCM.createCompatibleWritableRaster(src.getWidth(), src.getHeight()), dstCM.isAlphaPremultiplied(), null); 273 } 274 275 public Rectangle2D getBounds2D( BufferedImage src ) { 276 return new Rectangle(0, 0, src.getWidth(), src.getHeight()); 277 } 278 279 public Point2D getPoint2D( Point2D srcPt, Point2D dstPt ) { 280 if ( dstPt == null ) 281 dstPt = new Point2D.Double(); 282 dstPt.setLocation( srcPt.getX(), srcPt.getY() ); 283 return dstPt; 284 } 285 286 public RenderingHints getRenderingHints() { 287 return null; 288 } 289 290 /** 291 * A convenience method for setting ARGB pixels in an image. This tries to avoid the performance 292 * penalty of BufferedImage.setRGB unmanaging the image. 293 */ 294 private void setRGB( BufferedImage image, int x, int y, int width, int height, int[] pixels ) { 295 int type = image.getType(); 296 if ( type == BufferedImage.TYPE_INT_ARGB || type == BufferedImage.TYPE_INT_RGB ) 297 image.getRaster().setDataElements( x, y, width, height, pixels ); 298 else 299 image.setRGB( x, y, width, height, pixels, 0, width ); 300 } 301 302 public String toString() { 303 return "Texture/Brushed Metal..."; 304 } 305 public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src); 306 Object o; 307 if((o=parameters.removeEL(KeyImpl.init("Radius")))!=null)setRadius(ImageFilterUtil.toIntValue(o,"Radius")); 308 if((o=parameters.removeEL(KeyImpl.init("Amount")))!=null)setAmount(ImageFilterUtil.toFloatValue(o,"Amount")); 309 if((o=parameters.removeEL(KeyImpl.init("Shine")))!=null)setShine(ImageFilterUtil.toFloatValue(o,"Shine")); 310 if((o=parameters.removeEL(KeyImpl.init("Monochrome")))!=null)setMonochrome(ImageFilterUtil.toBooleanValue(o,"Monochrome")); 311 if((o=parameters.removeEL(KeyImpl.init("Color")))!=null)setColor(ImageFilterUtil.toIntValue(o,"Color")); 312 313 // check for arguments not supported 314 if(parameters.size()>0) { 315 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, Amount, Shine, Monochrome, Color]"); 316 } 317 318 return filter(src, dst); 319 } 320 }