HalconWindow.xaml.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Threading.Tasks;
  5. using System.Windows;
  6. using System.Windows.Controls;
  7. using System.Windows.Input;
  8. using System.Windows.Media;
  9. using HalconDotNet;
  10. namespace Plugins.Halcon.Views
  11. {
  12. /// <summary>HALCON 画面框的显示结果类型(OK/NG/无)。</summary>
  13. public enum HalconResultType { Null, OK, NG }
  14. /// <summary>双击放大回调(与 VppWindow.MouseDoubleDelegate 对等)。</summary>
  15. public delegate void HalconMouseDoubleDelegate(HalconWindow window, int number);
  16. /// <summary>
  17. /// 单个 HALCON 画面框(镜像 VppWindow/VmWindow 的角色):标题、状态圆点、图像区。
  18. /// 图像区用 HALCON 原生 WPF 控件 <see cref="HSmartWindowControlWPF"/>(内置缩放/平移):
  19. /// - RefreshImage(image, overlay):经典模式——单底图 + 单叠加(主页画面框用);
  20. /// - ShowLayers(layers):图层模式——底图 + 多彩色叠加(编辑器大显示窗按图层筛选渲染);
  21. /// - 鼠标移动实时读取 行/列/灰度;支持拖入图片文件、右键保存图片、适应窗体;
  22. /// - 右键菜单可交互绘制 矩形/旋转矩形/圆形 ROI(HDrawingObject),结果存 <see cref="LastRoi"/>。
  23. /// 所有 HALCON 调用集中在 <see cref="HalconRuntime"/> 或本类;显示操作统一切到 UI 线程。
  24. /// 所有权规则:外部传入的 HObject 只引用不释放;由本窗体从 GenImage 转换出的副本(owned)在本窗体负责释放。
  25. /// </summary>
  26. public partial class HalconWindow : UserControl
  27. {
  28. /// <summary>双击放大/还原回调(由 UCDispHalcon 挂接)。</summary>
  29. public HalconMouseDoubleDelegate MouseDouble { get; set; }
  30. /// <summary>画面框编号(1 起)。</summary>
  31. public int Number { get; }
  32. // HALCON 显示面与右键菜单改为 code-behind 防御式创建(见 InitHalconSurface):
  33. // 机器缺 halcon.dll 等原生运行库时,XAML 声明式创建 + WPF 渲染期会抛出
  34. // DllNotFoundException(首次可被吞掉,二次渲染异常直接致命闪退)。
  35. private HSmartWindowControlWPF MyHwindow;
  36. private ContextMenu MyMenu;
  37. // —— 经典模式状态(单图 + 单叠加) ——
  38. private HObject _image;
  39. private bool _imageOwned;
  40. private HObject _overlay;
  41. private bool _overlayOwned;
  42. private int _imgW, _imgH;
  43. private bool _hasImage;
  44. // —— 图层模式状态(底图 + 多叠加) ——
  45. private readonly List<HObject> _layerOwnedCopies = new List<HObject>(); // 本窗体自有的转换副本(释放责任在本窗体)
  46. private HObject _layerBaseImage;
  47. private bool _layerBaseOwned;
  48. private readonly List<KeyValuePair<HObject, string>> _layerOverlays = new List<KeyValuePair<HObject, string>>();
  49. private int _layerImgW, _layerImgH;
  50. private bool _layerMode;
  51. /// <summary>图层缺省配色(按叠加顺序取色,可用 LayerItem.Color 覆盖)。</summary>
  52. private static readonly string[] LayerPalette = { "red", "green", "blue", "yellow", "cyan", "magenta", "orange" };
  53. // ROI 交互绘制
  54. private HDrawingObject _drawing;
  55. private bool _drawingBusy;
  56. /// <summary>最近一次交互绘制得到的 ROI(供节点读取;未绘制时为 null)。</summary>
  57. public Models.HalconRoi LastRoi { get; private set; }
  58. public HalconWindow() : this(1) { }
  59. public HalconWindow(int number)
  60. {
  61. Number = number;
  62. InitializeComponent();
  63. CaptionText.Text = $"画面 {number}";
  64. InitHalconSurface();
  65. SizeChanged += (s, e) => { if (e.NewSize.Width > 1) { FitCurrent(); } };
  66. }
  67. /// <summary>
  68. /// 防御式创建 HALCON 显示面:先做一次原生调用探测(缺 halcon.dll 等运行库时立即失败),
  69. /// 可用才创建 HSmartWindowControlWPF 并挂事件/右键菜单;不可用则显示错误占位。
  70. /// 保证 Halcon 流程编辑器等宿主在无原生运行库的机器上照常打开,不闪退。
  71. /// </summary>
  72. private void InitHalconSurface()
  73. {
  74. if (!TryProbeHalconNative(out var probeError))
  75. {
  76. NativeErrorText.Text = "HALCON 原生运行库不可用\n\n" +
  77. $"原因:{probeError}\n\n" +
  78. "请检查 Runtime\\dll\\x64 下的 halcon.dll 及配套运行库是否完整," +
  79. "或安装 HALCON 运行时。流程画布与属性编辑不受影响。";
  80. NativeErrorText.Visibility = Visibility.Visible;
  81. return;
  82. }
  83. try
  84. {
  85. var hw = new HSmartWindowControlWPF
  86. {
  87. HDoubleClickToFitContent = false,
  88. AllowDrop = true,
  89. Cursor = Cursors.Cross
  90. };
  91. hw.HMouseMove += MyHwindow_HMouseMove;
  92. hw.MouseLeave += MyHwindow_MouseLeave;
  93. hw.MouseRightButtonDown += MyHwindow_MouseRightButtonDown;
  94. hw.Drop += MyHwindow_Drop;
  95. hw.DragEnter += MyHwindow_DragEnter;
  96. MyMenu = BuildContextMenu();
  97. hw.ContextMenu = MyMenu;
  98. MyHwindow = hw;
  99. HwindowHost.Content = hw;
  100. }
  101. catch (Exception ex)
  102. {
  103. MyHwindow = null;
  104. NativeErrorText.Text = $"HALCON 显示控件创建失败:{ex.Message}";
  105. NativeErrorText.Visibility = Visibility.Visible;
  106. }
  107. }
  108. /// <summary>原生运行库探测:一次无副作用的 HALCON 调用,失败=缺原生 DLL。</summary>
  109. private static bool TryProbeHalconNative(out string error)
  110. {
  111. try
  112. {
  113. HOperatorSet.GetSystem("version", out HTuple version);
  114. error = null;
  115. return true;
  116. }
  117. catch (Exception ex)
  118. {
  119. error = ex.Message;
  120. return false;
  121. }
  122. }
  123. /// <summary>构建显示区右键菜单(缩放/适应/保存/ROI 绘制)。</summary>
  124. private ContextMenu BuildContextMenu()
  125. {
  126. var menu = new ContextMenu();
  127. void AddHandler(string header, RoutedEventHandler onClick)
  128. {
  129. var mi = new MenuItem { Header = header };
  130. mi.Click += onClick;
  131. menu.Items.Add(mi);
  132. }
  133. AddHandler("放大", MenuZoomIn_Click);
  134. AddHandler("缩小", MenuZoomOut_Click);
  135. AddHandler("适应窗体", MenuFit_Click);
  136. AddHandler("保存图片", MenuSave_Click);
  137. menu.Items.Add(new Separator());
  138. AddHandler("绘制矩形ROI", MenuRoiRect1_Click);
  139. AddHandler("绘制旋转矩形ROI", MenuRoiRect2_Click);
  140. AddHandler("绘制圆形ROI", MenuRoiCircle_Click);
  141. AddHandler("清除ROI", MenuRoiClear_Click);
  142. return menu;
  143. }
  144. /// <summary>HALCON 窗口句柄(控件初始化后才有效;用前判空)。</summary>
  145. private HWindow Hwn
  146. {
  147. get { try { return MyHwindow?.HalconWindow; } catch { return null; } }
  148. }
  149. #region 引擎/显示插件调用的公共接口(与 VmWindow 对齐)
  150. public void SetCaption(string caption)
  151. {
  152. if (!Dispatcher.CheckAccess()) { Dispatcher.Invoke(() => SetCaption(caption)); return; }
  153. CaptionText.Text = string.IsNullOrWhiteSpace(caption) ? $"画面 {Number}" : caption;
  154. }
  155. public void SetStatusDotVisible(bool visible)
  156. {
  157. if (!Dispatcher.CheckAccess()) { Dispatcher.Invoke(() => SetStatusDotVisible(visible)); return; }
  158. StatusDot.Visibility = visible ? Visibility.Visible : Visibility.Collapsed;
  159. }
  160. /// <summary>按 OK/NG 刷状态圆点颜色(并置为可见)。</summary>
  161. public void SetLable(HalconResultType type)
  162. {
  163. if (!Dispatcher.CheckAccess()) { Dispatcher.Invoke(() => SetLable(type)); return; }
  164. StatusDot.Visibility = Visibility.Visible;
  165. switch (type)
  166. {
  167. case HalconResultType.OK: StatusDot.Fill = new SolidColorBrush(Color.FromRgb(0x4C, 0xAF, 0x50)); break;
  168. case HalconResultType.NG: StatusDot.Fill = new SolidColorBrush(Color.FromRgb(0xF4, 0x43, 0x36)); break;
  169. default: StatusDot.Fill = new SolidColorBrush(Color.FromRgb(0x88, 0x88, 0x88)); break;
  170. }
  171. }
  172. /// <summary>
  173. /// 【经典模式】刷新显示:image=图像(HObject/GenImage/Bitmap),overlay=可选叠加对象(区域/XLD,红色边界绘制)。
  174. /// 从流程传入的 HObject 只引用不释放;从 GenImage 转换的副本由本窗体负责释放。
  175. /// </summary>
  176. public void RefreshImage(object image, object overlay = null)
  177. {
  178. if (!Dispatcher.CheckAccess()) { Dispatcher.Invoke(() => RefreshImage(image, overlay)); return; }
  179. // 经典调用接管显示:退出图层模式
  180. ExitLayerMode();
  181. var hw = Hwn;
  182. if (hw == null) return;
  183. // image 为 null:仅更新叠加层(保留当前底图),供只产出区域的节点使用
  184. if (image == null)
  185. {
  186. if (overlay == null) return;
  187. try
  188. {
  189. bool ovOwned = !(overlay is HObject);
  190. var hover = HalconRuntime.ToHObject(overlay);
  191. if (_overlayOwned) HalconRuntime.Dispose(_overlay);
  192. _overlay = hover; _overlayOwned = ovOwned;
  193. Redraw();
  194. }
  195. catch { }
  196. return;
  197. }
  198. try
  199. {
  200. bool imgOwned = !(image is HObject);
  201. var himg = HalconRuntime.ToHObject(image);
  202. HObject hover = null;
  203. bool overOwned = false;
  204. if (overlay != null)
  205. {
  206. overOwned = !(overlay is HObject);
  207. try { hover = HalconRuntime.ToHObject(overlay); } catch { hover = null; }
  208. }
  209. HOperatorSet.GetImageSize(himg, out HTuple wT, out HTuple hT);
  210. int w = wT.I, h = hT.I;
  211. // 释放上一帧「本窗体自有」的副本(流程对象不动)
  212. if (_imageOwned) HalconRuntime.Dispose(_image);
  213. if (_overlayOwned) HalconRuntime.Dispose(_overlay);
  214. _image = himg; _imageOwned = imgOwned;
  215. _overlay = hover; _overlayOwned = overOwned;
  216. _hasImage = true;
  217. bool needFit = w != _imgW || h != _imgH;
  218. _imgW = w; _imgH = h;
  219. if (needFit) FitPart();
  220. Redraw();
  221. }
  222. catch { /* 显示失败不影响流程 */ }
  223. }
  224. /// <summary>清空画面显示。</summary>
  225. public void ClearImage()
  226. {
  227. if (!Dispatcher.CheckAccess()) { Dispatcher.Invoke(new Action(ClearImage)); return; }
  228. try
  229. {
  230. ExitLayerMode();
  231. if (_imageOwned) HalconRuntime.Dispose(_image);
  232. if (_overlayOwned) HalconRuntime.Dispose(_overlay);
  233. _image = null; _overlay = null; _hasImage = false; _imgW = _imgH = 0;
  234. Hwn?.ClearWindow();
  235. }
  236. catch { }
  237. }
  238. #endregion
  239. #region 图层显示(编辑器大显示窗 / 图层筛选)
  240. /// <summary>
  241. /// 【图层模式】按图层集渲染:首个 Image 图层作底图,其余按 LayerItem.Color(缺省调色板)叠加。
  242. /// 图层对象的「注册表自有」部分只引用不释放;本窗体从 GenImage 转换出的副本在下次刷新/清理时释放。
  243. /// </summary>
  244. public void ShowLayers(IReadOnlyList<TeamAAS.Vision.LayerItem> layers)
  245. {
  246. if (!Dispatcher.CheckAccess()) { Dispatcher.Invoke(() => ShowLayers(layers)); return; }
  247. ExitLayerMode();
  248. var hw = Hwn;
  249. if (hw == null || layers == null || layers.Count == 0) return;
  250. try
  251. {
  252. foreach (var layer in layers)
  253. {
  254. if (layer == null || layer.Content == null) continue;
  255. if (layer.Kind == TeamAAS.Vision.LayerKind.Image)
  256. {
  257. bool owned = !(layer.Content is HObject);
  258. var himg = HalconRuntime.ToHObject(layer.Content);
  259. if (owned) _layerOwnedCopies.Add(himg);
  260. if (_layerBaseOwned && _layerBaseImage != null) HalconRuntime.Dispose(_layerBaseImage);
  261. _layerBaseImage = himg;
  262. _layerBaseOwned = owned;
  263. }
  264. else
  265. {
  266. bool owned = !(layer.Content is HObject);
  267. var hobj = HalconRuntime.ToHObject(layer.Content);
  268. if (owned) _layerOwnedCopies.Add(hobj);
  269. var color = string.IsNullOrWhiteSpace(layer.Color)
  270. ? LayerPalette[_layerOverlays.Count % LayerPalette.Length]
  271. : layer.Color;
  272. _layerOverlays.Add(new KeyValuePair<HObject, string>(hobj, color));
  273. }
  274. }
  275. if (_layerBaseImage != null && _layerBaseImage.IsInitialized())
  276. {
  277. HOperatorSet.GetImageSize(_layerBaseImage, out HTuple wT, out HTuple hT);
  278. int w = wT.I, h = hT.I;
  279. bool needFit = w != _layerImgW || h != _layerImgH;
  280. _layerImgW = w; _layerImgH = h;
  281. if (needFit) FitPart();
  282. }
  283. _layerMode = true;
  284. Redraw();
  285. }
  286. catch
  287. {
  288. ExitLayerMode();
  289. }
  290. }
  291. /// <summary>退出图层模式:释放本窗体自有的图层副本,恢复经典状态。</summary>
  292. private void ExitLayerMode()
  293. {
  294. _layerMode = false;
  295. foreach (var copy in _layerOwnedCopies)
  296. HalconRuntime.Dispose(copy);
  297. _layerOwnedCopies.Clear();
  298. if (_layerBaseOwned) HalconRuntime.Dispose(_layerBaseImage);
  299. _layerBaseImage = null; _layerBaseOwned = false;
  300. _layerOverlays.Clear();
  301. _layerImgW = _layerImgH = 0;
  302. }
  303. /// <summary>图层模式重绘:底图 + 多彩叠加。</summary>
  304. private void RedrawLayers()
  305. {
  306. var hw = Hwn;
  307. if (hw == null) return;
  308. try
  309. {
  310. HOperatorSet.SetSystem("flush_graphic", "false");
  311. hw.ClearWindow();
  312. bool hasBase = _layerBaseImage != null && _layerBaseImage.IsInitialized();
  313. HObject dispBase = hasBase ? ToDisplayImage(_layerBaseImage) : null; // 位深归一化显示
  314. if (hasBase) { hw.SetColor("default"); hw.SetDraw("fill"); hw.DispObj(dispBase); }
  315. foreach (var kv in _layerOverlays)
  316. {
  317. if (kv.Key == null || !kv.Key.IsInitialized()) continue;
  318. hw.SetColor(string.IsNullOrWhiteSpace(kv.Value) ? "red" : kv.Value);
  319. hw.SetDraw("margin");
  320. hw.SetLineWidth(2.0);
  321. hw.DispObj(kv.Key);
  322. }
  323. HOperatorSet.SetSystem("flush_graphic", "true");
  324. if (dispBase != null && !ReferenceEquals(dispBase, _layerBaseImage)) dispBase.Dispose();
  325. }
  326. catch { }
  327. }
  328. #endregion
  329. #region 显示绘制
  330. /// <summary>按当前模式重绘(保持当前缩放/平移的 part)。</summary>
  331. private void Redraw()
  332. {
  333. if (_layerMode) { RedrawLayers(); return; }
  334. var hw = Hwn;
  335. if (hw == null) return;
  336. bool hasImg = _image != null && _image.IsInitialized();
  337. bool hasOv = _overlay != null && _overlay.IsInitialized();
  338. if (!hasImg && !hasOv) return;
  339. try
  340. {
  341. HOperatorSet.SetSystem("flush_graphic", "false");
  342. hw.ClearWindow();
  343. HObject dispImg = hasImg ? ToDisplayImage(_image) : null; // 位深归一化显示
  344. if (hasImg) { hw.SetDraw("fill"); hw.DispObj(dispImg); }
  345. if (hasOv)
  346. {
  347. hw.SetColor("red");
  348. hw.SetDraw("margin");
  349. hw.SetLineWidth(2.0);
  350. hw.DispObj(_overlay);
  351. }
  352. HOperatorSet.SetSystem("flush_graphic", "true");
  353. if (dispImg != null && !ReferenceEquals(dispImg, _image)) dispImg.Dispose();
  354. }
  355. catch { }
  356. }
  357. /// <summary>按当前模式的图像尺寸做等比适配。</summary>
  358. private void FitCurrent()
  359. {
  360. if (_layerMode) { FitImage(_layerImgW, _layerImgH); return; }
  361. FitImage(_imgW, _imgH);
  362. }
  363. /// <summary>按图像尺寸与控件尺寸做等比适配(set_part),不拉伸变形。</summary>
  364. private void FitPart()
  365. {
  366. FitImage(_layerMode ? _layerImgW : _imgW, _layerMode ? _layerImgH : _imgH);
  367. }
  368. // 首次显示时控件可能尚未完成布局(ActualWidth=0),此时 Fit 只能挂起,
  369. // 等布局完成补做 —— 否则显示区域(part)停在默认值,大图只露出某个角落,肉眼上"全黑"
  370. private bool _pendingFit;
  371. private void FitImage(int imgW, int imgH)
  372. {
  373. var hw = Hwn;
  374. if (hw == null || imgW <= 0 || imgH <= 0) return;
  375. double W = MyHwindow?.ActualWidth ?? 0, H = MyHwindow?.ActualHeight ?? 0;
  376. if (W <= 1 || H <= 1)
  377. {
  378. if (!_pendingFit)
  379. {
  380. _pendingFit = true;
  381. MyHwindow.LayoutUpdated += OnLayoutPendingFit;
  382. }
  383. return;
  384. }
  385. _pendingFit = false;
  386. try
  387. {
  388. double winAspect = W / H;
  389. double imgAspect = (double)imgW / imgH;
  390. double partW, partH;
  391. if (imgAspect > winAspect) { partW = imgW; partH = imgW / winAspect; }
  392. else { partH = imgH; partW = imgH * winAspect; }
  393. double r1 = -(partH - imgH) / 2.0;
  394. double c1 = -(partW - imgW) / 2.0;
  395. hw.SetPart(r1, c1, r1 + partH - 1, c1 + partW - 1);
  396. }
  397. catch { }
  398. }
  399. /// <summary>布局完成后补做挂起的适配(回到正确显示区域)。</summary>
  400. private void OnLayoutPendingFit(object sender, EventArgs e)
  401. {
  402. if (!_pendingFit) { MyHwindow.LayoutUpdated -= OnLayoutPendingFit; return; }
  403. int imgW = _layerMode ? _layerImgW : _imgW;
  404. int imgH = _layerMode ? _layerImgH : _imgH;
  405. if (imgW <= 0 || MyHwindow == null || MyHwindow.ActualWidth <= 1) return;
  406. _pendingFit = false;
  407. MyHwindow.LayoutUpdated -= OnLayoutPendingFit;
  408. FitCurrent();
  409. Redraw();
  410. }
  411. /// <summary>
  412. /// 显示用规范化:uint2/int2/int4/real 图像按实际灰度范围拉伸到 0-255 再转 byte。
  413. /// 背景:HALCON 显示 uint2 时灰度 100 只相当于位深的 0.15%,肉眼上"整幅是黑的",
  414. /// 而灰度读数(直接取像素值)完全正常 —— 16 位相机图像黑屏即此原因。byte 原样返回。
  415. /// </summary>
  416. private static HObject ToDisplayImage(HObject img)
  417. {
  418. try
  419. {
  420. HOperatorSet.GetImageType(img, out HTuple t);
  421. var type = t.S;
  422. if (type == "byte" || type == "complex" || type == "vector_field") return img;
  423. HOperatorSet.MinMaxGray(img, img, 0, out HTuple mn, out HTuple mx, out HTuple _);
  424. double min = mn.D, range = mx.D - min;
  425. if (range < 1) range = 1;
  426. double scale = 255.0 / range;
  427. HOperatorSet.ScaleImage(img, out HObject scaled, scale, -min * scale);
  428. HOperatorSet.ConvertImageType(scaled, out HObject byteImg, "byte");
  429. scaled.Dispose();
  430. return byteImg;
  431. }
  432. catch { return img; }
  433. }
  434. #endregion
  435. #region 鼠标读取 / 拖放 / 双击
  436. private void MyHwindow_HMouseMove(object sender, HSmartWindowControlWPF.HMouseEventArgsWPF e)
  437. {
  438. try
  439. {
  440. int row = (int)e.Row, col = (int)e.Column;
  441. string txt = $"行:{row} 列:{col}";
  442. var img = _layerMode ? _layerBaseImage : _image;
  443. int imgH = _layerMode ? _layerImgH : _imgH;
  444. int imgW = _layerMode ? _layerImgW : _imgW;
  445. if (img != null && img.IsInitialized() &&
  446. row >= 0 && col >= 0 && row < imgH && col < imgW)
  447. {
  448. try
  449. {
  450. HOperatorSet.GetGrayval(img, row, col, out HTuple gray);
  451. txt += $" 灰度:{gray.D:F0}";
  452. }
  453. catch { }
  454. }
  455. CoordText.Text = txt;
  456. CoordText.Visibility = Visibility.Visible;
  457. }
  458. catch { }
  459. }
  460. private void MyHwindow_MouseLeave(object sender, MouseEventArgs e) => CoordText.Visibility = Visibility.Collapsed;
  461. private void MyHwindow_DragEnter(object sender, DragEventArgs e)
  462. => e.Effects = e.Data.GetDataPresent(DataFormats.FileDrop) ? DragDropEffects.Copy : DragDropEffects.None;
  463. private void MyHwindow_Drop(object sender, DragEventArgs e)
  464. {
  465. try
  466. {
  467. if (e.Data.GetData(DataFormats.FileDrop) is string[] files && files.Length > 0)
  468. {
  469. HOperatorSet.ReadImage(out HObject img, files[0]);
  470. RefreshImage(img); // 拖入的图像由本窗体持有
  471. _imageOwned = true;
  472. }
  473. }
  474. catch { }
  475. }
  476. private void UserControl_MouseDoubleClick(object sender, MouseButtonEventArgs e)
  477. => MouseDouble?.Invoke(this, Number);
  478. // 绘制 ROI 时右键用于「确认」,此时抑制右键菜单;非绘制态恢复菜单
  479. private void MyHwindow_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
  480. {
  481. if (_drawingBusy) MyHwindow.ContextMenu = null;
  482. else MyHwindow.ContextMenu = MyMenu;
  483. }
  484. #endregion
  485. #region 右键菜单:适应 / 保存
  486. private void MenuFit_Click(object sender, RoutedEventArgs e) { FitCurrent(); Redraw(); }
  487. private void MenuZoomIn_Click(object sender, RoutedEventArgs e) => ZoomBy(0.8); // part 变小 = 放大
  488. private void MenuZoomOut_Click(object sender, RoutedEventArgs e) => ZoomBy(1.25); // part 变大 = 缩小
  489. /// <summary>
  490. /// 以画面中心为基准缩放:factor&lt;1 放大、&gt;1 缩小。读写窗口 part(GetPart/SetPart),
  491. /// 与 HSmartWindowControlWPF 内置的鼠标滚轮缩放共用同一 part 状态,不冲突。
  492. /// </summary>
  493. public void ZoomBy(double factor)
  494. {
  495. var hw = Hwn;
  496. int refH = _layerMode ? _layerImgH : _imgH;
  497. int refW = _layerMode ? _layerImgW : _imgW;
  498. bool hasAny = _layerMode ? (_layerBaseImage != null && _layerBaseImage.IsInitialized()) : _hasImage;
  499. if (hw == null || !hasAny) return;
  500. try
  501. {
  502. hw.GetPart(out int r1, out int c1, out int r2, out int c2);
  503. double h = r2 - r1, w = c2 - c1;
  504. double nh = h * factor, nw = w * factor;
  505. if (nh < 4 || nw < 4) return; // 放大上限保护
  506. if (nh > refH * 20 || nw > refW * 20) return; // 缩小上限保护
  507. double cr = (r1 + r2) / 2.0, cc = (c1 + c2) / 2.0;
  508. hw.SetPart(cr - nh / 2, cc - nw / 2, cr + nh / 2, cc + nw / 2);
  509. Redraw();
  510. }
  511. catch { }
  512. }
  513. private void MenuSave_Click(object sender, RoutedEventArgs e)
  514. {
  515. var img = _layerMode ? _layerBaseImage : _image;
  516. if (img == null || !img.IsInitialized()) return;
  517. var sfd = new Microsoft.Win32.SaveFileDialog
  518. {
  519. Filter = "PNG 图像|*.png|BMP 图像|*.bmp|TIFF 图像|*.tif|JPEG 图像|*.jpg",
  520. FileName = "halcon_image"
  521. };
  522. if (sfd.ShowDialog() != true) return;
  523. try
  524. {
  525. string ext = Path.GetExtension(sfd.FileName).ToLowerInvariant();
  526. string fmt = ext == ".bmp" ? "bmp" : ext == ".tif" || ext == ".tiff" ? "tiff" : ext == ".jpg" || ext == ".jpeg" ? "jpeg" : "png";
  527. string noExt = Path.Combine(Path.GetDirectoryName(sfd.FileName) ?? "", Path.GetFileNameWithoutExtension(sfd.FileName));
  528. HOperatorSet.WriteImage(img, fmt, 0, noExt);
  529. }
  530. catch (Exception ex) { MessageBox.Show($"保存失败:{ex.Message}"); }
  531. }
  532. #endregion
  533. #region 交互 ROI 绘制(HDrawingObject:左键拖动调整,右键确认)
  534. private void MenuRoiRect1_Click(object sender, RoutedEventArgs e) => _ = DrawAsync(Models.HalconRoiType.Rectangle1);
  535. private void MenuRoiRect2_Click(object sender, RoutedEventArgs e) => _ = DrawAsync(Models.HalconRoiType.Rectangle2);
  536. private void MenuRoiCircle_Click(object sender, RoutedEventArgs e) => _ = DrawAsync(Models.HalconRoiType.Circle);
  537. private void MenuRoiClear_Click(object sender, RoutedEventArgs e)
  538. {
  539. LastRoi = null;
  540. DetachDrawing();
  541. Redraw();
  542. }
  543. private async Task DrawAsync(Models.HalconRoiType type)
  544. {
  545. var hw = Hwn;
  546. var baseImg = _layerMode ? _layerBaseImage : _image;
  547. if (_drawingBusy || hw == null || baseImg == null || !baseImg.IsInitialized()) return;
  548. _drawingBusy = true;
  549. MyHwindow.ContextMenu = null;
  550. try
  551. {
  552. DetachDrawing();
  553. _drawing = new HDrawingObject();
  554. double h = _layerMode ? _layerImgH : _imgH;
  555. double w = _layerMode ? _layerImgW : _imgW;
  556. switch (type)
  557. {
  558. case Models.HalconRoiType.Rectangle1:
  559. _drawing.CreateDrawingObjectRectangle1(h * 0.25, w * 0.25, h * 0.75, w * 0.75);
  560. break;
  561. case Models.HalconRoiType.Rectangle2:
  562. _drawing.CreateDrawingObjectRectangle2(h * 0.5, w * 0.5, 0, Math.Min(w, h) * 0.25, Math.Min(w, h) * 0.15);
  563. break;
  564. case Models.HalconRoiType.Circle:
  565. _drawing.CreateDrawingObjectCircle(h * 0.5, w * 0.5, Math.Min(w, h) * 0.25);
  566. break;
  567. }
  568. _drawing.SetDrawingObjectParams("line_width", 2.5);
  569. _drawing.SetDrawingObjectParams("color", "green");
  570. hw.AttachDrawingObjectToWindow(_drawing);
  571. // 后台等待右键确认(左键用于拖动 ROI 的控制点)
  572. await Task.Run(() =>
  573. {
  574. int button = 0;
  575. while (true)
  576. {
  577. hw.GetMbutton(out int r, out int c, out button);
  578. if (button == 1) { while (button == 1) hw.GetMposition(out r, out c, out button); }
  579. else break;
  580. }
  581. });
  582. LastRoi = ReadRoi(type);
  583. }
  584. catch { }
  585. finally
  586. {
  587. DetachDrawing();
  588. _drawingBusy = false;
  589. MyHwindow.ContextMenu = MyMenu;
  590. Redraw();
  591. }
  592. }
  593. private Models.HalconRoi ReadRoi(Models.HalconRoiType type)
  594. {
  595. if (_drawing == null) return null;
  596. var roi = new Models.HalconRoi { Type = type };
  597. switch (type)
  598. {
  599. case Models.HalconRoiType.Rectangle1:
  600. roi.Row1 = _drawing.GetDrawingObjectParams("row1").D;
  601. roi.Col1 = _drawing.GetDrawingObjectParams("column1").D;
  602. roi.Row2 = _drawing.GetDrawingObjectParams("row2").D;
  603. roi.Col2 = _drawing.GetDrawingObjectParams("column2").D;
  604. break;
  605. case Models.HalconRoiType.Rectangle2:
  606. roi.Row = _drawing.GetDrawingObjectParams("row").D;
  607. roi.Col = _drawing.GetDrawingObjectParams("column").D;
  608. roi.Phi = _drawing.GetDrawingObjectParams("phi").D;
  609. roi.Length1 = _drawing.GetDrawingObjectParams("length1").D;
  610. roi.Length2 = _drawing.GetDrawingObjectParams("length2").D;
  611. break;
  612. case Models.HalconRoiType.Circle:
  613. roi.Row = _drawing.GetDrawingObjectParams("row").D;
  614. roi.Col = _drawing.GetDrawingObjectParams("column").D;
  615. roi.Radius = _drawing.GetDrawingObjectParams("radius").D;
  616. break;
  617. }
  618. return roi;
  619. }
  620. private void DetachDrawing()
  621. {
  622. try
  623. {
  624. if (_drawing != null)
  625. {
  626. Hwn?.DetachDrawingObjectFromWindow(_drawing);
  627. _drawing.Dispose();
  628. }
  629. }
  630. catch { }
  631. finally { _drawing = null; }
  632. }
  633. #endregion
  634. }
  635. }