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