001    package railo.commons.img;
002    
003    import java.awt.BasicStroke;
004    import java.awt.Color;
005    import java.awt.Dimension;
006    import java.awt.Font;
007    import java.awt.GradientPaint;
008    import java.awt.Graphics2D;
009    import java.awt.Point;
010    import java.awt.Rectangle;
011    import java.awt.RenderingHints;
012    import java.awt.geom.AffineTransform;
013    import java.awt.image.BufferedImage;
014    import java.util.ArrayList;
015    import java.util.List;
016    
017    /**
018     * Abstract template class for captcha generation
019     */
020    public abstract class AbstractCaptcha {
021    
022            public static final int DIFFICULTY_LOW=0;
023            public static final int DIFFICULTY_MEDIUM=1;
024            public static final int DIFFICULTY_HIGH=2;
025    
026            /**
027             * generates a Captcha as a Buffered Image file
028             * @param text text for the captcha 
029             * @param width width of the resulting image
030             * @param height height of the resulting image
031             * @param fonts list of font used for the captcha (all font are random used)
032             * @param useAntiAlias use anti aliasing or not
033             * @param fontColor color of the font
034             * @param fontSize size of the font
035             * @param difficulty difficulty of the reslting captcha
036             * @return captcha image 
037             * @throws CaptchaException
038             */
039            public BufferedImage generate(String text,int width, int height, String[] fonts, boolean useAntiAlias, Color fontColor,int fontSize, int difficulty) throws CaptchaException {
040                    if(difficulty==DIFFICULTY_LOW) {
041                            return generate(text, width, height, fonts, useAntiAlias, fontColor,fontSize, 0, 0, 0, 0, 0, 0,230,25);
042                    }
043                    if(difficulty==DIFFICULTY_MEDIUM) {
044                            return generate(text, width, height, fonts, useAntiAlias, fontColor,fontSize, 0, 0, 5, 30, 0, 0,200,35);
045                    }
046                    return generate(text, width, height, fonts, useAntiAlias, fontColor,fontSize, 4, 10, 30, 60, 4, 10,170,45);
047            }
048            
049            private BufferedImage generate(String text,int width, int height, String[] fonts, boolean useAntiAlias
050                            , Color fontColor,int fontSize
051                            , int minOvals, int maxOvals, int minBGLines, int maxBGLines, int minFGLines, int maxFGLines, int startColor, int shear) throws CaptchaException {
052    
053                    if(text==null || text.trim().length()==0)
054                            throw new CaptchaException("missing Text");
055                    
056                    char[] characters=text.toCharArray();
057                    int top=height/3;
058                    
059                    Dimension dimension = new Dimension(width, height);
060                    int imageType = BufferedImage.TYPE_INT_RGB;
061                    BufferedImage bufferedImage = new BufferedImage((int)dimension.getWidth(), (int)dimension.getHeight(), imageType);
062                    Graphics2D graphics = bufferedImage.createGraphics();
063                    
064                    // Set anti-alias setting
065                    if(useAntiAlias)
066                            graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
067                    
068                    drawBackground(graphics, dimension,startColor);
069                    
070                    // draw ovals
071                    if(maxOvals>0 && maxOvals>minOvals) {
072                            int to=rnd(minOvals,maxOvals);
073                            for(int i=1;i<=to;i++) {
074                                    drawRandomOval(graphics, dimension, getRandomColor(startColor));
075                            }
076                    }
077                    
078                    // Draw background lines 
079                    if(maxBGLines>0 && maxBGLines>minBGLines) {
080                            int to=rnd(minBGLines,maxBGLines);
081                            for(int i=1;i<=to;i++) {
082                                    drawRandomLine(graphics, dimension, getRandomColor(startColor));
083                            }
084                    }
085                    
086                    if(fonts==null || fonts.length==0) 
087                            throw new CaptchaException("no font's defined");
088                    
089                    // font
090                    Font f;
091                    ArrayList fontList=new ArrayList();
092                    for(int i=0;i<fonts.length;i++){
093                            f=getFont(fonts[i],null);
094                            if(f!=null) fontList.add(f);
095                            
096                    }
097                    if(fonts.length==0) 
098                            throw new CaptchaException("defined fonts are not available on this system");
099                    
100    
101                    int charWidth=0;
102                    int charHeight=0,tmp;
103                    int space=0;
104                    Font[] _fonts=new Font[characters.length];
105                    for(int i=0;i<characters.length;i++) {
106                            char c= characters[i];
107                            _fonts[i]=createFont(fontList,fontSize,shear,i);
108                            graphics.setFont(_fonts[i]);
109                            charWidth+=graphics.getFontMetrics().charWidth(c);
110                            tmp=graphics.getFontMetrics().getHeight();
111                            if(tmp>charHeight)charHeight=tmp;
112                    }
113                    if(charWidth<width) {
114                            space=(width-charWidth)/(characters.length+1);
115                    }
116                    else if (charWidth>width)throw new CaptchaException("the specified width for the CAPTCHA image is not big enough to fit the text. Minimum width is ["+charWidth+"]");
117                    if (charHeight>height)throw new CaptchaException("the specified height for the CAPTCHA image is not big enough to fit the text. Minimum height is ["+charHeight+"]");
118                    int left= space;
119                    
120                    // Draw captcha text
121                    for(int i=0;i<characters.length;i++) {
122                            char c= characters[i];
123                            // <cfset staticCollections.shuffle(definedFonts) />
124                            
125                            graphics.setFont(_fonts[i]);
126                            graphics.setColor(fontColor);
127                            // Check if font can display current character --->
128                                    /*<cfloop condition="NOT graphics.getFont().canDisplay(char)">
129                                    <cfset setFont(graphics, definedFonts) />                 
130                                    </cfloop>*/
131                                            
132                            // Compute the top character position --->
133                            top = rnd(graphics.getFontMetrics().getAscent(), height - (height - graphics.getFontMetrics().getHeight()) / 2);
134                                            
135                            // Draw character text
136                            graphics.drawString(String.valueOf(c),left,top);
137                                            
138                            // Compute the next character lef tposition --->
139                            //((rnd(150, 200) / 100) * 
140                            left +=  graphics.getFontMetrics().charWidth(c)+rnd(space, space);
141                    }
142                    
143    
144                    // Draw forground lines 
145                    if(maxFGLines>0 && maxFGLines>minFGLines) {
146                            int to=rnd(minFGLines,maxFGLines);
147                            for(int i=1;i<=to;i++) {
148                                    drawRandomLine(graphics, dimension, getRandomColor(startColor));
149                            }
150                    }
151                    
152                    return bufferedImage;
153            }
154    
155    
156            /**
157             * creates a font from given string
158             * @param font
159             * @param defaultValue
160             * @return
161             */
162            public abstract Font getFont(String font, Font defaultValue);
163    
164    
165            private void drawBackground(Graphics2D graphics, Dimension dimension,int _startColor) {
166                    Color startColor = getRandomColor(_startColor);
167                    Color endColor = getRandomColor(_startColor);
168                    GradientPaint gradientPaint = new GradientPaint(getRandomPointOnBorder(dimension), 
169                                                                                            startColor,
170                                                                                            getRandomPointOnBorder(dimension),  
171                                                                                            endColor.brighter(), 
172                                                                                            true);
173                    graphics.setPaint(gradientPaint);
174                    // arguments.graphics.setColor(startColor) />
175                            
176                    graphics.fill(new Rectangle(dimension));
177            }
178    
179            private Font createFont(List fonts, int fontSize, int shear,int index) {
180                    AffineTransform trans1 = getRandomTransformation(shear, shear);
181                    AffineTransform trans2 = getRandomTransformation(shear, shear);
182                    Font font = (Font) fonts.get(index%fonts.size());
183                    font = font.deriveFont((float)fontSize).deriveFont(trans1).deriveFont(trans2);
184                    return font;
185            }
186    
187            private Color getRandomColor(int startColor) {
188                    return new Color(r(startColor),r(startColor),r(startColor));
189            }
190    
191            private int r(int startColor) {
192                    return rnd(startColor-100,startColor);
193                    //int r= ((int)(Math.random()*(255-startColor)))+startColor;
194                    //return r;
195            }
196            
197            
198            private Point getRandomPointOnBorder(Dimension dimension) {
199                    int height = (int) dimension.getHeight();
200                    int width = (int) dimension.getWidth();
201                    
202                    switch(rnd(1, 4)) {
203                    case 1: //  left side
204                            return new Point(0, rnd(0, height));
205                    case 2: // right side
206                            return new Point(width, rnd(0, height));
207                    case 3: // top side
208                            return new Point(rnd(0, width), 0);
209                    case 4:
210                    default: // bottom side
211                            return new Point(rnd(0, width), height);
212                    }
213            }
214    
215    
216            private AffineTransform getRandomTransformation(int shearXRange, int shearYRange) {
217                    // create a slightly random affine transform 
218                    double shearX = rndd(-1 * (shearXRange * (rnd(50, 150) / 100d)), (shearXRange* (rndd(50, 150) / 100d))) / 100d ;
219                    double shearY = rndd(-1 * (shearYRange * (rnd(50, 150) / 100d)), (shearYRange * (rndd(50, 150) / 100d))) / 100d ;
220                    
221                    AffineTransform transformation = new AffineTransform();
222                    transformation.shear(shearX, shearY);
223                    return  transformation;
224            }
225    
226            private BasicStroke getRandomStroke() {
227                    return new BasicStroke(rnd(1, 3));
228            }
229    
230    
231            private Point getRandomPoint(Dimension dimension) {
232                    int height = (int) dimension.getHeight();
233                    int width = (int) dimension.getWidth();
234                    return new Point(rnd(0, width), rnd(0, height));
235            }
236            
237            protected static int rnd(double min, double max) {
238                    return (int) rndd(min, max);
239            }
240            
241            private static double rndd(double min, double max) {
242                    if(min>max) {
243                            double tmp=min;
244                            min=max;
245                            max=tmp;
246                    }
247                    double diff=max-min;
248                    return ((int)(StrictMath.random()*(diff+1)))+min;
249            }
250    
251            private void drawRandomLine(Graphics2D graphics, Dimension dimension, Color lineColorType) {
252                    Point point1 = getRandomPointOnBorder(dimension);
253                    Point point2 = getRandomPointOnBorder(dimension);
254                    
255                    graphics.setStroke(getRandomStroke());
256                    graphics.setColor(lineColorType);
257                    graphics.drawLine((int)point1.getX(), 
258                                    (int)point1.getY(), 
259                                    (int)point2.getX(), 
260                                    (int)point2.getY());
261            }
262    
263            private void drawRandomOval(Graphics2D graphics, Dimension dimension, Color ovalColorType) {
264                    Point point = getRandomPoint(dimension);
265                    double height =  dimension.getHeight() ;
266                    //double width =  dimension.getWidth() ;
267                    double minOval = height * .10;
268                    double maxOval = height * .75;
269                            
270                    graphics.setColor(ovalColorType);
271                    
272                    switch(rnd(1, 3)) {
273                    case 1:
274                            graphics.setStroke(getRandomStroke());
275                            graphics.drawOval(
276                                            (int)point.getX(), 
277                                            (int)point.getY(), 
278                                            rnd(minOval,maxOval), 
279                                            rnd(minOval,maxOval) );
280                    break;
281                    case 2:
282                    case 3:
283                            graphics.fillOval(
284                                            (int)point.getX(), 
285                                            (int)point.getY(), 
286                                            rnd(minOval,maxOval), 
287                                            rnd(minOval,maxOval) );
288                    break;
289                    }
290            }
291    }