public static bool TraceLine(FastBitmapHSV bitmap, int posX, int posY, int incX, int incY, int traceLen, FastPixelMatch colorMatch, out Point posHit, bool bDebugMode = false) { if (bDebugMode) { Logger.WriteLine("TraceLine [" + posX + ", " + posY + "] -> [" + (posX + (incX * traceLen)) + ", " + (posY + (incY * traceLen)) + "]"); } for (int stepIdx = 0; stepIdx < traceLen; stepIdx++) { int scanX = posX + (stepIdx * incX); int scanY = posY + (stepIdx * incY); FastPixelHSV testPx = bitmap.GetPixel(scanX, scanY); bool bIsMatching = colorMatch.IsMatching(testPx); if (bDebugMode) { Logger.WriteLine(" [" + scanX + ", " + scanY + "] " + testPx + " => match:" + bIsMatching); } if (bIsMatching) { posHit = new Point(scanX, scanY); return(true); } } if (bDebugMode) { Logger.WriteLine(" >> failed"); } posHit = new Point(posX + (traceLen * incX), posY + (traceLen * incY)); return(false); }
public static List <Point> TraceSpansH(FastBitmapHSV bitmap, Rectangle box, FastPixelMatch colorMatch, int minSize, bool bDebugMode = false) { List <Point> result = new List <Point>(); int lastX = -1; bool bHasMatch = false; for (int IdxX = box.Left; IdxX <= box.Right; IdxX++) { bHasMatch = false; for (int IdxY = box.Top; IdxY <= box.Bottom; IdxY++) { FastPixelHSV testPx = bitmap.GetPixel(IdxX, IdxY); bHasMatch = colorMatch.IsMatching(testPx); if (bHasMatch) { if (bDebugMode) { Logger.WriteLine("[" + IdxX + ", " + IdxY + "] " + testPx + " => match!"); } break; } } if (lastX == -1 && bHasMatch) { lastX = IdxX; } else if (lastX >= 0 && !bHasMatch) { int spanSize = IdxX - lastX; if (spanSize > minSize) { if (bDebugMode) { Logger.WriteLine(">> adding span: " + lastX + ", size:" + spanSize); } result.Add(new Point(lastX, spanSize)); } lastX = -1; } } if (lastX >= 0 && bHasMatch) { int spanSize = box.Right - lastX + 1; if (spanSize > minSize) { if (bDebugMode) { Logger.WriteLine(">> adding span: " + lastX + ", size:" + spanSize); } result.Add(new Point(lastX, spanSize)); } } return(result); }
public static float CountFillPct(FastBitmapHSV bitmap, Rectangle box, FastPixelMatch colorMatch) { int totalPixels = (box.Width + 1) * (box.Height + 1); int matchPixels = 0; for (int IdxY = box.Top; IdxY <= box.Bottom; IdxY++) { for (int IdxX = box.Left; IdxX <= box.Right; IdxX++) { FastPixelHSV testPx = bitmap.GetPixel(IdxX, IdxY); matchPixels += colorMatch.IsMatching(testPx) ? 1 : 0; } } return((float)matchPixels / totalPixels); }
public static FastBitmapHash CalculateImageHash(FastBitmapHSV bitmap, Rectangle box, Rectangle contextBox, FastPixelMatch colorMatch, int hashWidth, int hashHeight, bool bNormalizeColors = false) { byte[] hashImage = new byte[hashWidth * hashHeight]; // scale to requested size float scaleX = (float)hashWidth / box.Width; float scaleY = (float)hashHeight / box.Height; float endY = 0.0f; for (int hashY = 0; hashY < hashHeight; hashY++) { float startY = endY; endY = (hashY + 1) / scaleY; if (endY >= box.Height) { endY = box.Height - 0.00001f; } float endX = 0.0f; for (int hashX = 0; hashX < hashWidth; hashX++) { float startX = endX; endX = (hashX + 1) / scaleX; if (endX >= box.Width) { endX = box.Width - 0.00001f; } float sum = 0.0f; int sumDivNum = 0; for (int srcY = (int)startY; srcY <= (int)endY; srcY++) { float partY = 1.0f; if (srcY == (int)startY) { partY -= startY - srcY; } if (srcY == (int)endY) { partY -= srcY + 1 - endY; } for (int srcX = (int)startX; srcX <= (int)endX; srcX++) { float partX = 1.0f; if (srcX == (int)startX) { partX -= startX - srcX; } if (srcX == (int)endX) { partX -= srcX + 1 - endX; } FastPixelHSV valuePx = bitmap.GetPixel(box.Left + srcX, box.Top + srcY); float srcValueNorm = colorMatch.IsMatching(valuePx) ? (bNormalizeColors ? 1.0f : (valuePx.Monochrome / 255.0f)) : 0.0f; sum += srcValueNorm * partY * partX; sumDivNum++; } } float storeValueNorm = sum / sumDivNum; hashImage[hashX + (hashY * hashWidth)] = (byte)Math.Min(15, 15 * storeValueNorm); } } TlshBuilder hashBuilder = new TlshBuilder(); hashBuilder.Update(hashImage); TlshHash complexHash = hashBuilder.IsValid(false) ? hashBuilder.GetHash(false) : null; ScanLineHash simpleHash = ScanLineHash.CreateFromImage(hashImage, hashWidth, hashHeight); HashCollection hashCollection = new HashCollection(complexHash, simpleHash); return(new FastBitmapHash { Hash = hashCollection, Height = hashHeight, Width = hashWidth, Pixels = hashImage, SourceBounds = box, ContextBounds = contextBox, DrawPos = new Point(box.Right + 1, box.Top), }); }
public static bool CreateFloodFillBitmap(FastBitmapHSV srcBitmap, Point floodOrigin, Size floodExtent, FastPixelMatch colorMatch, out FastBitmapHSV floodBitmap, out Rectangle floodBounds, bool bDebugMode = false) { List <Point> floodPoints = new List <Point>(); int minX = floodOrigin.X; int maxX = floodOrigin.X; int minY = floodOrigin.Y; int maxY = floodOrigin.Y; Rectangle boundRect = new Rectangle(floodOrigin.X - floodExtent.Width, floodOrigin.Y - floodExtent.Height, floodExtent.Width * 2, floodExtent.Height * 2); Stack <Point> openList = new Stack <Point>(); openList.Push(floodOrigin); while (openList.Count > 0) { Point testPoint = openList.Pop(); if (floodPoints.Contains(testPoint)) { continue; } FastPixelHSV testPx = srcBitmap.GetPixel(testPoint.X, testPoint.Y); if (bDebugMode) { Logger.WriteLine("[" + testPoint.X + ", " + testPoint.Y + "] " + testPx + ", match:" + colorMatch.IsMatching(testPx) + ", inBounds:" + boundRect.Contains(testPoint)); } if (colorMatch.IsMatching(testPx) && boundRect.Contains(testPoint)) { floodPoints.Add(testPoint); minX = Math.Min(minX, testPoint.X); maxX = Math.Max(maxX, testPoint.X); minY = Math.Min(minY, testPoint.Y); maxY = Math.Max(maxY, testPoint.Y); openList.Push(new Point(testPoint.X - 1, testPoint.Y)); openList.Push(new Point(testPoint.X + 1, testPoint.Y)); openList.Push(new Point(testPoint.X, testPoint.Y - 1)); openList.Push(new Point(testPoint.X, testPoint.Y + 1)); openList.Push(new Point(testPoint.X - 1, testPoint.Y - 1)); openList.Push(new Point(testPoint.X + 1, testPoint.Y - 1)); openList.Push(new Point(testPoint.X - 1, testPoint.Y + 1)); openList.Push(new Point(testPoint.X + 1, testPoint.Y + 1)); } } floodBounds = new Rectangle(minX, minY, maxX - minX + 1, maxY - minY + 1); if (floodPoints.Count > 0) { FastPixelHSV[] bitmapPixels = new FastPixelHSV[floodBounds.Width * floodBounds.Height]; for (int Idx = 0; Idx < bitmapPixels.Length; Idx++) { bitmapPixels[Idx] = new FastPixelHSV(false); } foreach (Point p in floodPoints) { int Idx = (p.X - minX) + ((p.Y - minY) * floodBounds.Width); bitmapPixels[Idx] = new FastPixelHSV(true); } floodBitmap = new FastBitmapHSV() { Pixels = bitmapPixels, Width = floodBounds.Width, Height = floodBounds.Height, }; } else { floodBitmap = null; } return(floodBitmap != null); }
public static Point TraceBoundsH(FastBitmapHSV bitmap, Rectangle box, FastPixelMatch colorMatch, int maxGapSize, bool bDebugMode = false) { int boxCenter = (box.Right + box.Left) / 2; int minX = -1; int gapStart = -1; bool bPrevMatch = false; for (int IdxX = box.Left; IdxX < boxCenter; IdxX++) { bool bHasMatch = false; for (int IdxY = box.Top; IdxY <= box.Bottom; IdxY++) { FastPixelHSV testPx = bitmap.GetPixel(IdxX, IdxY); bHasMatch = colorMatch.IsMatching(testPx); if (bHasMatch) { if (bDebugMode) { Logger.WriteLine("[" + IdxX + ", " + IdxY + "] " + testPx + " => match!"); } break; } } if (bHasMatch) { int gapSize = IdxX - gapStart; if ((gapSize > maxGapSize && gapStart > 0) || (minX < 0)) { minX = IdxX; gapStart = -1; } if (bDebugMode) { Logger.WriteLine(">> gapSize:" + gapSize + ", gapStart:" + gapStart + ", bPrevMatch:" + bPrevMatch + " => minX:" + minX); } } else { if (bPrevMatch) { gapStart = IdxX; if (bDebugMode) { Logger.WriteLine(">> gapStart:" + gapStart); } } } bPrevMatch = bHasMatch; } if (minX >= 0) { int maxX = -1; gapStart = -1; bPrevMatch = false; for (int IdxX = box.Right; IdxX > boxCenter; IdxX--) { bool bHasMatch = false; for (int IdxY = box.Top; IdxY <= box.Bottom; IdxY++) { FastPixelHSV testPx = bitmap.GetPixel(IdxX, IdxY); bHasMatch = colorMatch.IsMatching(testPx); if (bHasMatch) { if (bDebugMode) { Logger.WriteLine("[" + IdxX + ", " + IdxY + "] " + testPx + " => match!"); } break; } } if (bHasMatch) { int gapSize = gapStart - IdxX; if ((gapSize > maxGapSize && gapStart > 0) || (maxX < 0)) { maxX = IdxX; gapStart = -1; } if (bDebugMode) { Logger.WriteLine(">> gapSize:" + gapSize + ", gapStart:" + gapStart + ", bPrevMatch:" + bPrevMatch + " => maxX:" + maxX); } } else { if (bPrevMatch) { gapStart = IdxX; if (bDebugMode) { Logger.WriteLine(">> gapStart:" + gapStart); } } } bPrevMatch = bHasMatch; } if (maxX > minX) { return(new Point(minX, maxX - minX)); } else { if (bDebugMode) { Logger.WriteLine(">> TraceBoundsH: no match on right side!"); } } } else { if (bDebugMode) { Logger.WriteLine(">> TraceBoundsH: no match on left side!"); } } return(new Point()); }
public static List <int> TraceLineSegments(FastBitmapHSV bitmap, int posX, int posY, int incX, int incY, int traceLen, FastPixelMatch colorMatch, int minSegSize, int segLimit, bool bDebugMode = false) { FastPixelHSV[] streakBuffer = new FastPixelHSV[minSegSize]; int bufferIdx = 0; for (int Idx = 0; Idx < streakBuffer.Length; Idx++) { streakBuffer[Idx] = new FastPixelHSV(255, 255, 255); } List <int> result = new List <int>(); bool bWasMatch = false; if (bDebugMode) { Logger.WriteLine("TraceLineSegments [" + posX + ", " + posY + "] -> [" + (posX + (incX * traceLen)) + ", " + (posY + (incY * traceLen)) + "]"); } for (int stepIdx = 0; stepIdx < traceLen; stepIdx++) { int scanX = posX + (stepIdx * incX); int scanY = posY + (stepIdx * incY); FastPixelHSV testPx = bitmap.GetPixel(scanX, scanY); streakBuffer[bufferIdx] = testPx; bufferIdx = (bufferIdx + 1) % minSegSize; bool bBufferMatching = true; for (int Idx = 0; Idx < streakBuffer.Length; Idx++) { bBufferMatching = bBufferMatching && colorMatch.IsMatching(streakBuffer[Idx]); } if (bDebugMode) { Logger.WriteLine(" [" + scanX + ", " + scanY + "] " + testPx + " => match:" + colorMatch.IsMatching(testPx) + ", buffer:" + bBufferMatching); } if (bBufferMatching != bWasMatch) { bWasMatch = bBufferMatching; int segPos = bBufferMatching ? (incX != 0) ? (scanX - (incX * minSegSize)) : (scanY - (incY * minSegSize)) : (incX != 0) ? scanX : scanY; result.Add(segPos); if (bDebugMode) { Logger.WriteLine(" >> mark segment:" + segPos); } if (result.Count >= segLimit && segLimit > 0) { break; } } } return(result); }
public static int FindPatternMatch(FastBitmapHSV bitmap, Point pos, FastPixelMatch colorMatch, List <ImagePatternDigit> patterns, out ImageDataDigit digitData, bool bDebugMode = false) { int bestValue = 0; int bestValueExact = 0; float totalBestScore = 0; float totalBestScoreExact = 0; const int offsetSize = 5; const int cacheWidth = offsetSize + 10 + offsetSize; const int cacheHeight = offsetSize + 8 + offsetSize; byte[] PixelMatch = new byte[cacheWidth * cacheHeight]; int bestPatternMatchX = 0; int bestPatternExactX = 0; int bestPatternMatchY = 0; int bestPatternExactY = 0; foreach (ImagePatternDigit pattern in patterns) { float bestScore = 0; float bestScoreExact = 0; int bestPosX = 0; int bestPosXExact = 0; int bestPosY = 0; int bestPosYExact = 0; int ByteIdx = 0; for (int IdxY = 0; IdxY < cacheHeight; IdxY++) { for (int IdxX = 0; IdxX < cacheWidth; IdxX++) { FastPixelHSV testPx = bitmap.GetPixel(pos.X + IdxX - offsetSize, pos.Y + IdxY - offsetSize); PixelMatch[ByteIdx] = colorMatch.IsMatching(testPx) ? (byte)1 : (byte)0; ByteIdx++; } } /*if (bDebugMode) * { * bestPosX = 894; * bestPosY = 615; * bestScore = HasPatternMatch(PixelMatch, bestPosX - pos.X + offsetSize, bestPosY - pos.Y + offsetSize, cacheWidth, * pattern, pattern.Value == 3 || pattern.Value == 5 || pattern.Value == 8); * } * else*/ { for (int offsetX = -offsetSize; offsetX <= offsetSize; offsetX++) { for (int offsetY = -offsetSize; offsetY <= offsetSize; offsetY++) { float testScore = HasPatternMatch(PixelMatch, offsetX + offsetSize, offsetY + offsetSize, cacheWidth, false, pattern); if (testScore > bestScore) { bestPosX = pos.X + offsetX; bestPosY = pos.Y + offsetY; bestScore = testScore; } float testScoreExact = HasPatternMatch(PixelMatch, offsetX + offsetSize, offsetY + offsetSize, cacheWidth, true, pattern); if (testScoreExact > bestScoreExact) { bestPosXExact = pos.X + offsetX; bestPosYExact = pos.Y + offsetY; bestScoreExact = testScoreExact; } } } } if (bestScore > totalBestScore) { totalBestScore = bestScore; bestValue = pattern.Value; bestPatternMatchX = bestPosX - pos.X + offsetSize; bestPatternMatchY = bestPosY - pos.Y + offsetSize; } if (bestScoreExact > totalBestScoreExact) { totalBestScoreExact = bestScoreExact; bestValueExact = pattern.Value; bestPatternExactX = bestPosXExact - pos.X + offsetSize; bestPatternExactY = bestPosYExact - pos.Y + offsetSize; } if (bDebugMode) { Logger.WriteLine("FindPatternMatch: " + pattern.Value + " best at (" + bestPosX + ", " + bestPosY + ") = " + (int)(100 * bestScore) + "%," + " exact at (" + bestPosXExact + ", " + bestPosYExact + ") = " + (int)(100 * bestScoreExact) + "%"); } } { byte[] digitDataPixels = new byte[10]; for (int IdxX = 0; IdxX < 10; IdxX++) { int Value = 0; int ByteIdx = (bestPatternMatchY * cacheWidth) + bestPatternMatchX + IdxX; for (int IdxY = 0; IdxY < 8; IdxY++) { Value |= (PixelMatch[ByteIdx + (IdxY * cacheWidth)] != 0) ? (1 << IdxY) : 0; } digitDataPixels[IdxX] = (byte)Value; } digitData = new ImageDataDigit(digitDataPixels); if (bDebugMode) { for (int IdxY = 0; IdxY < 8; IdxY++) { string debugDesc = ""; for (int IdxX = 0; IdxX < 10; IdxX++) { byte ColValue = digitDataPixels[IdxX]; byte RowMask = (byte)(1 << IdxY); debugDesc += ((ColValue & RowMask) != 0 ? "#" : ".") + " "; } Logger.WriteLine(debugDesc); } } } return((totalBestScoreExact > 0.95) ? bestValueExact : (totalBestScore > 0.8) ? bestValue : 0); }
private bool HasCactpotCircleEdgeV(FastBitmapHSV bitmap, int posX, int posY, int sideX, bool bDebugDetection = false) { const int spreadY = 2; const int offsetDeepX = 5; FastPixelHSV testPx = bitmap.GetPixel(posX, posY); if (!colorMatchCircleOut.IsMatching(testPx) && !colorMatchCircleOutH.IsMatching(testPx)) { if (bDebugDetection) { Logger.WriteLine("HasCactpotCircleEdgeV[" + posX + "," + posY + "] failed: edge center " + testPx); } return(false); } testPx = bitmap.GetPixel(posX - (sideX * offsetDeepX), posY); if (!colorMatchCircleOut.IsMatching(testPx) && !colorMatchCircleOutH.IsMatching(testPx)) { if (bDebugDetection) { Logger.WriteLine("HasCactpotCircleEdgeV[" + posX + "," + posY + "] failed: deep center " + testPx); } return(false); } testPx = bitmap.GetPixel(posX, posY - spreadY); if (!colorMatchCircleOut.IsMatching(testPx) && !colorMatchCircleOutH.IsMatching(testPx)) { if (bDebugDetection) { Logger.WriteLine("HasCactpotCircleEdgeV[" + posX + "," + posY + "] failed: edge top " + testPx); } return(false); } testPx = bitmap.GetPixel(posX, posY + spreadY); if (!colorMatchCircleOut.IsMatching(testPx) && !colorMatchCircleOutH.IsMatching(testPx)) { if (bDebugDetection) { Logger.WriteLine("HasCactpotCircleEdgeV[" + posX + "," + posY + "] failed: edge bottom " + testPx); } return(false); } testPx = bitmap.GetPixel(posX + sideX, posY); if (!colorMatchBack.IsMatching(testPx) && !colorMatchCircleFade.IsMatching(testPx) && !colorMatchCircleFadeH.IsMatching(testPx)) { if (bDebugDetection) { Logger.WriteLine("HasCactpotCircleEdgeV[" + posX + "," + posY + "] failed: edge center prev " + testPx); } return(false); } testPx = bitmap.GetPixel(posX + sideX, posY - spreadY); if (!colorMatchBack.IsMatching(testPx) && !colorMatchCircleFade.IsMatching(testPx) && !colorMatchCircleFadeH.IsMatching(testPx)) { if (bDebugDetection) { Logger.WriteLine("HasCactpotCircleEdgeV[" + posX + "," + posY + "] failed: edge top prev " + testPx); } return(false); } testPx = bitmap.GetPixel(posX + sideX, posY + spreadY); if (!colorMatchBack.IsMatching(testPx) && !colorMatchCircleFade.IsMatching(testPx) && !colorMatchCircleFadeH.IsMatching(testPx)) { if (bDebugDetection) { Logger.WriteLine("HasCactpotCircleEdgeV[" + posX + "," + posY + "] failed: edge bottom prev " + testPx); } return(false); } return(true); }
private bool HasCactpotCircleMatch(FastBitmapHSV bitmap, int posX, int posY, int cellSize, int offsetIn, bool bDebugDetection = false) { int sizeA = cellSize; int offsetB = Math.Max(5, offsetIn - 2); int sizeB = sizeA - (offsetB * 2); Point[] testPoints = new Point[4]; int numHighlights = 0; // 4 points: corners of cell => dark background { testPoints[0].X = posX; testPoints[0].Y = posY; testPoints[1].X = posX + sizeA; testPoints[1].Y = posY; testPoints[2].X = posX; testPoints[2].Y = posY + sizeA; testPoints[3].X = posX + sizeA; testPoints[3].Y = posY + sizeA; for (int Idx = 0; Idx < 4; Idx++) { FastPixelHSV testPx = bitmap.GetPixel(testPoints[Idx].X, testPoints[Idx].Y); //if (bDebugDetection) { Logger.WriteLine("[" + posX + ", " + posY + "] testing A[" + testPoints[Idx].X + "," + testPoints[Idx].Y + "]: " + testPx); } if (!colorMatchBack.IsMatching(testPx)) { if (bDebugDetection) { Logger.WriteLine("[" + posX + ", " + posY + "] failed, not background: A[" + testPoints[Idx].X + "," + testPoints[Idx].Y + "]: " + testPx); } return(false); } } } // 4 points: corners with offset B => dark background { testPoints[0].X = posX + offsetB; testPoints[0].Y = posY + offsetB; testPoints[1].X = posX + sizeA - offsetB; testPoints[1].Y = posY + offsetB; testPoints[2].X = posX + offsetB; testPoints[2].Y = posY + sizeA - offsetB; testPoints[3].X = posX + sizeA - offsetB; testPoints[3].Y = posY + sizeA - offsetB; for (int Idx = 0; Idx < 4; Idx++) { FastPixelHSV testPx = bitmap.GetPixel(testPoints[Idx].X, testPoints[Idx].Y); //if (bDebugDetection) { Logger.WriteLine("[" + posX + ", " + posY + "] testing B1[" + testPoints[Idx].X + "," + testPoints[Idx].Y + "]: " + testPx); } if (!colorMatchBack.IsMatching(testPx)) { if (bDebugDetection) { Logger.WriteLine("[" + posX + ", " + posY + "] failed, not background: B1[" + testPoints[Idx].X + "," + testPoints[Idx].Y + "]: " + testPx); } return(false); } } } Point[] testPointCloser = new Point[4]; // 4 points: midpoints with offset B => yellow/white { testPoints[0].X = posX + offsetB; testPoints[0].Y = posY + (sizeA / 2); testPoints[1].X = posX + sizeA - offsetB; testPoints[1].Y = posY + (sizeA / 2); testPoints[2].X = posX + (sizeA / 2); testPoints[2].Y = posY + offsetB; testPoints[3].X = posX + (sizeA / 2); testPoints[3].Y = posY + sizeA - offsetB; for (int Idx = 0; Idx < 4; Idx++) { FastPixelHSV testPx = bitmap.GetPixel(testPoints[Idx].X, testPoints[Idx].Y); //if (bDebugDetection) { Logger.WriteLine("[" + posX + ", " + posY + "] testing B2[" + testPoints[Idx].X + "," + testPoints[Idx].Y + "]: " + testPx); } if (colorMatchCircleOutH.IsMatching(testPx)) { numHighlights++; } else if (!colorMatchCircleOut.IsMatching(testPx)) { if (bDebugDetection) { Logger.WriteLine("[" + posX + ", " + posY + "] failed, not circle: B2[" + testPoints[Idx].X + "," + testPoints[Idx].Y + "]: " + testPx); } return(false); } } } // 4 points: midpoints of diagonal lines between midpoints B (C) => yellow/white { testPoints[0].X = posX + offsetB + (sizeB / 4); testPoints[0].Y = posY + offsetB + (sizeB / 4); testPoints[1].X = posX + sizeA - offsetB - (sizeB / 4); testPoints[1].Y = posY + offsetB + (sizeB / 4); testPoints[2].X = posX + offsetB + (sizeB / 4); testPoints[2].Y = posY + sizeA - offsetB - (sizeB / 4); testPoints[3].X = posX + sizeA - offsetB - (sizeB / 4); testPoints[3].Y = posY + sizeA - offsetB - (sizeB / 4); for (int Idx = 0; Idx < 4; Idx++) { FastPixelHSV testPx = bitmap.GetPixel(testPoints[Idx].X, testPoints[Idx].Y); //if (bDebugDetection) { Logger.WriteLine("[" + posX + ", " + posY + "] testing B3[" + testPoints[Idx].X + "," + testPoints[Idx].Y + "]: " + testPx); } if (!colorMatchCircleIn.IsMatching(testPx)) { if (bDebugDetection) { Logger.WriteLine("[" + posX + ", " + posY + "] failed, not circle: B3[" + testPoints[Idx].X + "," + testPoints[Idx].Y + "]: " + testPx); } return(false); } } } return((numHighlights == 0) || (numHighlights == 4)); }