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}