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