using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace anjianjingling.Vision
{
///
/// 找色:LockBits 锁内存逐像素扫描,支持容差。
///
public sealed class ColorFinder
{
/// 解析 "RRGGBB" 或 "#RRGGBB" 颜色。
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);
}
///
/// 在 bmp 的指定区域内找颜色,返回第一个匹配点的 bmp 内坐标;未找到返回 (-1,-1)。
///
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);
}
/// LockBits 包装,按 Format24bpp/32bpp 通用读取 BGR。
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);
}
}
}
}