first commit
This commit is contained in:
commit
761b95080f
404 files changed
+312608
No files matched your search
@@ -0,0 +1,85 @@
|
||||
using System.Drawing.Imaging;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace anjianjingling.Vision
|
||||
{
|
||||
/// <summary>
|
||||
/// 找色:LockBits 锁内存逐像素扫描,支持容差。
|
||||
/// </summary>
|
||||
public sealed class ColorFinder
|
||||
{
|
||||
/// <summary>解析 "RRGGBB" 或 "#RRGGBB" 颜色。</summary>
|
||||
public static Color ParseHex(string hex)
|
||||
{
|
||||
hex = hex.Trim().TrimStart('#');
|
||||
if (hex.Length != 6) throw new ArgumentException($"颜色格式非法:{hex}(应为 RRGGBB)");
|
||||
int r = Convert.ToInt32(hex[0..2], 16);
|
||||
int g = Convert.ToInt32(hex[2..4], 16);
|
||||
int b = Convert.ToInt32(hex[4..6], 16);
|
||||
return Color.FromArgb(r, g, b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在 bmp 的指定区域内找颜色,返回第一个匹配点的 bmp 内坐标;未找到返回 (-1,-1)。
|
||||
/// </summary>
|
||||
public Point FindColor(Bitmap bmp, int x1, int y1, int x2, int y2, Color target, int tolerance = 0)
|
||||
{
|
||||
// 裁剪到合法范围
|
||||
x1 = Math.Clamp(x1, 0, bmp.Width - 1);
|
||||
x2 = Math.Clamp(x2, 0, bmp.Width - 1);
|
||||
y1 = Math.Clamp(y1, 0, bmp.Height - 1);
|
||||
y2 = Math.Clamp(y2, 0, bmp.Height - 1);
|
||||
if (x1 > x2 || y1 > y2) return new Point(-1, -1);
|
||||
|
||||
var rect = new Rectangle(x1, y1, x2 - x1 + 1, y2 - y1 + 1);
|
||||
using var fast = new LockedBitmap(bmp, rect);
|
||||
for (int y = 0; y < rect.Height; y++)
|
||||
{
|
||||
for (int x = 0; x < rect.Width; x++)
|
||||
{
|
||||
var c = fast.GetPixel(x, y);
|
||||
if (Math.Abs(c.R - target.R) <= tolerance &&
|
||||
Math.Abs(c.G - target.G) <= tolerance &&
|
||||
Math.Abs(c.B - target.B) <= tolerance)
|
||||
{
|
||||
return new Point(rect.X + x, rect.Y + y);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Point(-1, -1);
|
||||
}
|
||||
|
||||
/// <summary>LockBits 包装,按 Format24bpp/32bpp 通用读取 BGR。</summary>
|
||||
private sealed class LockedBitmap : IDisposable
|
||||
{
|
||||
private readonly BitmapData _data;
|
||||
private readonly Bitmap _bmp;
|
||||
private readonly int _stride;
|
||||
private readonly byte[] _buf;
|
||||
private readonly int _bytesPerPixel;
|
||||
|
||||
public LockedBitmap(Bitmap bmp, Rectangle rect)
|
||||
{
|
||||
_bmp = bmp;
|
||||
_bytesPerPixel = Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
|
||||
if (_bytesPerPixel < 3) _bytesPerPixel = 3;
|
||||
_data = bmp.LockBits(rect, ImageLockMode.ReadOnly,
|
||||
_bytesPerPixel == 3 ? PixelFormat.Format24bppRgb : PixelFormat.Format32bppArgb);
|
||||
_stride = Math.Abs(_data.Stride);
|
||||
_buf = new byte[_stride * rect.Height];
|
||||
Marshal.Copy(_data.Scan0, _buf, 0, _buf.Length);
|
||||
}
|
||||
|
||||
public Color GetPixel(int x, int y)
|
||||
{
|
||||
int i = y * _stride + x * _bytesPerPixel;
|
||||
return Color.FromArgb(_buf[i + 2], _buf[i + 1], _buf[i]);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_bmp.UnlockBits(_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using OpenCvSharp;
|
||||
using System.Drawing.Imaging;
|
||||
|
||||
namespace anjianjingling.Vision
|
||||
{
|
||||
/// <summary>
|
||||
/// 找图:OpenCV 模板匹配(TM_CCOEFF_NORMED),支持相似度阈值。
|
||||
/// </summary>
|
||||
public sealed class ImageFinder : IDisposable
|
||||
{
|
||||
private readonly Dictionary<string, Mat> _templateCache = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private static Mat BitmapToMat(Bitmap bmp)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
bmp.Save(ms, ImageFormat.Png);
|
||||
return Cv2.ImDecode(ms.ToArray(), ImreadModes.Color);
|
||||
}
|
||||
|
||||
private Mat LoadTemplate(string path)
|
||||
{
|
||||
if (_templateCache.TryGetValue(path, out var cached)) return cached;
|
||||
if (!File.Exists(path))
|
||||
throw new FileNotFoundException($"模板图片不存在:{path}");
|
||||
var mat = Cv2.ImRead(path, ImreadModes.Color);
|
||||
if (mat.Empty())
|
||||
throw new InvalidDataException($"模板图片无法读取:{path}");
|
||||
_templateCache[path] = mat;
|
||||
return mat;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在 haystack 位图中查找模板。
|
||||
/// </summary>
|
||||
/// <param name="haystack">被查找的画面(通常是区域截图)</param>
|
||||
/// <param name="templatePath">模板图路径</param>
|
||||
/// <param name="threshold">相似度阈值 0~1</param>
|
||||
/// <param name="offsetX">结果坐标要加上的屏幕偏移 X</param>
|
||||
/// <param name="offsetY">结果坐标要加上的屏幕偏移 Y</param>
|
||||
/// <returns>匹配中心屏幕坐标与相似度;未达阈值返回 (-1,-1,score)</returns>
|
||||
public (int X, int Y, double Score) Find(Bitmap haystack, string templatePath,
|
||||
double threshold, int offsetX = 0, int offsetY = 0)
|
||||
{
|
||||
using var hay = BitmapToMat(haystack);
|
||||
using var tpl = LoadTemplate(templatePath);
|
||||
|
||||
if (tpl.Width > hay.Width || tpl.Height > hay.Height)
|
||||
throw new ArgumentException($"模板图({tpl.Width}x{tpl.Height})大于查找区域({hay.Width}x{hay.Height})");
|
||||
|
||||
using var result = new Mat();
|
||||
Cv2.MatchTemplate(hay, tpl, result, TemplateMatchModes.CCoeffNormed);
|
||||
Cv2.MinMaxLoc(result, out _, out double maxVal, out _, out OpenCvSharp.Point maxLoc);
|
||||
|
||||
if (maxVal >= threshold)
|
||||
{
|
||||
int cx = offsetX + maxLoc.X + tpl.Width / 2;
|
||||
int cy = offsetY + maxLoc.Y + tpl.Height / 2;
|
||||
return (cx, cy, maxVal);
|
||||
}
|
||||
return (-1, -1, maxVal);
|
||||
}
|
||||
|
||||
/// <summary>清除模板缓存(模板文件更新后调用)。</summary>
|
||||
public void ClearCache()
|
||||
{
|
||||
foreach (var m in _templateCache.Values) m.Dispose();
|
||||
_templateCache.Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ClearCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using anjianjingling.Native;
|
||||
|
||||
namespace anjianjingling.Vision
|
||||
{
|
||||
/// <summary>
|
||||
/// 屏幕/区域/窗口截图(GDI BitBlt)。
|
||||
/// </summary>
|
||||
public sealed class ScreenCapture
|
||||
{
|
||||
/// <summary>截取整个虚拟屏幕(所有显示器)。</summary>
|
||||
public Bitmap CaptureScreen()
|
||||
{
|
||||
int x = Win32.GetSystemMetrics(Win32.SM_XVIRTUALSCREEN);
|
||||
int y = Win32.GetSystemMetrics(Win32.SM_YVIRTUALSCREEN);
|
||||
int w = Win32.GetSystemMetrics(Win32.SM_CXVIRTUALSCREEN);
|
||||
int h = Win32.GetSystemMetrics(Win32.SM_CYVIRTUALSCREEN);
|
||||
return CaptureRegion(x, y, w, h);
|
||||
}
|
||||
|
||||
/// <summary>截取指定屏幕区域。</summary>
|
||||
public Bitmap CaptureRegion(int x, int y, int width, int height)
|
||||
{
|
||||
if (width <= 0 || height <= 0)
|
||||
throw new ArgumentException($"截图区域非法:({x},{y},{width},{height})");
|
||||
|
||||
var bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
|
||||
using (var g = Graphics.FromImage(bmp))
|
||||
{
|
||||
g.CopyFromScreen(x, y, 0, 0, new Size(width, height), CopyPixelOperation.SourceCopy);
|
||||
}
|
||||
return bmp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 截取指定窗口(含被遮挡部分,依赖 PrintWindow;部分程序不支持则返回 null)。
|
||||
/// </summary>
|
||||
public Bitmap? CaptureWindow(IntPtr hWnd)
|
||||
{
|
||||
if (!Win32.GetClientRect(hWnd, out var rc) || rc.Width <= 0 || rc.Height <= 0)
|
||||
return null;
|
||||
|
||||
var bmp = new Bitmap(rc.Width, rc.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
|
||||
using (var g = Graphics.FromImage(bmp))
|
||||
{
|
||||
IntPtr hdc = g.GetHdc();
|
||||
bool ok = Win32.PrintWindow(hWnd, hdc, Win32.PW_RENDERFULLCONTENT);
|
||||
g.ReleaseHdc(hdc);
|
||||
if (!ok)
|
||||
{
|
||||
bmp.Dispose();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return bmp;
|
||||
}
|
||||
|
||||
/// <summary>截图并保存到文件。</summary>
|
||||
public string SaveRegion(int x, int y, int width, int height, string path)
|
||||
{
|
||||
using var bmp = CaptureRegion(x, y, width, height);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path))!);
|
||||
bmp.Save(path, System.Drawing.Imaging.ImageFormat.Png);
|
||||
return Path.GetFullPath(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user