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