/** * Copyright (C) 2007-2008 Nicholas Berardi, Managed Fusion, LLC (nick@managedfusion.com) * * Nicholas Berardi * nick@managedfusion.com * Managed Fusion, LLC * Url Rewriter and Reverse Proxy * Microsoft Public License (Ms-PL) * * This software, as defined above in , is copyrighted by the and the * and is licensed for use under , all defined above. * * This copyright notice may not be removed and if this or any parts of it are used any other * packaged software, attribution needs to be given to the author, . This can be in the form of a textual * message at program startup or in documentation (online or textual) provided with the packaged software. * * http://www.managedfusion.com/products/url-rewriter/ * http://www.managedfusion.com/products/url-rewriter/license.aspx */ using System; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Web; using System.Text; namespace ManagedFusion.Web.Controls { /// /// Amount of random font warping to apply to rendered text /// public enum FontWarpFactor { /// /// /// None, /// /// /// Low, /// /// /// Medium, /// /// /// High, /// /// /// Extreme } /// /// Amount of background noise to add to rendered image /// public enum BackgroundNoiseLevel { /// /// /// None, /// /// /// Low, /// /// /// Medium, /// /// /// High, /// /// /// Extreme } /// /// Amount of curved line noise to add to rendered image /// public enum LineNoiseLevel { /// /// /// None, /// /// /// Low, /// /// /// Medium, /// /// /// High, /// /// /// Extreme } /// /// CAPTCHA Image /// /// Original By Jeff Atwood public class CaptchaImage { #region Static /// /// Gets the cached captcha. /// /// The GUID. /// public static CaptchaImage GetCachedCaptcha(string guid) { if (String.IsNullOrEmpty(guid)) return null; return (CaptchaImage)HttpRuntime.Cache.Get(guid); } /// /// /// private static readonly string[] RandomFontFamily = { "arial", "arial black", "comic sans ms", "courier new", "estrangelo edessa", "franklin gothic medium", "georgia", "lucida console", "lucida sans unicode", "mangal", "microsoft sans serif", "palatino linotype", "sylfaen", "tahoma", "times new roman", "trebuchet ms", "verdana" }; /// /// /// private static readonly Color[] RandomColor = { Color.Red, Color.Green, Color.Blue, Color.Black, Color.Purple, Color.Orange }; /// /// Gets or sets a string of available text characters for the generator to use. /// /// The text chars. public static string TextChars { get; set; } /// /// Gets or sets the length of the text. /// /// The length of the text. public static int TextLength { get; set; } /// /// Gets and sets amount of random warping to apply to the instance. /// /// The font warp. public static FontWarpFactor FontWarp { get; set; } /// /// Gets and sets amount of background noise to apply to the instance. /// /// The background noise. public static BackgroundNoiseLevel BackgroundNoise { get; set; } /// /// Gets or sets amount of line noise to apply to the instance. /// /// The line noise. public static LineNoiseLevel LineNoise { get; set; } /// /// Gets or sets the cache time out. /// /// The cache time out. public static double CacheTimeOut { get; set; } #endregion private int _height; private int _width; private Random _rand; #region Public Properties /// /// Returns a GUID that uniquely identifies this Captcha /// /// The unique id. public string UniqueId { get; private set; } /// /// Returns the date and time this image was last rendered /// /// The rendered at. public DateTime RenderedAt { get; private set; } /// /// Gets the randomly generated Captcha text. /// /// The text. public string Text { get; set; } /// /// Width of Captcha image to generate, in pixels /// /// The width. public int Width { get { return _width; } set { if ((value <= 60)) throw new ArgumentOutOfRangeException("width", value, "width must be greater than 60."); _width = value; } } /// /// Height of Captcha image to generate, in pixels /// /// The height. public int Height { get { return _height; } set { if (value <= 30) throw new ArgumentOutOfRangeException("height", value, "height must be greater than 30."); _height = value; } } #endregion /// /// Initializes the class. /// static CaptchaImage() { FontWarp = FontWarpFactor.Medium; BackgroundNoise = BackgroundNoiseLevel.Low; LineNoise = LineNoiseLevel.Low; TextLength = 5; TextChars = "ACDEFGHJKLNPQRTUVXYZ2346789"; CacheTimeOut = 90D; } /// /// Initializes a new instance of the class. /// public CaptchaImage() { _rand = new Random(); Width = 180; Height = 50; Text = GenerateRandomText(); RenderedAt = DateTime.Now; UniqueId = Guid.NewGuid().ToString("N"); } /// /// Forces a new Captcha image to be generated using current property value settings. /// /// public Bitmap RenderImage() { return GenerateImagePrivate(); } /// /// Returns a random font family from the font whitelist /// /// private string GetRandomFontFamily() { return RandomFontFamily[_rand.Next(0, RandomFontFamily.Length)]; } /// /// generate random text for the CAPTCHA /// /// private string GenerateRandomText() { StringBuilder sb = new StringBuilder(TextLength); int maxLength = TextChars.Length; for (int n = 0; n <= TextLength - 1; n++) sb.Append(TextChars.Substring(_rand.Next(maxLength), 1)); return sb.ToString(); } /// /// Returns a random point within the specified x and y ranges /// /// The xmin. /// The xmax. /// The ymin. /// The ymax. /// private PointF RandomPoint(int xmin, int xmax, int ymin, int ymax) { return new PointF(_rand.Next(xmin, xmax), _rand.Next(ymin, ymax)); } /// /// Randoms the color. /// /// private Color GetRandomColor() { return RandomColor[_rand.Next(0, RandomColor.Length)]; } /// /// Returns a random point within the specified rectangle /// /// The rect. /// private PointF RandomPoint(Rectangle rect) { return RandomPoint(rect.Left, rect.Width, rect.Top, rect.Bottom); } /// /// Returns a GraphicsPath containing the specified string and font /// /// The s. /// The f. /// The r. /// private GraphicsPath TextPath(string s, Font f, Rectangle r) { StringFormat sf = new StringFormat(); sf.Alignment = StringAlignment.Near; sf.LineAlignment = StringAlignment.Near; GraphicsPath gp = new GraphicsPath(); gp.AddString(s, f.FontFamily, (int)f.Style, f.Size, r, sf); return gp; } /// /// Returns the CAPTCHA font in an appropriate size /// /// private Font GetFont() { float fsize; string fname = GetRandomFontFamily(); switch (FontWarp) { case FontWarpFactor.None: goto default; case FontWarpFactor.Low: fsize = Convert.ToInt32(_height * 0.8); break; case FontWarpFactor.Medium: fsize = Convert.ToInt32(_height * 0.85); break; case FontWarpFactor.High: fsize = Convert.ToInt32(_height * 0.9); break; case FontWarpFactor.Extreme: fsize = Convert.ToInt32(_height * 0.95); break; default: fsize = Convert.ToInt32(_height * 0.7); break; } return new Font(fname, fsize, FontStyle.Bold); } /// /// Renders the CAPTCHA image /// /// private Bitmap GenerateImagePrivate() { Bitmap bmp = new Bitmap(_width, _height, PixelFormat.Format24bppRgb); using (Graphics gr = Graphics.FromImage(bmp)) { gr.SmoothingMode = SmoothingMode.AntiAlias; gr.Clear(Color.White); int charOffset = 0; double charWidth = _width / TextLength; Rectangle rectChar; foreach (char c in Text) { // establish font and draw area using (Font fnt = GetFont()) { using (Brush fontBrush = new SolidBrush(GetRandomColor())) { rectChar = new Rectangle(Convert.ToInt32(charOffset * charWidth), 0, Convert.ToInt32(charWidth), _height); // warp the character GraphicsPath gp = TextPath(c.ToString(), fnt, rectChar); WarpText(gp, rectChar); // draw the character gr.FillPath(fontBrush, gp); charOffset += 1; } } } Rectangle rect = new Rectangle(new Point(0, 0), bmp.Size); AddNoise(gr, rect); AddLine(gr, rect); } return bmp; } /// /// Warp the provided text GraphicsPath by a variable amount /// /// The text path. /// The rect. private void WarpText(GraphicsPath textPath, Rectangle rect) { float WarpDivisor; float RangeModifier; switch (FontWarp) { case FontWarpFactor.None: goto default; case FontWarpFactor.Low: WarpDivisor = 6F; RangeModifier = 1F; break; case FontWarpFactor.Medium: WarpDivisor = 5F; RangeModifier = 1.3F; break; case FontWarpFactor.High: WarpDivisor = 4.5F; RangeModifier = 1.4F; break; case FontWarpFactor.Extreme: WarpDivisor = 4F; RangeModifier = 1.5F; break; default: return; } RectangleF rectF; rectF = new RectangleF(Convert.ToSingle(rect.Left), 0, Convert.ToSingle(rect.Width), rect.Height); int hrange = Convert.ToInt32(rect.Height / WarpDivisor); int wrange = Convert.ToInt32(rect.Width / WarpDivisor); int left = rect.Left - Convert.ToInt32(wrange * RangeModifier); int top = rect.Top - Convert.ToInt32(hrange * RangeModifier); int width = rect.Left + rect.Width + Convert.ToInt32(wrange * RangeModifier); int height = rect.Top + rect.Height + Convert.ToInt32(hrange * RangeModifier); if (left < 0) left = 0; if (top < 0) top = 0; if (width > this.Width) width = this.Width; if (height > this.Height) height = this.Height; PointF leftTop = RandomPoint(left, left + wrange, top, top + hrange); PointF rightTop = RandomPoint(width - wrange, width, top, top + hrange); PointF leftBottom = RandomPoint(left, left + wrange, height - hrange, height); PointF rightBottom = RandomPoint(width - wrange, width, height - hrange, height); PointF[] points = new PointF[] { leftTop, rightTop, leftBottom, rightBottom }; Matrix m = new Matrix(); m.Translate(0, 0); textPath.Warp(points, rectF, m, WarpMode.Perspective, 0); } /// /// Add a variable level of graphic noise to the image /// /// The graphics1. /// The rect. private void AddNoise(Graphics g, Rectangle rect) { int density; int size; switch (BackgroundNoise) { case BackgroundNoiseLevel.None: goto default; case BackgroundNoiseLevel.Low: density = 30; size = 40; break; case BackgroundNoiseLevel.Medium: density = 18; size = 40; break; case BackgroundNoiseLevel.High: density = 16; size = 39; break; case BackgroundNoiseLevel.Extreme: density = 12; size = 38; break; default: return; } SolidBrush br = new SolidBrush(GetRandomColor()); int max = Convert.ToInt32(Math.Max(rect.Width, rect.Height) / size); for (int i = 0; i <= Convert.ToInt32((rect.Width * rect.Height) / density); i++) g.FillEllipse(br, _rand.Next(rect.Width), _rand.Next(rect.Height), _rand.Next(max), _rand.Next(max)); br.Dispose(); } /// /// Add variable level of curved lines to the image /// /// The graphics1. /// The rect. private void AddLine(Graphics g, Rectangle rect) { int length; float width; int linecount; switch (LineNoise) { case LineNoiseLevel.None: goto default; case LineNoiseLevel.Low: length = 4; width = Convert.ToSingle(_height / 31.25); linecount = 1; break; case LineNoiseLevel.Medium: length = 5; width = Convert.ToSingle(_height / 27.7777); linecount = 1; break; case LineNoiseLevel.High: length = 3; width = Convert.ToSingle(_height / 25); linecount = 2; break; case LineNoiseLevel.Extreme: length = 3; width = Convert.ToSingle(_height / 22.7272); linecount = 3; break; default: return; } PointF[] pf = new PointF[length + 1]; using (Pen p = new Pen(GetRandomColor(), width)) { for (int l = 1; l <= linecount; l++) { for (int i = 0; i <= length; i++) pf[i] = RandomPoint(rect); g.DrawCurve(p, pf, 1.75F); } } } } }