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);
}
}
}
User contributions licensed under CC BY-SA 3.0