// Spludlow Software // Copyright © Samuel P. Ludlow 2020 All Rights Reserved // Distributed under the terms of the GNU General Public License version 3 // Distributed WITHOUT ANY WARRANTY; without implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE // https://www.spludlow.co.uk/LICENCE.TXT // The Spludlow logo is a registered trademark of Samuel P. Ludlow and may not be used without permission // v1.14 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.IO; using System.Web; using System.Drawing; using System.Drawing.Imaging; using System.Security.Cryptography; using System.Drawing.Text; namespace Spludlow { public class WebVerificationImage { private static Color[][] ColourCircles = new Color[2][]; // 0 background, 1 foreground private static Font _Font; private const PixelFormat _PixelFormat = PixelFormat.Format24bppRgb; private const int _Width = 52; private const int _Height = 24; private const double _Saturation = 0.12; private const int _MissChance = 7; private const int _FrameEachCount = 4; [ThreadStatic] private static Random _Random; static WebVerificationImage() { for (int circle = 0; circle < ColourCircles.Length; ++circle) { ColourCircles[circle] = new Color[360]; double sat = 1; if (circle == 1) sat = _Saturation; for (int index = 0; index < 360; ++index) ColourCircles[circle][index] = Spludlow.Drawing.Colour.FromHSV(index, sat, 1); } _Font = new Font(FontFamily.GenericMonospace, 18, FontStyle.Bold); } public static void PageLoad(HttpRequest request, HttpResponse response) { // TODO : Add IP blocking string code = request.QueryString["CODE"]; if (code == null) code = ""; try { if (code.Length > 0) { try { code = Unprotect(code); } catch (Exception ee) { code = ""; Spludlow.Log.Error("WebVerificationImage Unprotect", ee, code); } } response.Clear(); response.ContentType = "image/gif"; WriteGif(code, response.OutputStream); } catch (Exception ee) { Spludlow.Log.Error("VerificationImage", ee); throw ee; } response.End(); } public static void WriteGif(string code, Stream outputStream) { if (_Random == null) _Random = new Random(); int frameCount; string[] codes = null; List randomCodeIndexes = null; if (code.Length > 0) { frameCount = code.Length * _FrameEachCount; // Make text of each code with 1 character missing codes = new string[code.Length]; for (int index = 0; index < codes.Length; ++index) { StringBuilder text = new StringBuilder(code); text[index] = ' '; codes[index] = text.ToString(); } // Make code indexes for each frame List codeIndexes = new List(); for (int index = 0; index < frameCount; ++index) codeIndexes.Add(index % codes.Length); // Ranomize the code indexes randomCodeIndexes = new List(); while (codeIndexes.Count > 0) { int index = _Random.Next(codeIndexes.Count); randomCodeIndexes.Add(codeIndexes[index]); codeIndexes.RemoveAt(index); } } else { frameCount = _FrameEachCount; } MemoryStream[] memoryStreams = new MemoryStream[frameCount]; try { for (int frame = 0; frame < frameCount; ++frame) { memoryStreams[frame] = new MemoryStream(); string drawCode = ""; if (codes != null) drawCode = codes[randomCodeIndexes[frame]]; using (Bitmap bitmap = Spludlow.WebVerificationImage.Draw(drawCode)) { bitmap.Save(memoryStreams[frame], ImageFormat.Gif); } memoryStreams[frame].Position = 0; } Spludlow.Drawing.GIF.MakeGif(memoryStreams, outputStream); } finally { foreach (MemoryStream memoryStream in memoryStreams) { if (memoryStream != null) memoryStream.Dispose(); } } } public static Bitmap Draw(string code) { if (_Random == null) _Random = new Random(); Bitmap bitmap = new Bitmap(_Width, _Height, _PixelFormat); if (code.Length > 0) { using (Graphics graphics = Graphics.FromImage(bitmap)) { graphics.TextRenderingHint = TextRenderingHint.SingleBitPerPixel; graphics.DrawString(code, _Font, Brushes.White, 0, 0); } } byte[] data = Spludlow.Drawing.Bitmaps.ReadBitmapData(bitmap, _PixelFormat); int stride = data.Length / _Height; for (int y = 0; y < _Height; ++y) { int yOff = y * stride; for (int x = 0; x < _Width; ++x) { int index = yOff + x * 3; Color colour = ColourCircles[code.Length > 0 && data[index] == 0xFF && _Random.Next(_MissChance) > 0 ? 1 : 0][_Random.Next(360)]; data[index + 0] = colour.B; data[index + 1] = colour.G; data[index + 2] = colour.R; } } Spludlow.Drawing.Bitmaps.WriteBitmapData(bitmap, data, _PixelFormat); return bitmap; } public static string GetCode() { if (_Random == null) _Random = new Random(); StringBuilder text = new StringBuilder(); for (int count = 0; count < 3; ++count) text.Append(_Random.Next(10)); return text.ToString(); } public static string Protect(string text) { byte[] data = ProtectedData.Protect(Encoding.ASCII.GetBytes(text), null, DataProtectionScope.LocalMachine); return Convert.ToBase64String(data); } public static string Unprotect(string text) { byte[] data = ProtectedData.Unprotect(Convert.FromBase64String(text), null, DataProtectionScope.LocalMachine); return Encoding.ASCII.GetString(data); } } }