/// <summary> /// Counts pixels of given color. Search in Y coordinate given and X range given /// </summary> /// <param name="locked">bitmap to search in</param> /// <param name="colorFunc">function that checks if the color should be counted or not</param> /// <param name="xRange">horizontal range in which to search for pixels</param> /// <param name="y">Y coordinate</param> /// <returns>number of pixels of given color found</returns> public static int CountPixelsHorizontal(LockBitmap locked, Func<Color, bool> colorFunc, IEnumerable<int> xRange, int y) { int count = 0; foreach (var x in xRange) { var pixel = locked.GetPixel(x, y); if (colorFunc(pixel)) { count++; } else { break; } } return count; }
private List<Line> GetTooltipBlackLines(LockBitmap locked, Rectangle searchArea, int projectedTooltipWidth, Func<double, int> v, Graphics g) { var lines = new List<Line>(); Func<Color, bool> blackFunc = c => c.R < 10 && c.G < 10 && c.B < 10; var blackLinesThreshold = (int)Math.Floor(0.6 / 100 * locked.Height); // 0,6% of Height // It doesn't make sense to start search at the beginning of searchArea as it always covers the upper-left corner, // so we can skip this first vertical scanline to save some time (wouldn't find black lines there anyway). // In most cases tooltip will be found in first iteration as long as it was on the left side of the mouse cursor, // otherwise it should take no more than 2 iterations of the outer loop for (int x = searchArea.Left + projectedTooltipWidth * 3 / 4; x < searchArea.Right; x += projectedTooltipWidth * 3 / 4) { int? firstLineX = null; g.DrawLine(Pens.Red, x, searchArea.Top, x, searchArea.Bottom); for (int y = searchArea.Top; y < searchArea.Bottom; y++) { var pixel = locked.GetPixel(x, y); if (blackFunc(pixel)) { var leftRange = Enumerable.Range(searchArea.Left, x - searchArea.Left).Reverse(); // search until the searchArea.Left as it covers the upper-left corner var rightRange = Enumerable.Range(x + 1, (int)(projectedTooltipWidth * 1.2)); int left = CountPixelsHorizontal(locked, blackFunc, leftRange, y); // count black pixels to the left from current pos int right = CountPixelsHorizontal(locked, blackFunc, rightRange, y); // count black pixels to the right from current pos var line_width = left + right + 1; if (line_width > v(39)) { // make bigger steps when longer lines are found, they waste a lot of pixels and give no results (step is almost like tooltip border width) y += v(0.5); continue; } if (line_width > v(37) && line_width < v(39)) // potential tooltip width, can't calculate it to the pixel's accuracy { if (!firstLineX.HasValue && lines.Count > 0 && lines.Last().P1.X != x - left) // group only lines with the same x-pos { lines.Clear(); } if (!firstLineX.HasValue || lines[0].P1.X == x - left) { lines.Add(new Line(new Point(x - left, y), new Point(x + right, y))); g.DrawLine(Pens.Lime, lines.Last().P1, lines.Last().P2); } if (!firstLineX.HasValue && lines.Count > blackLinesThreshold) // just found the beginning of the tooltip { firstLineX = lines[0].P1.X; // already found a potential tooltip, so need to adjust X a bit, so that it's more efficiently used (as less pixels checked as possible) // leaving it as it is would cause the algorithm to find a lot of black areas inside the tooltip (and a lot of pixels read for no reason), // but if we move X just next to the left border then the other inner border of the tooltip will stop search very soon for each line, reducing the number of checked pixels massively // Note: it's safe to modify a loop iterator as the code won't iterate more over the outer loop anyway (see below: if (firstLineX.HasValue)) x = firstLineX.Value; searchArea.Height = locked.Height - searchArea.Top; // extend searchArea to the bottom (almost.. see comment below) } } } else if (firstLineX.HasValue) // no need to search until the very bottom of the screenshot, last "black" pixel is for sure last "black" line in the tooltip { break; } } if (firstLineX.HasValue) { return lines; } } return null; }
private Line FindVerticalBorder(LockBitmap locked, IEnumerable<int> range, int y, Func<double, int> v, Func<Color, bool> borderFunc, bool isLeft = true) { foreach (var x in range) { var pixel = locked.GetPixel(x, y); if (borderFunc(pixel)) { var upStart = Math.Max(y - v(6), 0); var upRange = Enumerable.Range(upStart, y - upStart).Reverse(); var downRange = Enumerable.Range(y + 1, locked.Height - y - 1); var rng = isLeft ? Enumerable.Range(x + 1, 5) : Enumerable.Range(x - 5, 5).Reverse(); var up = CountPixelsVertical(locked, borderFunc, upRange, x, rng); var down = CountPixelsVertical(locked, borderFunc, downRange, x); return new Line(new Point(x, y - up), new Point(x, y + down)); } } return null; }
private int CountPixelsVertical(LockBitmap locked, Func<Color, bool> colorFunc, IEnumerable<int> range, int x, IEnumerable<int> additionalRange = null) { int count = 0; foreach (var y in range) { var pixel = locked.GetPixel(x, y); if (colorFunc(pixel)) { count++; if (additionalRange != null && additionalRange.All(ax => colorFunc(locked.GetPixel(ax, y)))) { break; } } else { break; } } return count; }
private static int? GetVerticalTextBounds(LockBitmap bmp, int top, int bottom, int left, int right, Func<Color, bool> colorFunc, bool bottomToTop = false) { var range = Enumerable.Range(left, right - left + 1); var vRange = Enumerable.Range(top, bottom - top + 1); if (bottomToTop) { vRange = vRange.Reverse(); } foreach (var y in vRange) { var r = range.Where(x => colorFunc(bmp.GetPixel(x, y))); if (r.Count() > 0) { return y; } } return null; }
private static int? GetHorizontalTextBounds(LockBitmap bmp, int top, int bottom, IEnumerable<int> widths, Func<Color, bool> colorFunc, bool rightToLeft = false) { var w = widths.ToArray(); var count = widths.Count(); var range = Enumerable.Range(top, bottom - top); for (int i = 0; i < count; i++) { var r = range.Where(y => colorFunc(bmp.GetPixel(w[i], y))); if (r.Count() > 0) { if (i > 0) // need to search previous "sectors" line by line to get exact results { var newStart = w[i - 1]; if (i > 2) { newStart = w[i - 3]; // ...or even 3 (sometimes a letter 'I' might get skipped by the scanlines) } else if (i > 1) { newStart = w[i - 2]; // search 2 previous sectors if possible } if (rightToLeft) { widths = Enumerable.Range(w[i], newStart - w[i]).Reverse(); } else { widths = Enumerable.Range(newStart, w[i] - newStart); } foreach (var x in widths) { r = range.Where(y => colorFunc(bmp.GetPixel(x, y))); if (r.Count() > 0) { return x; } } } return w[i]; // that's first found vertical scanline } } return null; }
public static Rectangle GetTextBounding(LockBitmap bitmap, Rectangle outerBound, Func<Color, bool> colorFunc) { int sampleSize = 3; int l = outerBound.Left, r = outerBound.Right, t = outerBound.Top, b = outerBound.Bottom; var left = GetHorizontalTextBounds(bitmap, t, b, Enumerable.Range(l, outerBound.Width * 3 / 4).Sample(sampleSize), colorFunc); if (left.HasValue) { l = left.Value; } var right = GetHorizontalTextBounds(bitmap, t, b, Enumerable.Range(outerBound.Right - outerBound.Width * 9 / 10, outerBound.Width * 9 / 10).Reverse().Sample(sampleSize), colorFunc, true); if (right.HasValue) { r = right.Value; } var top = GetVerticalTextBounds(bitmap, t, b, l, r, colorFunc); if (top.HasValue) { t = top.Value; } var bottom = GetVerticalTextBounds(bitmap, t, b, l, r, colorFunc, true); if (bottom.HasValue) { b = bottom.Value; } return Rectangle.FromLTRB(l, t, r, b); }