예제 #1
0
        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);
        }
예제 #2
0
        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);
        }
예제 #3
0
        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);
        }
예제 #4
0
        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),
            });
        }
예제 #5
0
        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);
        }
예제 #6
0
        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());
        }
예제 #7
0
        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);
        }
예제 #8
0
        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);
        }
예제 #9
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);
        }
예제 #10
0
        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));
        }