/// <summary> /// Calculates the percentage of a given bitmap's region which is populated by certain colors. /// </summary> /// <param name="lb">The bitmap to analyze</param> /// <param name="boundingRect">The region of the bitmap to analyze</param> /// <param name="colors">The colors to scan for. Every other color is ignored.</param> /// <returns></returns> public static double GetBlobDensity(LockBitmap lb, Rectangle boundingRect, IEnumerable <Color> colors) { int totalMatches = 0; try { for (int x = (boundingRect.Left >= 0 ? boundingRect.Left : 0); x <= boundingRect.Right; x++) { for (int y = boundingRect.Top; y <= boundingRect.Bottom && y < lb.Height; y++) { Color c = lb.GetPixel(x, y); foreach (Color match in colors) { if (c == match) { totalMatches++; break; } } } } return((double)totalMatches / (boundingRect.Height * boundingRect.Width)); } catch (Exception) { return(0.0f); } }
/// <summary> /// See above. /// </summary> /// <param name="b"></param> /// <param name="boundingRectangle"></param> /// <param name="threshold"></param> /// <returns></returns> public static List <Line> OCRBitmap(Bitmap b, Rectangle boundingRectangle, IEnumerable <Color> threshold) { // Perf -- lock crud using (LockBitmap lb = new LockBitmap(b)) { return(OCRBitmap(lb, boundingRectangle, threshold)); } }
public Line(LockBitmap b, Rectangle boundingRectangle) { BoundingRectangle = boundingRectangle; List <Char> characters = new List <Char>(); int rectLeft = -1, rectTop = -1, rectBottom = -1, rectRight = -1; bool inRect = false; Color black = Color.FromArgb(255, 0, 0, 0); for (int x = boundingRectangle.Left; x <= boundingRectangle.Right; x++) { bool blackFound = false; for (int y = boundingRectangle.Top; y <= boundingRectangle.Bottom; y++) { Color c = b.GetPixel(x, y); if (c == black) { if (rectLeft == -1) { rectLeft = x; } if (y < rectTop || rectTop == -1) { rectTop = y; } if (y > rectBottom || rectBottom == -1) { rectBottom = y; } blackFound = true; inRect = true; } } if (!blackFound && inRect) { rectRight = x - 1; characters.Add(new Char(new Rectangle(rectLeft, rectTop, rectRight - rectLeft, rectBottom - rectTop))); rectLeft = rectTop = rectRight = rectBottom = -1; inRect = false; } } if (inRect) { rectRight = boundingRectangle.Right; characters.Add(new Char(new Rectangle(rectLeft, rectTop, rectRight - rectLeft, rectBottom - rectTop))); } Characters = characters; }
/// <summary> /// Performs OCR on this line, recursively calling each child Char's DoOCR function /// </summary> /// <param name="lb">The bitmap containing the line</param> /// <param name="screentotalArea">The total area of the playscreen, used for OCR purposes</param> /// <param name="specialcharsenabled">True if the line can contain "e" and "."</param> /// <param name="lthreshold">The left-threshold. Characters which are contained entirely to the left of this value are skipped</param> /// <param name="lskip">Left skip. Specifies a number of characters which will be skipped, when reading from left to right.</param> public void DoOcr(LockBitmap lb, int screentotalArea, bool specialcharsenabled = true, int lthreshold = -1, int lskip = -1) { StringBuilder sb = new StringBuilder(Characters.Count()); int lskipped = 0; foreach (Char c in Characters) { if (lthreshold != -1 && c.GetBoundingRectangle().Right < lthreshold) { continue; } if (lskip != -1 && lskipped < lskip) { lskipped++; continue; } c.DoOcr(lb, screentotalArea, specialcharsenabled); sb.Append(c.GuessedCharacter); } OcrString = sb.ToString(); }
/// <summary> /// Performs OCR on a bitmap, reading the Chars and Lines present. /// </summary> /// <param name="lb">The bitmap to OCR</param> /// <param name="boundingRectangle">Defines the region of the bitmap to OCR. Defining a larger region than necessary will have perf consequences.</param> /// <param name="threshold">An enumeration of colors which are considered text colors. Every pixel which is not one of these colors will be ignored.</param> /// <returns>A list of lines. Note that character OCR is not performed on the returned lines -- those lines' DoOCR function needs to be called separately (for perf reasons)</returns> public static List <Line> OCRBitmap(LockBitmap lb, Rectangle boundingRectangle, IEnumerable <Color> threshold) { List <Line> lines = new List <Line>(); int rectLeft = -1, rectRight = -1, rectTop = -1, rectBot = -1; bool inLine = false; for (int y = boundingRectangle.Top; y < boundingRectangle.Bottom; y++) { bool foundBlack = false; for (int x = boundingRectangle.Left; x < boundingRectangle.Right; x++) { Color pixel = lb.GetPixel(x, y); if (pixel == Color.FromArgb(255, 0, 0, 0)) { lb.SetPixel(x, y, Color.White); } foreach (Color c in threshold) { if (c == pixel) { lb.SetPixel(x, y, Color.Black); if (rectTop == -1) { rectTop = y; } if (x < rectLeft || rectLeft == -1) { rectLeft = x; } if (x > rectRight || rectRight == -1) { rectRight = x; } foundBlack = true; inLine = true; break; } } } if (inLine && !foundBlack) { rectBot = y - 1; // Minimum area requirements if ((rectBot - rectTop) > 5) { lines.Add(new Line(lb, new Rectangle(rectLeft, rectTop, rectRight - rectLeft, rectBot - rectTop))); } rectLeft = rectTop = rectRight = rectBot = -1; inLine = false; } } return(lines); }
/// <summary> /// Tries to get the current amount of money from the screen. Is slow. /// </summary> /// <returns></returns> public static double GetMoney() { Size s = MoneyArea.Size; double money = -1; using (Bitmap bitmap = GetImage(MoneyArea)) { IEnumerable<Line> lines = OCREngine.OCRBitmap(bitmap, new Rectangle(0, 0, bitmap.Width, bitmap.Height), new Color[] { Color.FromArgb(255, 254, 254, 254), Color.FromArgb(255, 254, 254, 253) }); using (LockBitmap lb = new LockBitmap(bitmap)) { if (lines.Count() != 0) { Rectangle playableArea = GameEngine.GetPlayableArea(); lines.First().DoOcr(lb, playableArea.Height * playableArea.Width); try { money = Convert.ToDouble(lines.First().OcrString); } catch (Exception) { // ignore } } } return money; } }
///// <summary> ///// Tries to get the current amount of money from the screen. Is slow. ///// </summary> ///// <returns></returns> //public static string GetDamagePerSecond() //{ // Size s = DamagePerSecondArea.Size; // string dps = ""; // using (Bitmap bitmap = GetDamagePerSecondBMP()) // { // IEnumerable<Line> lines = OCREngine.OCRBitmap(bitmap, new Rectangle(0, 0, bitmap.Width, bitmap.Height), // new Color[] // { // Color.FromArgb(255, 254, 254, 254), // Color.FromArgb(255, 254, 254, 253) // }); // if (lines.Count() != 0) // { // using (LockBitmap lb = new LockBitmap(bitmap)) // { // Rectangle playableArea = GameEngine.GetPlayableArea(); // lines.First().DoOcr(lb, playableArea.Height * playableArea.Width); // try // { // //dps = Convert.ToDouble(lines.First().OcrString); // dps = lines.First().OcrString; // } // catch (Exception) // { // // ignore // } // } // } // return dps; // } //} //public static Bitmap GetDamagePerSecondBMP() //{ // Bitmap bitmap = GetImage(DamagePerSecondArea); // Bitmap resizedBitmap = new Bitmap(bitmap, new Size((int) (bitmap.Width * 2), (int) (bitmap.Height * 2))); // // return resizedBitmap; // Bitmap newBitmap = new Bitmap(resizedBitmap.Width, resizedBitmap.Height); // var newColor = Color.FromArgb(255, 254, 254, 254); // var white = Color.DeepPink; // for (int i = 0; i < resizedBitmap.Width; i++) // { // for (int j = 0; j < resizedBitmap.Height; j++) // { // var actulaColor = resizedBitmap.GetPixel(i, j); // if (actulaColor.R > 220) // newBitmap.SetPixel(i, j, newColor); // else // newBitmap.SetPixel(i, j, white); // } // } // return newBitmap; //} /// <summary> /// Tries to parse all heroes on screen. Is null if there is crap on the screen preventing the heros from being parsed. Is very slow. /// </summary> /// <returns></returns> public static ParsedHeroes GetHeroes() { Size s = HeroesArea.Size; using (Bitmap bitmap = GetImage(HeroesArea)) { List<Line> lines = OCREngine.OCRBitmap(bitmap, new Rectangle(0, 0, bitmap.Width, bitmap.Height), new Color[] { Color.FromArgb(255, 254, 254, 254), Color.FromArgb(255, 254, 254, 253), Color.FromArgb(255, 102, 51, 204), // purple for gilded heroes }); using (LockBitmap lb = new LockBitmap(bitmap)) { ParsedHeroes ph = GameEngine.ParseHeroes(lines, lb); if (ph == null) { return null; } foreach (HeroStats hs in ph.HeroStats) { hs.CalculateUpgrades(lb); } return ph; } } }
/// <summary> /// Tries to get the current amount of money from the screen. Is slow. /// </summary> /// <returns></returns> public static double GetMoney() { Size s = MoneyArea.Size; double money = -1; Console.WriteLine(s.Width); Console.WriteLine(s.Height); using (Bitmap bitmap = new Bitmap(s.Width, s.Height)) { using (Graphics g = Graphics.FromImage(bitmap)) { g.CopyFromScreen(new Point(MoneyArea.Left, MoneyArea.Top), Point.Empty, s); } IEnumerable<Line> lines = OCREngine.OCRBitmap(bitmap, new Rectangle(0, 0, bitmap.Width, bitmap.Height), new Color[] { Color.FromArgb(255, 254, 254, 254), Color.FromArgb(255, 254, 254, 253) }); using (LockBitmap lb = new LockBitmap(bitmap)) { if (lines.Count() != 0) { Rectangle playableArea = GameEngine.GetPlayableArea(); lines.First().DoOcr(lb, playableArea.Height * playableArea.Width); try { money = Convert.ToDouble(lines.First().OcrString); } catch (Exception) { // ignore } } } return money; } }
/// <summary> /// Populates the HeroStat's UpgradeBitfield. /// </summary> /// <param name="lb"></param> public void CalculateUpgrades(LockBitmap lb) { UpgradeBitfield = 0; for (int i = 0; i < 7; i++) { Rectangle r = GetUpgradeRect(i); if (r.Bottom > lb.Height) { UpgradeBitfield = Int16.MinValue; return; } double d = OCREngine.GetBlobDensity(lb, r, new Color[] { Color.FromArgb(39, 166, 10), // Normal Color.FromArgb(7, 33, 1), // Bugged out }); if (d > 0.003) { UpgradeBitfield |= (Int16)(1 << i); } } }
/// <summary> /// Performs OCR on this char /// </summary> /// <param name="b">The bitmap containing this char</param> /// <param name="screentotalArea">The total area of the entire playscreen, used for OCR purposes</param> /// <param name="specialcharsenabled">True if "e" and "." can appear in the string </param> public void DoOcr(LockBitmap b, int screentotalArea, bool specialcharsenabled = true) { int totalArea = 0, lArea = 0, tArea = 0; int leftArea = 0, rightArea = 0; int groupsDownMiddleVertical = 0; int midwidth = 0; for (int i = BoundingRect.Left; i <= BoundingRect.Right; i++) { for (int j = BoundingRect.Top; j <= BoundingRect.Bottom; j++) { if (b.GetPixel(i, j) == Color.FromArgb(255, 0, 0, 0)) { totalArea++; if (j < (BoundingRect.Bottom - BoundingRect.Top) / 2 + BoundingRect.Top) { tArea++; } else { if (j == (BoundingRect.Bottom - BoundingRect.Top) / 2 + BoundingRect.Top) { midwidth++; } lArea++; } if (i < (BoundingRect.Right - BoundingRect.Left) / 2 + BoundingRect.Left) { leftArea++; } else { rightArea++; } } } } bool isBlack = false; bool storedWhiteSubgroup = false; for (int j = BoundingRect.Top; j <= BoundingRect.Bottom; j++) { if (b.GetPixel((BoundingRect.Right - BoundingRect.Left) / 2 + BoundingRect.Left, j) == Color.FromArgb(255, 0, 0, 0)) { isBlack = true; if (storedWhiteSubgroup) { groupsDownMiddleVertical++; storedWhiteSubgroup = false; } } else { if (isBlack) { storedWhiteSubgroup = true; isBlack = false; } } } char guessedchar = '~'; switch (groupsDownMiddleVertical) { case 0: if (totalArea > 0.000157 * screentotalArea || specialcharsenabled == false) { guessedchar = '1'; } else { guessedchar = '.'; } break; case 2: if (totalArea < 0.0003571 * screentotalArea && specialcharsenabled) //(double)rightArea / leftArea < 1.1) //1.1256) { guessedchar = 'e'; } else if ((double)rightArea / leftArea > 1.6160) //1.6 1.6177 { guessedchar = '3'; } else if ((double)midwidth / BoundingRect.Width > 0.75) //1.2597) { guessedchar = '5'; } else { guessedchar = '8'; } break; case 1: if ((double)lArea / tArea > 1.6691) //1.6406 1.7420 1.6933 { guessedchar = '6'; } else if ((double)lArea / tArea > 1.3405) //1.2514 { guessedchar = '4'; } else if ((double)lArea / tArea <= 1) //0.9769 { if ((double)midwidth / BoundingRect.Width < 0.7045) { guessedchar = '7'; } else { guessedchar = '9'; } } else { if ((double)rightArea / leftArea > 1.20) // 1.1540) { guessedchar = '2'; } else { guessedchar = '0'; } } break; default: break; } TotalArea = totalArea; MaxVerticalGroups = groupsDownMiddleVertical; UpperArea = tArea; LowerArea = lArea; LeftArea = leftArea; RightArea = rightArea; GuessedCharacter = guessedchar; }
/// <summary> /// See above. /// </summary> /// <param name="b"></param> /// <param name="boundingRectangle"></param> /// <param name="threshold"></param> /// <returns></returns> public static List<Line> OCRBitmap(Bitmap b, Rectangle boundingRectangle, IEnumerable<Color> threshold) { // Perf -- lock crud using (LockBitmap lb = new LockBitmap(b)) { return OCRBitmap(lb, boundingRectangle, threshold); } }
/// <summary> /// Performs OCR on a bitmap, reading the Chars and Lines present. /// </summary> /// <param name="lb">The bitmap to OCR</param> /// <param name="boundingRectangle">Defines the region of the bitmap to OCR. Defining a larger region than necessary will have perf consequences.</param> /// <param name="threshold">An enumeration of colors which are considered text colors. Every pixel which is not one of these colors will be ignored.</param> /// <returns>A list of lines. Note that character OCR is not performed on the returned lines -- those lines' DoOCR function needs to be called separately (for perf reasons)</returns> public static List<Line> OCRBitmap(LockBitmap lb, Rectangle boundingRectangle, IEnumerable<Color> threshold) { List<Line> lines = new List<Line>(); int rectLeft = -1, rectRight = -1, rectTop = -1, rectBot = -1; bool inLine = false; for (int y = boundingRectangle.Top; y < boundingRectangle.Bottom; y++) { bool foundBlack = false; for (int x = boundingRectangle.Left; x < boundingRectangle.Right; x++) { Color pixel = lb.GetPixel(x, y); if (pixel == Color.FromArgb(255, 0, 0, 0)) { lb.SetPixel(x, y, Color.White); } foreach (Color c in threshold) { if (c == pixel) { lb.SetPixel(x, y, Color.Black); if (rectTop == -1) { rectTop = y; } if (x < rectLeft || rectLeft == -1) { rectLeft = x; } if (x > rectRight || rectRight == -1) { rectRight = x; } foundBlack = true; inLine = true; break; } } } if (inLine && !foundBlack) { rectBot = y - 1; // Minimum area requirements if ((rectBot - rectTop) > 5) { lines.Add(new Line(lb, new Rectangle(rectLeft, rectTop, rectRight - rectLeft, rectBot - rectTop))); } rectLeft = rectTop = rectRight = rectBot = -1; inLine = false; } } return lines; }
/// <summary> /// Calculates the percentage of a given bitmap's region which is populated by certain colors. /// </summary> /// <param name="lb">The bitmap to analyze</param> /// <param name="boundingRect">The region of the bitmap to analyze</param> /// <param name="colors">The colors to scan for. Every other color is ignored.</param> /// <returns></returns> public static double GetBlobDensity(LockBitmap lb, Rectangle boundingRect, IEnumerable<Color> colors) { int totalMatches = 0; try { for (int x = (boundingRect.Left >= 0 ? boundingRect.Left : 0); x <= boundingRect.Right; x++) { for (int y = boundingRect.Top; y <= boundingRect.Bottom && y < lb.Height; y++) { Color c = lb.GetPixel(x, y); foreach (Color match in colors) { if (c == match) { totalMatches++; break; } } } } return (double)totalMatches / (boundingRect.Height * boundingRect.Width); } catch (Exception) { return 0.0f; } }
public Line(LockBitmap b, Rectangle boundingRectangle) { BoundingRectangle = boundingRectangle; List<Char> characters = new List<Char>(); int rectLeft = -1, rectTop = -1, rectBottom = -1, rectRight = -1; bool inRect = false; Color black = Color.FromArgb(255, 0, 0, 0); for (int x = boundingRectangle.Left; x <= boundingRectangle.Right; x++) { bool blackFound = false; for (int y = boundingRectangle.Top; y <= boundingRectangle.Bottom; y++) { Color c = b.GetPixel(x, y); if (c == black) { if (rectLeft == -1) { rectLeft = x; } if (y < rectTop || rectTop == -1) { rectTop = y; } if (y > rectBottom || rectBottom == -1) { rectBottom = y; } blackFound = true; inRect = true; } } if (!blackFound && inRect) { rectRight = x - 1; characters.Add(new Char(new Rectangle(rectLeft, rectTop, rectRight - rectLeft, rectBottom - rectTop))); rectLeft = rectTop = rectRight = rectBottom = -1; inRect = false; } } if (inRect) { rectRight = boundingRectangle.Right; characters.Add(new Char(new Rectangle(rectLeft, rectTop, rectRight - rectLeft, rectBottom - rectTop))); } Characters = characters; }
/// <summary> /// Helper method for GetHeroes /// </summary> /// <param name="lines"></param> /// <param name="lb"></param> /// <returns></returns> private static ParsedHeroes ParseHeroes(List<Line> lines, LockBitmap lb) { ParsedHeroes parsedHeroes = new ParsedHeroes(); if (lines.Count() == 0) { return null; } else if (lines.Count() <= 3) { // it's cid it's cid it's cid // Note: Cid is special because of 2 reasons: when she appears it's the only time in the game // there can be less than 3 heroes on screen. Second, she doesn't have a number to represent DPS, // so we need to special case some stuff to make OCR work. parsedHeroes.HeroStats = new List<HeroStats>(); parsedHeroes.FirstHeroIndex = 0; parsedHeroes.LastHeroIndex = 0; HeroStats cid = new HeroStats(); cid.Hero = GameEngine.HeroList[0]; cid.Level = 0; cid.bottomright.X = lines[0].GetBoundingRectangle().Right; cid.bottomright.Y = lines[0].GetBoundingRectangle().Bottom; parsedHeroes.HeroStats.Add(cid); return parsedHeroes; } List<Line> widths = new List<Line>(); List<Line> levels = new List<Line>(); int i = -1; bool rectSkipped = false; if (lines[1].GetBoundingRectangle().Top - lines[0].GetBoundingRectangle().Top > HeroSeperatorHeight) { i = 1; rectSkipped = true; } else { i = 0; } for (; i < lines.Count(); i+=2) { widths.Add(lines[i]); if (i + 1 < lines.Count()) { levels.Add(lines[i + 1]); } } // If less than 6 lines -- it's Cid if (lines.Count() < 6) { parsedHeroes.FirstHeroIndex = 0; parsedHeroes.LastHeroIndex = lines.Count() / 2; } else { int smallestIndex = -1; int smallestScore = int.MaxValue; for (int j = 0; j + widths.Count() <= HeroList.Count(); j++) { int thisScore = 0; for (int k = 0; k < widths.Count(); k++) { thisScore += Math.Abs((int)(HeroList[j + k].Namewidth * PlayableArea.Width - widths[k].GetBoundingRectangle().Width)); } if (thisScore < smallestScore) { smallestScore = thisScore; smallestIndex = j; } } parsedHeroes.FirstHeroIndex = smallestIndex; parsedHeroes.LastHeroIndex = smallestIndex + widths.Count() - 1; } List<HeroStats> heroStats = new List<HeroStats>(); if (parsedHeroes.FirstHeroIndex == 1 && rectSkipped) { // oh god, is it cid? why is it always cid if (lines[0].GetBoundingRectangle().Left < lb.Width / 2) { HeroStats stats = new HeroStats(); stats.Hero = HeroList[0]; stats.Level = 0; stats.bottomright = new Point(lines[0].GetBoundingRectangle().Right, lines[0].GetBoundingRectangle().Bottom); heroStats.Add(stats); parsedHeroes.FirstHeroIndex = 0; } } for (int j = 0; j < widths.Count(); j++) { HeroStats stats = new HeroStats(); stats.Hero = HeroList[parsedHeroes.FirstHeroIndex + j]; stats.bottomright = new Point(widths[j].GetBoundingRectangle().Right, widths[j].GetBoundingRectangle().Bottom); if (j < levels.Count()) { levels[j].DoOcr(lb, PlayableArea.Width * PlayableArea.Height, false, lb.Width / 2, 3 /* lvl */); if (!int.TryParse(levels[j].OcrString, out stats.Level)) { stats.Level = 0; } } else { stats.Level = -1; } heroStats.Add(stats); } parsedHeroes.HeroStats = heroStats; return parsedHeroes; }
/// <summary> /// Tries to parse all heroes on screen. Is null if there is crap on the screen preventing the heros from being parsed. Is very slow. /// </summary> /// <returns></returns> public static ParsedHeroes GetHeroes() { Size s = HeroesArea.Size; using (Bitmap bitmap = new Bitmap(s.Width, s.Height)) { using (Graphics g = Graphics.FromImage(bitmap)) { g.CopyFromScreen(new Point(HeroesArea.Left, HeroesArea.Top), Point.Empty, s); } using (LockBitmap lb = new LockBitmap(bitmap)) { List<Line> lines = OCREngine.OCRBitmap(lb, new Rectangle(0, 0, bitmap.Width, bitmap.Height), new Color[] { Color.FromArgb(255, 254, 254, 254), Color.FromArgb(255, 254, 254, 253), Color.FromArgb(255, 102, 51, 204), // purple for gilded heroes }); ParsedHeroes ph = GameEngine.ParseHeroes(lines, lb); if (ph == null) { return null; } foreach (HeroStats hs in ph.HeroStats) { hs.CalculateUpgrades(lb); } return ph; } } }