using System;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using AutoFocusTool.Imaging;
namespace AutoFocusTool
{
///
/// BMP→小PNG 诊断转换器(不依赖相机/串口,纯 System.Drawing)。
/// 把相机 15MB 的 24bpp BMP 缩成小 PNG 供肉眼/AI 视觉检查,并:
/// - 跑 WellDetector 把"检测器眼里的圆"画上去(绿圆+圆心十字)
/// - 画画面中心十字(白)+ 居中容差带(±12% 高度,黄色虚拟区,用上下两条线表示)
/// - stdout 打印检测数值(圆心/半径/偏移/完整/拒绝原因)
/// 用法:ToPng.exe <输入.bmp 或 目录> [缩放宽度=900] [--nodetect]
/// 注意:磁盘上的 BMP 已被 FlipY 翻正(人眼朝向),WellDetector 在此朝向上跑,
/// 得到的圆位置即人眼所见;Y偏移符号与引擎内部(未翻转)相反,但位置/半径一致。
///
internal class ToPng
{
static void Main(string[] args)
{
Console.OutputEncoding = System.Text.Encoding.UTF8;
if (args.Length == 0) { Console.WriteLine("用法: ToPng [宽度=900] [--nodetect]"); return; }
string input = args[0];
int targetW = 900;
bool detect = true;
for (int i = 1; i < args.Length; i++)
{
if (args[i] == "--nodetect") detect = false;
else if (int.TryParse(args[i], out int w)) targetW = w;
}
if (Directory.Exists(input))
{
foreach (var f in Directory.GetFiles(input, "*.bmp"))
Convert(f, targetW, detect);
}
else if (File.Exists(input))
{
Convert(input, targetW, detect);
}
else Console.WriteLine($"找不到: {input}");
}
static void Convert(string bmpPath, int targetW, bool detect)
{
try
{
using var src = new Bitmap(bmpPath);
int W = src.Width, H = src.Height;
string outPath = Path.ChangeExtension(bmpPath, ".png");
if (bmpPath.EndsWith("_v.png", StringComparison.OrdinalIgnoreCase)) return; // 跳过已生成的
// 取 BGR24 原始字节供 WellDetector
WellCircle c = null;
if (detect)
{
byte[] bgr = ToBgr24(src, W, H);
c = WellDetector.Detect(bgr, W, H);
Console.WriteLine($"{Path.GetFileName(bmpPath)}: " +
(c.Found
? $"圆心({c.Cx:F0},{c.Cy:F0}) R={c.Radius:F0} 偏移X={c.OffsetXPct:F1}% Y={c.OffsetYPct:F1}% 完整={c.Complete} 面积={c.AreaPct:F1}% aspect={c.Aspect:F2} boxFill={c.BoxFill:F2} rConsist={c.RConsist:F2}"
: $"未检出圆 [{c.RejectReason}] 面积={c.AreaPct:F1}%"));
}
// 叠加标注(在全分辨率上画,再缩放,线条清晰)
using (var g = Graphics.FromImage(src))
{
// 画面中心十字(白)
var cxp = W / 2f; var cyp = H / 2f;
using var wp = new Pen(Color.White, 3);
g.DrawLine(wp, cxp - 40, cyp, cxp + 40, cyp);
g.DrawLine(wp, cxp, cyp - 40, cxp, cyp + 40);
// 居中容差带(±12% 高度)黄色横线
using var yp = new Pen(Color.Yellow, 2) { DashStyle = System.Drawing.Drawing2D.DashStyle.Dash };
float tol = 0.12f * H;
g.DrawLine(yp, 0, cyp - tol, W, cyp - tol);
g.DrawLine(yp, 0, cyp + tol, W, cyp + tol);
if (c != null && c.Found)
{
using var gp = new Pen(Color.Lime, 4);
g.DrawEllipse(gp, (float)(c.Cx - c.Radius), (float)(c.Cy - c.Radius),
(float)(c.Radius * 2), (float)(c.Radius * 2));
// 检测圆心(红十字)
using var rp = new Pen(Color.Red, 3);
g.DrawLine(rp, (float)c.Cx - 30, (float)c.Cy, (float)c.Cx + 30, (float)c.Cy);
g.DrawLine(rp, (float)c.Cx, (float)c.Cy - 30, (float)c.Cx, (float)c.Cy + 30);
}
}
int targetH = (int)((long)targetW * H / W);
using var dst = new Bitmap(targetW, targetH);
using (var g = Graphics.FromImage(dst))
{
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.DrawImage(src, 0, 0, targetW, targetH);
}
dst.Save(outPath, ImageFormat.Png);
Console.WriteLine($" → {Path.GetFileName(outPath)} ({targetW}x{targetH})");
}
catch (Exception ex) { Console.WriteLine($"{bmpPath} 转换失败: {ex.Message}"); }
}
/// System.Drawing.Bitmap → 24bpp BGR 行优先字节(WellDetector 输入格式)。
static byte[] ToBgr24(Bitmap bmp, int W, int H)
{
var clone = bmp.PixelFormat == PixelFormat.Format24bppRgb ? bmp
: bmp.Clone(new Rectangle(0, 0, W, H), PixelFormat.Format24bppRgb);
var data = clone.LockBits(new Rectangle(0, 0, W, H), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
byte[] buf = new byte[W * H * 3];
int rowBytes = W * 3;
for (int y = 0; y < H; y++)
Marshal.Copy(data.Scan0 + y * data.Stride, buf, y * rowBytes, rowBytes);
clone.UnlockBits(data);
if (!ReferenceEquals(clone, bmp)) clone.Dispose();
return buf;
}
}
}