first commit

This commit is contained in:
hero920103 committed 2026-10-24 13:02:19 +08:00
commit 761b95080f
404 files changed
+312608

No files matched your search

+85
View File
@@ -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);
}
}
}
}
+75
View File
@@ -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();
}
}
}
+66
View File
@@ -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);
}
}
}