# Window.cs ``` cs /// Author : Maxime Rohmer /// Date : 08/05/2023 /// File : Window.cs /// Brief : Default Window object that is mainly expected to be inherited. /// Version : 0.1 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Drawing; using System.IO; using Tesseract; using System.Text.RegularExpressions; using System.Drawing.Drawing2D; namespace Test_Merge { public class Window { private Rectangle _bounds; private Bitmap _image; private string _name; protected TesseractEngine Engine; public Rectangle Bounds { get => _bounds; private set => _bounds = value; } public Bitmap Image { get => _image; set => _image = value; } public string Name { get => _name; protected set => _name = value; } //This will have to be changed if you want to make it run on your machine public static DirectoryInfo TESS_DATA_FOLDER = new DirectoryInfo(@"C:\Users\Moi\Pictures\SeleniumScreens\TessData"); public Bitmap WindowImage { get { //This little trickery lets you have the image that the window sees Bitmap sample = new Bitmap(Bounds.Width, Bounds.Height); Graphics g = Graphics.FromImage(sample); g.DrawImage(Image, new Rectangle(0, 0, sample.Width, sample.Height), Bounds, GraphicsUnit.Pixel); return sample; } } public Window(Bitmap image, Rectangle bounds, bool generateEngine = true) { Image = image; Bounds = bounds; if (generateEngine) { Engine = new TesseractEngine(TESS_DATA_FOLDER.FullName, "eng", EngineMode.Default); Engine.DefaultPageSegMode = PageSegMode.SingleLine; } } /// /// Method that will have to be used by the childrens to let the model make them decode the images they have /// /// Returns an object because we dont know what kind of return it will be public virtual async Task DecodePng() { return "NaN"; } /// /// Method that will have to be used by the childrens to let the model make them decode the images they have /// /// This is a list of the different possible drivers in the race. It should not be too big but NEVER be too short /// Returns an object because we dont know what kind of return it will be public virtual async Task DecodePng(List driverList) { return "NaN"; } /// /// This converts an image into a byte[]. It can be usefull when doing unsafe stuff. Use at your own risks /// /// The image you want to convert /// A byte array containing the image informations public static byte[] ImageToByte(Image inputImage) { using (var stream = new MemoryStream()) { inputImage.Save(stream, System.Drawing.Imaging.ImageFormat.Png); return stream.ToArray(); } } /// /// This method is used to recover a time from a PNG using Tesseract OCR /// /// The image where the text is /// The type of window it is /// The Tesseract Engine /// The time in milliseconds public static async Task GetTimeFromPng(Bitmap windowImage, OcrImage.WindowType windowType, TesseractEngine Engine) { //Kind of a big method but it has a lot of error handling and has to work with three special cases string rawResult = ""; int result = 0; switch (windowType) { case OcrImage.WindowType.Sector: //The usual sector is in this form : 33.456 Engine.SetVariable("tessedit_char_whitelist", "0123456789."); break; case OcrImage.WindowType.LapTime: //The usual Lap time is in this form : 1:45:345 Engine.SetVariable("tessedit_char_whitelist", "0123456789.:"); break; case OcrImage.WindowType.Gap: //The usual Gap is in this form : + 34.567 Engine.SetVariable("tessedit_char_whitelist", "0123456789.+"); break; default: Engine.SetVariable("tessedit_char_whitelist", ""); break; } Bitmap enhancedImage = new OcrImage(windowImage).Enhance(windowType); var tessImage = Pix.LoadFromMemory(ImageToByte(enhancedImage)); Page page = Engine.Process(tessImage); Graphics g = Graphics.FromImage(enhancedImage); // Get the iterator for the page layout using (var iter = page.GetIterator()) { // Loop over the elements of the page layout iter.Begin(); do { // Get the text for the current element try { rawResult += iter.GetText(PageIteratorLevel.Word); } catch { //nothing we just dont add it if its not a number } } while (iter.Next(PageIteratorLevel.Word)); } List rawNumbers; //In the gaps we can find '+' but we dont care about it its redondant a driver will never be - something if (windowType == OcrImage.WindowType.Gap) rawResult = Regex.Replace(rawResult, "[^0-9.:]", ""); //Splits into minuts seconds miliseconds rawNumbers = rawResult.Split('.', ':').ToList(); //removes any empty cells (tho this usually sign of a really bad OCR implementation tbh will have to be fixed higher in the chian) rawNumbers.RemoveAll(x => ((string)x) == ""); if (rawNumbers.Count == 3) { //mm:ss:ms result = (Convert.ToInt32(rawNumbers[0]) * 1000 * 60) + (Convert.ToInt32(rawNumbers[1]) * 1000) + Convert.ToInt32(rawNumbers[2]); } else { if (rawNumbers.Count == 2) { //ss:ms result = (Convert.ToInt32(rawNumbers[0]) * 1000) + Convert.ToInt32(rawNumbers[1]); if (result > 999999) { //We know that we have way too much seconds to make a minut //Its usually because the ":" have been interpreted as a number int minuts = (int)(rawNumbers[0][0] - '0'); // rawNumbers[0][1] should contain the : that has been mistaken int seconds = Convert.ToInt32(rawNumbers[0][2].ToString() + rawNumbers[0][3].ToString()); int ms = Convert.ToInt32(rawNumbers[1]); result = (Convert.ToInt32(minuts) * 1000 * 60) + (Convert.ToInt32(seconds) * 1000) + Convert.ToInt32(ms); } } else { if (rawNumbers.Count == 1) { try { result = Convert.ToInt32(rawNumbers[0]); } catch { //It can be because the input is empty or because its the LEADER bracket result = 0; } } else { //Auuuugh result = 0; } } } page.Dispose(); return result; } /// /// Method that recovers strings from an image using Tesseract OCR /// /// The image of the window that contains text /// The Tesseract engine /// The list of allowed chars /// The type of window the text is on. Depending on the context the OCR will behave differently /// the string it found public static async Task GetStringFromPng(Bitmap WindowImage, TesseractEngine Engine, string allowedChars = "", OcrImage.WindowType windowType = OcrImage.WindowType.Text) { string result = ""; Engine.SetVariable("tessedit_char_whitelist", allowedChars); Bitmap rawData = WindowImage; Bitmap enhancedImage = new OcrImage(rawData).Enhance(windowType); Page page = Engine.Process(enhancedImage); using (var iter = page.GetIterator()) { iter.Begin(); do { result += iter.GetText(PageIteratorLevel.Word); } while (iter.Next(PageIteratorLevel.Word)); } page.Dispose(); return result; } /// /// Get a smaller image from a bigger one /// /// The big bitmap you want to get a part of /// The dimensions of the new bitmap /// The little bitmap protected Bitmap GetSmallBitmapFromBigOne(Bitmap inputBitmap, Rectangle newBitmapDimensions) { Bitmap sample = new Bitmap(newBitmapDimensions.Width, newBitmapDimensions.Height); Graphics g = Graphics.FromImage(sample); g.DrawImage(inputBitmap, new Rectangle(0, 0, sample.Width, sample.Height), newBitmapDimensions, GraphicsUnit.Pixel); return sample; } /// /// Returns the closest string from a list of options /// /// an array of all the possibilities /// the string you want to compare /// The closest option protected static string FindClosestMatch(List options, string testString) { var closestMatch = ""; var closestDistance = int.MaxValue; foreach (var item in options) { var distance = LevenshteinDistance(item, testString); if (distance < closestDistance) { closestMatch = item; closestDistance = distance; } } return closestMatch; } //This method has been generated with the help of ChatGPT /// /// Method that computes a score of distance between two strings /// /// The first string (order irrelevant) /// The second string (order irrelevant) /// The levenshtein distance protected static int LevenshteinDistance(string string1, string string2) { if (string.IsNullOrEmpty(string1)) { return string.IsNullOrEmpty(string2) ? 0 : string2.Length; } if (string.IsNullOrEmpty(string2)) { return string.IsNullOrEmpty(string1) ? 0 : string1.Length; } var d = new int[string1.Length + 1, string2.Length + 1]; for (var i = 0; i <= string1.Length; i++) { d[i, 0] = i; } for (var j = 0; j <= string2.Length; j++) { d[0, j] = j; } for (var i = 1; i <= string1.Length; i++) { for (var j = 1; j <= string2.Length; j++) { var cost = (string1[i - 1] == string2[j - 1]) ? 0 : 1; d[i, j] = Math.Min(Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), d[i - 1, j - 1] + cost); } } return d[string1.Length, string2.Length]; } public virtual string ToJSON() { string result = ""; result += "\"" + Name + "\"" + ":{" + Environment.NewLine; result += "\t" + "\"x\":" + Bounds.X + "," + Environment.NewLine; result += "\t" + "\"y\":" + Bounds.Y + "," + Environment.NewLine; result += "\t" + "\"width\":" + Bounds.Width + Environment.NewLine; result += "}"; return result; } } } ```