Exemple #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);
        }
Exemple #2
0
        public static FastBitmapHSV ConvertToFastBitmap(Bitmap image)
        {
            FastBitmapHSV result = new FastBitmapHSV();

            result.Width  = image.Width;
            result.Height = image.Height;
            result.Pixels = new FastPixelHSV[image.Width * image.Height];

            unsafe
            {
                BitmapData bitmapData    = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, image.PixelFormat);
                int        bytesPerPixel = Image.GetPixelFormatSize(image.PixelFormat) / 8;
                int        bytesPerRow   = bitmapData.Width * bytesPerPixel;

                for (int IdxY = 0; IdxY < image.Height; IdxY++)
                {
                    byte *pixels   = (byte *)bitmapData.Scan0 + (IdxY * bitmapData.Stride);
                    int   IdxPixel = IdxY * image.Width;
                    for (int IdxByte = 0; IdxByte < bytesPerRow; IdxByte += bytesPerPixel)
                    {
                        result.Pixels[IdxPixel] = new FastPixelHSV(pixels[IdxByte + 2], pixels[IdxByte + 1], pixels[IdxByte]);
                        IdxPixel++;
                    }
                }

                image.UnlockBits(bitmapData);
            }

            return(result);
        }
Exemple #3
0
        public static Bitmap ConvertToBitmap(FastBitmapHSV bitmap)
        {
            Bitmap bmp = new Bitmap(bitmap.Width, bitmap.Height);

            unsafe
            {
                BitmapData bitmapData    = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat);
                int        bytesPerPixel = Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
                int        bytesPerRow   = bitmapData.Width * bytesPerPixel;

                for (int IdxY = 0; IdxY < bmp.Height; IdxY++)
                {
                    byte *pixels = (byte *)bitmapData.Scan0 + (IdxY * bitmapData.Stride);
                    for (int IdxByte = 0; IdxByte < bytesPerRow; IdxByte += bytesPerPixel)
                    {
                        FastPixelHSV writePx    = bitmap.GetPixel(IdxByte / bytesPerPixel, IdxY);
                        Color        writeColor = Color.FromArgb(writePx.Monochrome, writePx.Monochrome, writePx.Monochrome);
                        pixels[IdxByte + 3] = writeColor.A;
                        pixels[IdxByte + 2] = writeColor.R;
                        pixels[IdxByte + 1] = writeColor.G;
                        pixels[IdxByte + 0] = writeColor.B;
                    }
                }

                bmp.UnlockBits(bitmapData);
            }

            return(bmp);
        }
Exemple #4
0
        public virtual bool DoWork(FastBitmapHSV bitmap, int scannerFlags, Stopwatch perfTimer, bool debugMode)
        {
            this.debugMode = debugMode;
            debugShapes.Clear();
            debugHashes.Clear();

            return(false);
        }
        private Rectangle FindCactpotCoords(FastBitmapHSV bitmap)
        {
            Rectangle rect = new Rectangle();

            // find number fields
            //
            // scan method similar to triple triad board with matching top left corner and tile size

            int minCellSize = bitmap.Width * 2 / 100;
            int maxCellSize = bitmap.Width * 6 / 100;
            int maxScanX    = bitmap.Width - (maxCellSize * 3) - 20;
            int maxScanY    = bitmap.Height - (maxCellSize * 2) - 20;

            Stopwatch stopwatch = new Stopwatch();

            stopwatch.Start();

            bool[] highlights = new bool[9];
            bool   bScanning  = true;

            for (int IdxY = 0; IdxY < maxScanY && bScanning; IdxY++)
            {
                int ScanY = (bitmap.Height / 2) + ((IdxY / 2) * ((IdxY % 2 == 0) ? 1 : -1));
                for (int IdxX = 10; IdxX < maxScanX && bScanning; IdxX++)
                {
                    bool debugGridMatch = false;
                    //debugGridMatch = (IdxX == 585) && (ScanY == 495);
                    if (HasCactpotCircleEdgeV(bitmap, IdxX, ScanY, -1, debugGridMatch))
                    {
                        for (int cellSize = minCellSize; cellSize < maxCellSize && bScanning; cellSize++)
                        {
                            if (HasCactpotCellMatch(bitmap, IdxX, ScanY, cellSize, out int CellPosX, out int CellPosY, out int offsetIn, debugGridMatch))
                            {
                                if (HasCactpotCircleMatch(bitmap, CellPosX, CellPosY, cellSize, offsetIn, debugGridMatch))
                                {
                                    if (HasCactpotMatch(bitmap, CellPosX, CellPosY, cellSize, offsetIn, debugGridMatch))
                                    {
                                        rect           = new Rectangle(CellPosX, CellPosY, cellSize * 3, cellSize * 3);
                                        cachedOffsetIn = offsetIn;

                                        bScanning = false;
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
            }

            stopwatch.Stop();
            if (debugMode)
            {
                Logger.WriteLine("FindCactpotCoords: " + stopwatch.ElapsedMilliseconds + "ms");
            }

            return(rect);
        }
Exemple #6
0
        public static Bitmap CreateBitmapWithShapes(FastBitmapHSV bitmap, List <Rectangle> listBounds, List <FastBitmapHash> listHashes)
        {
            Bitmap bmp = new Bitmap(bitmap.Width, bitmap.Height);

            unsafe
            {
                BitmapData bitmapData    = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat);
                int        bytesPerPixel = Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
                int        bytesPerRow   = bitmapData.Width * bytesPerPixel;

                for (int IdxY = 0; IdxY < bmp.Height; IdxY++)
                {
                    byte *pixels = (byte *)bitmapData.Scan0 + (IdxY * bitmapData.Stride);
                    for (int IdxByte = 0; IdxByte < bytesPerRow; IdxByte += bytesPerPixel)
                    {
                        FastPixelHSV writePx    = bitmap.GetPixel(IdxByte / bytesPerPixel, IdxY);
                        Color        writeColor = Color.FromArgb(writePx.Monochrome, writePx.Monochrome, writePx.Monochrome);
                        pixels[IdxByte + 3] = writeColor.A;
                        pixels[IdxByte + 2] = writeColor.R;
                        pixels[IdxByte + 1] = writeColor.G;
                        pixels[IdxByte + 0] = writeColor.B;
                    }
                }

                bmp.UnlockBits(bitmapData);
            }

            using (Graphics gBmp = Graphics.FromImage(bmp))
            {
                if (listBounds.Count > 0)
                {
                    Pen boundsPen = new Pen(Color.Cyan);
                    gBmp.DrawRectangles(boundsPen, listBounds.ToArray());
                }

                Pen hashPen = new Pen(Color.Magenta);
                foreach (FastBitmapHash hashData in listHashes)
                {
                    gBmp.DrawRectangle(hashPen, hashData.SourceBounds);

                    if (hashData.Pixels != null)
                    {
                        for (int HashX = 0; HashX < hashData.Width; HashX++)
                        {
                            for (int HashY = 0; HashY < hashData.Height; HashY++)
                            {
                                byte  writeValue    = hashData.Pixels[HashX + (HashY * hashData.Width)];
                                int   writeValueInt = Math.Min(255, writeValue * 16);
                                Color writeColor    = Color.FromArgb(writeValueInt, 0, 0);
                                bmp.SetPixel(HashX + hashData.DrawPos.X, HashY + hashData.DrawPos.Y, writeColor);
                            }
                        }
                    }
                }
            }

            return(bmp);
        }
Exemple #7
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);
        }
Exemple #8
0
        public static void SaveBitmapWithShapes(FastBitmapHSV bitmap, List <Rectangle> listBounds, List <FastBitmapHash> listHashes, string fileName)
        {
            if (File.Exists(fileName))
            {
                File.Delete(fileName);
            }

            using (Bitmap bmp = CreateBitmapWithShapes(bitmap, listBounds, listHashes))
            {
                bmp.Save(AssetManager.Get().CreateFilePath(fileName), ImageFormat.Png);
            }
        }
Exemple #9
0
 public static void FindColorRange(FastBitmapHSV bitmap, Rectangle box, out int minMono, out int maxMono)
 {
     minMono = 255;
     maxMono = 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);
             minMono = Math.Min(minMono, testPx.Monochrome);
             maxMono = Math.Max(maxMono, testPx.Monochrome);
         }
     }
 }
Exemple #10
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);
        }
        private bool HasCactpotMatch(FastBitmapHSV bitmap, int posX, int posY, int cellSize, int offsetIn, bool bDebugDetection = false)
        {
            if (bDebugDetection)
            {
                Logger.WriteLine("HasCactpotMatch[" + posX + ", " + posY + "]? testing...");
            }

            // circle at (posX, posY) already matched, test other 8
            bool bHasMatch   = true;
            bool bHasMatch01 = (bHasMatch || bDebugDetection) && HasCactpotCircleMatch(bitmap, posX + cellSize, posY, cellSize, offsetIn, bDebugDetection);

            bHasMatch = bHasMatch && bHasMatch01;
            bool bHasMatch02 = (bHasMatch || bDebugDetection) && HasCactpotCircleMatch(bitmap, posX + (cellSize * 2), posY, cellSize, offsetIn, bDebugDetection);

            bHasMatch = bHasMatch && bHasMatch02;

            bool bHasMatch10 = (bHasMatch || bDebugDetection) && HasCactpotCircleMatch(bitmap, posX, posY + cellSize, cellSize, offsetIn, bDebugDetection);

            bHasMatch = bHasMatch && bHasMatch10;
            bool bHasMatch11 = (bHasMatch || bDebugDetection) && HasCactpotCircleMatch(bitmap, posX + cellSize, posY + cellSize, cellSize, offsetIn, bDebugDetection);

            bHasMatch = bHasMatch && bHasMatch11;
            bool bHasMatch12 = (bHasMatch || bDebugDetection) && HasCactpotCircleMatch(bitmap, posX + (cellSize * 2), posY + cellSize, cellSize, offsetIn, bDebugDetection);

            bHasMatch = bHasMatch && bHasMatch12;

            bool bHasMatch20 = (bHasMatch || bDebugDetection) && HasCactpotCircleMatch(bitmap, posX, posY + (cellSize * 2), cellSize, offsetIn, bDebugDetection);

            bHasMatch = bHasMatch && bHasMatch20;
            bool bHasMatch21 = (bHasMatch || bDebugDetection) && HasCactpotCircleMatch(bitmap, posX + cellSize, posY + (cellSize * 2), cellSize, offsetIn, bDebugDetection);

            bHasMatch = bHasMatch && bHasMatch21;
            bool bHasMatch22 = (bHasMatch || bDebugDetection) && HasCactpotCircleMatch(bitmap, posX + (cellSize * 2), posY + (cellSize * 2), cellSize, offsetIn, bDebugDetection);

            bHasMatch = bHasMatch && bHasMatch22;

            if (bDebugDetection)
            {
                Logger.WriteLine(">> [#" + (bHasMatch01 ? "#" : ".") + (bHasMatch02 ? "#" : ".") +
                                 "][" + (bHasMatch10 ? "#" : ".") + (bHasMatch11 ? "#" : ".") + (bHasMatch12 ? "#" : ".") +
                                 "][" + (bHasMatch20 ? "#" : ".") + (bHasMatch21 ? "#" : ".") + (bHasMatch22 ? "#" : ".") +
                                 "] => " + (bHasMatch ? "match" : "nope"));
            }

            return(bHasMatch);
        }
        public override bool DoWork(FastBitmapHSV bitmap, int scannerFlags, Stopwatch perfTimer, bool debugMode)
        {
            base.DoWork(bitmap, scannerFlags, perfTimer, debugMode);
            perfTimer.Restart();

            if (!HasValidCache(bitmap, scannerFlags))
            {
                cachedCircles = null;
                cachedBoard   = FindCactpotCoords(bitmap);

                if (cachedBoard.Width > 0)
                {
                    screenAnalyzer.currentScanArea = cachedBoard;
                    cachedCircles = FindCactpotCircleCoords(cachedBoard);
                }
            }

            cachedGameState     = null;
            cachedGameStateBase = null;

            if (cachedCircles != null)
            {
                screenAnalyzer.ClearKnownHashes();
                cachedGameState     = new GameState();
                cachedGameStateBase = cachedGameState;

                for (int Idx = 0; Idx < cachedGameState.board.Length; Idx++)
                {
                    cachedGameState.board[Idx]   = ParseCactpotCircle(bitmap, cachedCircles[Idx], "board" + Idx);
                    cachedGameState.numRevealed += (cachedGameState.board[Idx] != 0) ? 1 : 0;
                }
            }

            perfTimer.Stop();
            if (debugMode)
            {
                Logger.WriteLine("Parse cactpot board: " + perfTimer.ElapsedMilliseconds + "ms");
            }
            return(cachedGameState != null);
        }
        private int ParseCactpotCircle(FastBitmapHSV bitmap, Rectangle circleBox, string debugName)
        {
            float[] values = ExtractCirclePattern(bitmap, circleBox);
            int     result;

            ImageHashData pattern = new ImageHashData()
            {
                type = EImageHashType.Cactpot, previewBounds = circleBox, isKnown = true
            };

            pattern.CalculateHash(values);

            // allow overwrites in case there is a user defined value, must be exact match
            ImageHashData overridePattern = ImageHashDB.Get().FindExactMatch(pattern);

            if (overridePattern != null)
            {
                result = (int)overridePattern.ownerOb;
            }
            else
            {
                result         = classifierCactpot.Calculate(values, out float DummyPct);
                pattern.isAuto = true;
            }

            pattern.ownerOb = result;
            screenAnalyzer.AddImageHash(pattern);

            Rectangle previewBounds = new Rectangle(circleBox.X + circleBox.Width, circleBox.Y, 8, 16);

            debugHashes.Add(new ImageUtils.HashPreview()
            {
                hashValues = values, bounds = previewBounds
            });
            debugShapes.Add(GetCirclePatternBounds(circleBox));

            return(result);
        }
Exemple #14
0
        public static FastPixelHSV GetAverageColor(FastBitmapHSV bitmap, Rectangle bounds)
        {
            float scale = 1.0f / (bounds.Width * bounds.Height);
            float accR  = 0.0f;
            float accG  = 0.0f;
            float accB  = 0.0f;

            for (int idxY = 0; idxY < bounds.Height; idxY++)
            {
                for (int idxX = 0; idxX < bounds.Width; idxX++)
                {
                    FastPixelHSV testPx = bitmap.GetPixelRaw(bounds.X + idxX, bounds.Y + idxY);
                    accR += testPx.RawR;
                    accG += testPx.RawG;
                    accB += testPx.RawB;
                }
            }

            FastPixelHSV avgPx = new FastPixelHSV((byte)(accR * scale), (byte)(accG * scale), (byte)(accB * scale));

            avgPx.ExpandHSV();
            return(avgPx);
        }
        private bool HasCactpotCellMatch(FastBitmapHSV bitmap, int posX, int posY, int cellSize, out int cellPosX, out int cellPosY, out int offsetIn, bool bDebugDetection = false)
        {
            cellPosX = 0;
            cellPosY = 0;
            offsetIn = 0;

            bool bHasEdgeV2 = HasCactpotCircleEdgeV(bitmap, posX + cellSize, posY, -1);

            if (bDebugDetection)
            {
                Logger.WriteLine("HasCactpotCellMatch[" + posX + ", " + posY + "], cellSize:" + cellSize + " => " + (bHasEdgeV2 ? "found V2" : "nope"));
            }

            if (bHasEdgeV2)
            {
                for (int Idx = 5; Idx < 20; Idx++)
                {
                    int  invPosX    = posX + cellSize - Idx;
                    bool bHasEdgeV3 = HasCactpotCircleEdgeV(bitmap, invPosX, posY, 1);
                    if (bHasEdgeV3)
                    {
                        cellPosX = posX - (Idx / 2);
                        cellPosY = posY - (cellSize / 2);
                        offsetIn = Idx;

                        if (bDebugDetection)
                        {
                            Logger.WriteLine(">> spacing check: V3 at X:{0}, offsetIn:{1}", invPosX, offsetIn);
                        }
                        return(true);
                    }
                }
            }

            return(false);
        }
Exemple #16
0
        public static float[] ExtractImageFeaturesScaled(FastBitmapHSV bitmap, Rectangle bounds, int destWidth, int destHeight, Func <FastPixelHSV, float> pxFunc)
        {
            float[] values = new float[destWidth * destHeight];

            // scale to requested size
            float scaleX = (float)destWidth / bounds.Width;
            float scaleY = (float)destHeight / bounds.Height;
            float endY   = 0.0f;

            for (int hashY = 0; hashY < destHeight; hashY++)
            {
                float startY = endY;
                endY = (hashY + 1) / scaleY;
                if (endY >= bounds.Height)
                {
                    endY = bounds.Height - 0.00001f;
                }
                float endX = 0.0f;

                for (int hashX = 0; hashX < destWidth; hashX++)
                {
                    float startX = endX;
                    endX = (hashX + 1) / scaleX;
                    if (endX >= bounds.Width)
                    {
                        endX = bounds.Width - 0.00001f;
                    }
                    float sum = 0.0f;

                    float sumDiv = 0.00001f;
                    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 testPx      = bitmap.GetPixel(bounds.Left + srcX, bounds.Top + srcY);
                            float        testPxValue = pxFunc(testPx);
                            sum    += testPxValue * partY * partX;
                            sumDiv += partY * partX;
                        }
                    }

                    values[hashX + (hashY * destWidth)] = sum / sumDiv;
                }
            }

            return(values);
        }
Exemple #17
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());
        }
        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));
        }
Exemple #19
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);
        }
        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 float[] ExtractCirclePattern(FastBitmapHSV bitmap, Rectangle circleBox)
 {
     return(ImageUtils.ExtractImageFeaturesScaled(bitmap, GetCirclePatternBounds(circleBox), 8, 16, ImageUtils.GetPixelFeaturesMono));
 }
Exemple #22
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);
        }
Exemple #23
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);
        }
 public override bool HasValidCache(FastBitmapHSV bitmap, int scannerFlags)
 {
     return((cachedBoard.Width > 0) && (cachedCircles != null) && HasCactpotMatch(bitmap, cachedBoard.Left, cachedBoard.Top, cachedBoard.Width / 3, cachedOffsetIn));
 }
Exemple #25
0
 public virtual bool HasValidCache(FastBitmapHSV bitmap, int scannerFlags)
 {
     return(false);
 }
Exemple #26
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),
            });
        }
        public void DoWork(EMode mode = EMode.Default, int scannerFlags = 0)
        {
            Stopwatch timerTotal = new Stopwatch();
            Stopwatch timerStep  = new Stopwatch();

            timerTotal.Start();

            bool debugMode = (mode & EMode.Debug) != EMode.None;

            activeScanner = null;
            currentMode   = mode;

            // load input
            bool hasInputImage = LoadInputImage(mode, timerStep, scanClipBounds);

            if (hasInputImage)
            {
                // convert to HSL representation
                timerStep.Restart();
                cachedFastBitmap = ImageUtils.ConvertToFastBitmap(screenReader.cachedScreenshot);
                timerStep.Stop();
                if (debugMode)
                {
                    Logger.WriteLine("Screenshot convert: " + timerStep.ElapsedMilliseconds + "ms");
                }
                if (Logger.IsSuperVerbose())
                {
                    Logger.WriteLine("Screenshot: {0}x{1}", screenReader.cachedScreenshot.Width, screenReader.cachedScreenshot.Height);
                }

                // reset scanner's intermediate data
                bool canResetCache = (mode & EMode.NeverResetCache) == EMode.None;
                if (canResetCache)
                {
                    bool forceResetCache = (mode & EMode.AlwaysResetCache) != EMode.None;
                    if (forceResetCache)
                    {
                        unknownHashes.Clear();
                        currentHashMatches.Clear();
                    }

                    // invalidate scanner's cache if input image size has changed
                    if (forceResetCache || cachedBitmapSize.Width <= 0 || cachedBitmapSize.Width != cachedFastBitmap.Width || cachedBitmapSize.Height != cachedFastBitmap.Height)
                    {
                        cachedBitmapSize = new Size(cachedFastBitmap.Width, cachedFastBitmap.Height);
                        foreach (var kvp in mapScanners)
                        {
                            kvp.Value.InvalidateCache();
                        }
                    }
                }

                currentState = EState.NoScannerMatch;

                // pass 1: check if cache is still valid for requested scanner
                foreach (var kvp in mapScanners)
                {
                    if ((kvp.Key & mode) != EMode.None && kvp.Value.HasValidCache(cachedFastBitmap, scannerFlags))
                    {
                        bool scanned = false;
                        try
                        {
                            scanned = kvp.Value.DoWork(cachedFastBitmap, scannerFlags, timerStep, debugMode);
                        }
                        catch (Exception ex)
                        {
                            Logger.WriteLine("Failed to scan [1] image! {0}", ex);
                            scanned = false;
                        }

                        if (scanned)
                        {
                            activeScanner = kvp.Value;
                            currentState  = (currentState == EState.NoScannerMatch) ? EState.NoErrors : currentState;
                            if (debugMode)
                            {
                                Logger.WriteLine("Scan [1] successful, type:{0}, state:{1}", kvp.Key, currentState);
                            }
                        }
                        else
                        {
                            currentState = EState.ScannerErrors;
                        }

                        break;
                    }
                }

                // pass 2: all requested
                if (activeScanner == null)
                {
                    foreach (var kvp in mapScanners)
                    {
                        if ((kvp.Key & mode) != EMode.None)
                        {
                            bool scanned = false;
                            try
                            {
                                scanned = kvp.Value.DoWork(cachedFastBitmap, scannerFlags, timerStep, debugMode);
                            }
                            catch (Exception ex)
                            {
                                Logger.WriteLine("Failed to scan [2] image! {0}", ex);
                                scanned = false;
                            }

                            if (scanned)
                            {
                                activeScanner = kvp.Value;
                                currentState  = (currentState == EState.NoScannerMatch) ? EState.NoErrors : currentState;
                                if (debugMode)
                                {
                                    Logger.WriteLine("Scan [2] successful, type:{0}, state:{1}", kvp.Key, currentState);
                                }
                                break;
                            }
                        }
                    }
                }

                // save debug markup if needed
                if ((activeScanner != null) && ((mode & EMode.DebugSaveMarkup) != EMode.None))
                {
                    List <Rectangle> debugBounds = new List <Rectangle>();
                    List <ImageUtils.HashPreview> debugHashes = new List <ImageUtils.HashPreview>();
                    activeScanner.AppendDebugShapes(debugBounds, debugHashes);

                    if (currentScanArea.Width > 0)
                    {
                        debugBounds.Add(currentScanArea);
                    }

                    timerStep.Restart();

                    string imagePath = GetDefaultScreenshotPath() + "screenshot-markup.png";
                    if (File.Exists(imagePath))
                    {
                        File.Delete(imagePath);
                    }

                    using (Bitmap markupBitmap = ImageUtils.ConvertToBitmap(cachedFastBitmap))
                    {
                        ImageUtils.DrawDebugShapes(markupBitmap, debugBounds);
                        ImageUtils.DrawDebugHashes(markupBitmap, debugHashes);

                        markupBitmap.Save(imagePath, ImageFormat.Png);
                    }

                    timerStep.Stop();
                    Logger.WriteLine("Screenshot save: " + timerStep.ElapsedMilliseconds + "ms");
                }
            }
            else
            {
                currentState = EState.NoInputImage;
            }

            timerTotal.Stop();
            if (debugMode)
            {
                Logger.WriteLine("Screenshot TOTAL: " + timerTotal.ElapsedMilliseconds + "ms");
            }
        }