Find an Image in Image via sampling points across the image to search for

0

This code is based on D. Jurcau's Code located here. Find Bitmap within Bitmap

instead of scraping off percentages and shortcircuiting, idea is to condense the needle down to a smaller array, and verify those points accross the potential location for the needle.

The goal is to reduce the time it takes identify and click on a image on the screen.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace BotIt
{
    [ClassInterface(ClassInterfaceType.AutoDual)]
    public class DetectImageAndClick
    {

        [Flags]
        private enum MouseEvents
        {
            LeftDown = 0x00000002,
            LeftUp = 0x00000004,
            MiddleDown = 0x00000020,
            MiddleUp = 0x00000040,
            Move = 0x00000001,
            Absolute = 0x00008000,
            RightDown = 0x00000008,
            RightUp = 0x00000010
        }

        public enum RegionToSearch
        {
            Full = 0,
            Top = 1,
            Bottom = 2
        }

        public const int KEYEVENTF_EXTENDEDKEY = 0x0001; //key down
        public const int KEYEVENTF_KEYUP = 0x0002; //key up

        public const int VK_SNAPSHOT = 0x2C; //VirtualKey code for print key

        [DllImport("user32.dll")]
        private static extern void keybd_event(byte vVK, byte bScan, int dwFlags, int dwExtraInfo);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GetCursorPos(out MousePoint lpMousePoint);

        [DllImport("user32.dll")]
        private static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo);

        private MousePoint GetCursorPosition()
        {
            var gotPoint = GetCursorPos(out MousePoint currentMousePoint);
            if (!gotPoint) { currentMousePoint = new MousePoint(0, 0); }
            return currentMousePoint;
        }

        private void MouseEvent(MouseEvents value)
        {
            MousePoint position = GetCursorPosition();

            mouse_event
                ((int)value,
                 position.X,
                 position.Y,
                 0,
                 0)
                ;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct MousePoint
        {
            public int X;
            public int Y;

            public MousePoint(int x, int y)
            {
                X = x;
                Y = y;
            }
        }

        private void PrintScreen()
        {
            keybd_event(VK_SNAPSHOT, 0, KEYEVENTF_EXTENDEDKEY, 0);
            keybd_event(VK_SNAPSHOT, 0, KEYEVENTF_KEYUP, 0);
        }

        public Bitmap CaptureScreenPrtSc()
        {
            PrintScreen();
            Application.DoEvents();
            if (Clipboard.ContainsImage())
            {
                using (Image img = Clipboard.GetImage())
                {
                    if (img != null)
                    {
                        img.Save("Clipboard.PNG", ImageFormat.Png);
                        return new Bitmap(img);
                    }                   
                }
            }
            return CaptureScreenPrtSc();
        }

        public bool FindAndClick(string FilePathToPNG, RegionToSearch region = RegionToSearch.Full, bool Click = true)
        {
            if (null == FilePathToPNG)
            {
                return false;
            }
            if (!File.Exists(FilePathToPNG))
            {
                return false;
            }
            using (var needle = new Bitmap(FilePathToPNG))
            {

                using (var haystack = CaptureScreenPrtSc())
                {
                    Bitmap stack; 
                    if(haystack == null)
                    {
                        stack = new Bitmap("Clipboard.PNG");
                    }
                    else
                    {
                        stack = haystack;
                    }
                    var needleArray = GetPixelArray(needle, RegionToSearch.Full);

                    var haystackArray = GetPixelArray(stack, region);
                    var CInfo = CompressNeedle(needleArray);
                    var lr = Parallel.ForEach(FindMatch(haystackArray.Take(stack.Height - needle.Height), needleArray[0]), new ParallelOptions() { CancellationToken = new CancellationToken() }, (firstLineMatchPoint, parallelLoopState) =>
                    {
                        if (IsCompressedNeedlePresentAtLocation(haystackArray, CInfo, firstLineMatchPoint))
                        {
                            if (Click)
                            {
                                var MatchPoint = firstLineMatchPoint;
                                MatchPoint.X -= needle.Width / 4;
                                MatchPoint.Y -= needle.Height / 4;
                                Cursor.Position = MatchPoint;
                                MouseEvent(MouseEvents.LeftDown);

                                MouseEvent(MouseEvents.LeftUp);
                            }
                            parallelLoopState.Stop();
                        }
                    });

                    if (lr.IsCompleted)
                    {
                        return false;
                    }

                    return true;
                }
            }
        }

        private int[][] GetPixelArray(Bitmap bitmap, RegionToSearch region)
        {
            int[][] result;
            BitmapData bitmapData;
            switch (region)
            {
                case RegionToSearch.Full:
                    bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
                    result = new int[bitmap.Height][];
                    for (int y = 0; y < bitmap.Height - 1; y++)
                    {
                        result[y] = new int[bitmap.Width];
                        Marshal.Copy(bitmapData.Scan0 + (y * bitmapData.Stride), result[y], 0, result[y].Length);
                    }
                    break;

                case RegionToSearch.Top:
                    bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height / 2), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
                    result = new int[bitmap.Height / 2][];
                    for (int y = 0; y < bitmap.Height / 2; y++)
                    {
                        result[y] = new int[bitmap.Width];
                        Marshal.Copy(bitmapData.Scan0 + (y * bitmapData.Stride), result[y], 0, result[y].Length);
                    }
                    break;

                case RegionToSearch.Bottom:
                    result = new int[bitmap.Height / 2][];
                    bitmapData = bitmap.LockBits(new Rectangle(0, bitmap.Height / 2, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
                    for (int y = bitmap.Height; y > bitmap.Height / 2; y--)
                    {
                        result[y] = new int[bitmap.Width];
                        Marshal.Copy(bitmapData.Scan0 + (y * bitmapData.Stride), result[y], 0, result[y].Length);
                    }
                    break;

                default:
                    bitmapData = default;
                    result = default;
                    break;
            }
            if (result == default)
            {
                if (bitmapData != default)
                {
                    bitmap.UnlockBits(bitmapData);
                }
                return default;
            }
            bitmap.UnlockBits(bitmapData);
            return result;
        }

        private IEnumerable<Point> FindMatch(IEnumerable<int[]> haystackLines, int[] needleLine)
        {
            var y = 0;
            foreach (int[] haystackLine in haystackLines)
            {
                for (int x = 0, n = haystackLine.Length - needleLine.Length; x < n; ++x)
                {
                    if (ContainSameElements(haystackLine, x, needleLine, 0, needleLine.Length))
                    {
                        yield return new Point(x, y);
                    }
                }
                y += 1;
            }
        }

        private bool ContainSameElements(int[] first, int firstStart, int[] second, int secondStart, int length)
        {
            int Success = 0;
            for (int i = 0; i < length; ++i)
            {
                if ((first[i + firstStart] ^ second[i + secondStart]) != 0)
                {
                    return false;
                }
                else
                {
                    Success += Success < second.Length / 2 ? 1 : -Success - 1;
                    if (Success == -1)
                    {
                        return true;
                    }
                }
            }
            return true;
        }

        private bool IsNeedlePresentAtLocation(int[][] haystack, int[][] needle, Point point, int alreadyVerified)
        {
            // needle identification
            // c = 1,
            // Verify 10% and Skip 10%(20% total)
            // c +=2 (3),
            // verify 30% and Skip 30% = 80% total,
            // c+=2 (5),
            // c == 5 ? c - 4 : c,
            // c = 1,
            // verify 10% + Skip 10% = 100% total

            int Success = 0;
            int JumpCount = 1;

            //we already know that "alreadyVerified" lines already match, so skip them
            int y = alreadyVerified;

            do
            {
                if (!ContainSameElements(haystack[y + point.Y], point.X, needle[y], 0, needle[y].Length))
                {
                    return false;
                }
                else
                {
                    Success++;
                    if (Success == needle[0].Length / 100 * (10 * JumpCount))
                    {
                        y += needle[0].Length / 100 * (10 * JumpCount);
                        JumpCount += 2;
                        JumpCount = JumpCount == 5 ? JumpCount - 4 : JumpCount;
                        Success = 0;
                    }
                    else
                    {
                        y++;
                    }
                }
            } while (y < needle.Length - 1);
            return true;
        }

        private bool IsCompressedNeedlePresentAtLocation(int[][] haystack, (int YDiv, int XDiv, int[][] Array) CInfo, Point MatchPoint)
        {
            if (CInfo == default || haystack == null)
                return false;

            var CompressedNeedle = CInfo.Array;
            int iY = MatchPoint.Y;
            int LoopYCount = 0;
            foreach (int[] CompressionLine in CompressedNeedle)
            {
                for (int iX = 0; iX < CompressionLine.Length; iX++)
                {
                    if(CompressionLine[iX] != haystack[iY + (LoopYCount * CInfo.YDiv)][iX * CInfo.XDiv])
                    {
                        return false;
                    }
                }
                LoopYCount++;
                iY++;
            }
            return true;
        }

        private (int YDiv, int XDiv, int[][] Array) CompressNeedle(int[][] needleArray)
        {
            int DivisorY, DivisorX;

            if (needleArray != null && needleArray.Length >= 50)
            {
                DivisorY = needleArray.Length > 100 ? 10 : 5;
            }
            else
            {
                return default;
            }

            if (needleArray[0].Length >= 50)
            {
                DivisorX = needleArray[0].Length > 100 ? 10 : 5;
            }
            else
            {
                return default;
            }

            int[][] CompressionArray = new int[DivisorY][];
            for (int y = 0; y < CompressionArray.Length; y++)
            {
                CompressionArray[y] = new int[DivisorX];
            }

            int iY = 0;
            foreach(int[] CompressionLine in CompressionArray)
            {                
                for(int iX = 0; iX < CompressionLine.Length; iX++)
                {
                    CompressionArray[iY][iX] = needleArray[iY * DivisorY][iX * DivisorX];
                }
                iY++;
            }

            return (DivisorY, DivisorX, CompressionArray);
        }  
    }
}  
c#
bit-manipulation
asked on Stack Overflow Dec 31, 2019 by BanMe

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0