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}