using MainShell.EventArgsFolder; using MainShell.Log; using MainShell.Vision; using MwFramework.Device.Model; using MwFramework.Controls.ControlCanvas.DrawingControl; using Stylet; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Media.Imaging; using AppCameraType = MainShell.Common.CameraType; using System.Windows.Media; namespace MainShell.Common.Display.ViewModel { public class CameraViewModel : Screen, IHandle, IHandle { private readonly IEventAggregator _eventAggregator; public AppCameraType CameraType { get; } private WriteableBitmap currentImage; private WriteableBitmap _displayImage; public WriteableBitmap DisplayImage { get { return _displayImage; } set { SetAndNotify(ref _displayImage, value); } } private ObservableCollection _shapes = new ObservableCollection(); public ObservableCollection Shapes { get { return _shapes; } set { if (_shapes != value) { _shapes = value; OnPropertyChanged(nameof(Shapes)); } } } /// /// 根据 CameraData 更新并返回 WriteableBitmap。 /// 优化要点: /// - 重用已有 WriteableBitmap(当尺寸与像素格式相同时)以减少分配。 /// - 校验参数与缓冲区长度。 /// - 捕获并记录异常,失败时返回 null(不抛出)。 /// public WriteableBitmap UpdateBitmap(CameraData cameraData) { if (cameraData == null) { "图片数据为空".LogSysError(); return null; } try { int width = (int)cameraData.Width; int height = (int)cameraData.Height; int bitCnt = cameraData.Bit; byte[] buffer = cameraData.BufferData; if (width <= 0 || height <= 0) { $"CameraViewModel.UpdateBitmap: invalid size {width}x{height}".LogSysError(); return null; } // 选择像素格式 System.Windows.Media.PixelFormat pixelFormat = bitCnt == 1 ? System.Windows.Media.PixelFormats.Gray8 : (bitCnt == 3 ? System.Windows.Media.PixelFormats.Bgr24 : System.Windows.Media.PixelFormats.Bgra32); // 计算每行字节数(stride) int bytesPerPixel = (pixelFormat.BitsPerPixel + 7) / 8; int stride = width * bytesPerPixel; long requiredLength = (long)stride * height; if (buffer == null) { "CameraViewModel.UpdateBitmap: buffer is null".LogSysError(); return null; } if (buffer.Length < requiredLength) { ($"CameraViewModel.UpdateBitmap: buffer length {buffer.Length} is less than required {requiredLength}").LogSysError(); return null; } // 如果现有 bitmap 可复用(尺寸和格式一致),就重用以减少 GC/分配 if (currentImage == null || currentImage.PixelWidth != width || currentImage.PixelHeight != height || !currentImage.Format.Equals(pixelFormat)) { // 新建 WriteableBitmap currentImage = new WriteableBitmap(width, height, 96, 96, pixelFormat, null); } // 将字节拷贝到 BackBuffer(UI 线程中调用) currentImage.Lock(); try { // BackBuffer 为 IntPtr,使用 Marshal.Copy 从 byte[] 拷贝 System.Runtime.InteropServices.Marshal.Copy(buffer, 0, currentImage.BackBuffer, (int)requiredLength); currentImage.AddDirtyRect(new Int32Rect(0, 0, width, height)); } finally { currentImage.Unlock(); } return currentImage; } catch (Exception ex) { ($"CameraViewModel.UpdateBitmap exception: {ex}").LogSysError(); return null; } } public CameraViewModel(IEventAggregator eventAggregator, AppCameraType cameraType) { _eventAggregator = eventAggregator; CameraType = cameraType; _eventAggregator?.Subscribe(this); } public void Handle(DisplayImageEventArgs message) { if (message == null || message.CameraType != CameraType) { return; } CommonUti.RunOnUi(() => { WriteableBitmap bmp = UpdateBitmap(message.CameraData); // 只有在成功生成 bitmap 时才更新 DisplayImage,避免把 null 赋值导致 UI 清空 if (bmp != null) { DisplayImage = bmp; } }); } public void Handle(DisplayFindTemplateResultEventArgs message) { if (message == null || message.CameraType != CameraType) { return; } CommonUti.RunOnUi(() => { ApplyTemplateResultShapes(message); }); } private void ApplyTemplateResultShapes(DisplayFindTemplateResultEventArgs message) { if (message.ClearShapesBeforeDisplay) { Shapes.Clear(); } if (message.Result == null) { return; } AppendCenterShapes(message.Result); AppendContourShape(message.Result.Contour); AppendCircleShapes(message.Result.Circles); AppendLineShapes(message.Result.Lines); AppendRectangleShapes(message.Result.Rectangles); } private void AppendCenterShapes(FindTemplateResult result) { if (result == null || !AreFinite(result.CenterX, result.CenterY, result.Score)) { return; } ShapeDrawing pointShape = new ShapeDrawing(); pointShape.X = result.CenterX; pointShape.Y = result.CenterY; Shapes.Add(pointShape); TextShapeDrawing textShape = new TextShapeDrawing(); textShape.X = result.CenterX; textShape.Y = result.CenterY; textShape.Text = $"Score:{result.Score:F3}"; Shapes.Add(textShape); } private void AppendContourShape(FindTemplateContourResult contour) { if (contour == null || !AreFinite(contour.StartX, contour.StartY, contour.EndX, contour.EndY)) { return; } RectShapeDrawing contourShape = new RectShapeDrawing(); contourShape.X = contour.StartX; contourShape.Y = contour.StartY; contourShape.X1 = contour.EndX; contourShape.Y1 = contour.EndY; Shapes.Add(contourShape); } private void AppendCircleShapes(IList circles) { if (circles == null) { return; } foreach (FindTemplateCircleResult circle in circles) { if (circle == null || !AreFinite(circle.CenterX, circle.CenterY, circle.Radius)) { continue; } CircleShapeDrawing circleShape = new CircleShapeDrawing(); circleShape.X = circle.CenterX; circleShape.Y = circle.CenterY; circleShape.CenterPoint = new Point(circle.CenterX, circle.CenterY); circleShape.RadiusX = circle.Radius; circleShape.RadiusY = circle.Radius; circleShape.LineWidth = 1; circleShape.FillColor = Brushes.Blue; Shapes.Add(circleShape); } } private void AppendLineShapes(IList lines) { if (lines == null) { return; } foreach (FindTemplateLineResult line in lines) { if (line == null || !AreFinite(line.StartX, line.StartY, line.EndX, line.EndY)) { continue; } LineShapeDrawing lineShape = new LineShapeDrawing(); lineShape.X = line.StartX; lineShape.Y = line.StartY; lineShape.X1 = line.EndX; lineShape.Y1 = line.EndY; Shapes.Add(lineShape); } } private void AppendRectangleShapes(IList rectangles) { if (rectangles == null) { return; } foreach (FindTemplateRectangleResult rectangle in rectangles) { if (rectangle == null || !AreFinite(rectangle.StartX, rectangle.StartY, rectangle.Width, rectangle.Height, rectangle.Angle)) { continue; } double halfWidth = rectangle.Width / 2d; double halfHeight = rectangle.Height / 2d; double cosAngle = Math.Cos(rectangle.Angle); double sinAngle = Math.Sin(rectangle.Angle); double centerX = rectangle.StartX + (halfWidth * cosAngle) - (halfHeight * sinAngle); double centerY = rectangle.StartY + (halfWidth * sinAngle) + (halfHeight * cosAngle); RectShape2Drawing rectangleShape = new RectShape2Drawing(); rectangleShape.X = centerX; rectangleShape.Y = centerY; rectangleShape.CenterX = centerX; rectangleShape.CenterY = centerY; rectangleShape.HalfWidth = halfWidth; rectangleShape.HalfHeigth = halfHeight; rectangleShape.Angle = rectangle.Angle; Shapes.Add(rectangleShape); } } private static bool AreFinite(params double[] values) { if (values == null) { return false; } foreach (double value in values) { if (double.IsNaN(value) || double.IsInfinity(value)) { return false; } } return true; } } }