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 }