using System; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Reactive; using System.Threading.Tasks; using Avalonia; using Avalonia.Media; using Avalonia.Media.Imaging; using Avalonia.Threading; using DispenserCommon.Aop; using DispenserCommon.Events; using DispenserCommon.Scheduler; using DispenserCore.Service; using DispenserHal.Camera.Factory; using DispenserHal.Camera.VO; using DispenserUI.Utils; using DispenserUI.ViewModels.Recorder; using DispenserUI.Views.Controls; using DispenserUI.Views.Windows; using Masuit.Tools; using ReactiveUI; using Serilog; using SharpAvi; using Bitmap = System.Drawing.Bitmap; using Point = Avalonia.Point; namespace DispenserUI.ViewModels.Components; /// /// 相机查看器 /// [GlobalTry] public class CameraViewerVM : ViewModelBase { private readonly SystemParamsService _systemParamsService = new(); private const string TaskName = "CameraViewer#UpdateDisplay"; private bool _blank = true; private Bitmap _captureImage; private bool _capturingPicture; private bool _capturingVideo; private bool _recordingVideo; private bool _isFullScreen; private bool _showingCrosshair; private bool _showingGrid; private double _acquisitionFps; private int _imageCount; private int _displayImageCount; private string _bandwidth; private string _originSize; private string _pixelCoordinate; private Point _centerPixelPoint = new(2048, 1500); private PixelFormat _pixelFormat; private int _receivedPerSecond; private int _tuningStep = 1; private Stretch _stretch = Stretch.Uniform; private readonly Stretch[] _stretchs = [ Stretch.None, Stretch.Uniform, Stretch.UniformToFill ]; private int _stretchIndex = 1; private string _scaleRatio = "100%"; private ImageViewer _imageViewer; private CameraViewerWindow _window; private ScreenRecorder? _recorder; private double _cameraPositionX; private double _cameraPositionY; public CameraViewerVM() { ToCapturePhoto = ReactiveCommand.CreateFromTask(CapturePhoto); ToggleCaptureVideo = ReactiveCommand.Create(CaptureVideo); ToToggleRecordingVideo = ReactiveCommand.Create(ToggleRecordingVideo); ToggleShowCrosshair = ReactiveCommand.Create(ShowCrosshair); ToggleShowGrid = ReactiveCommand.Create(ShowGrid); ToChangeStretch = ReactiveCommand.Create(ChangeStretch); ToSavePhoto = ReactiveCommand.Create(SavePhoto); } public bool Blank { get => _blank; private set => this.RaiseAndSetIfChanged(ref _blank, value); } public bool CapturingVideo { get => _capturingVideo; set => this.RaiseAndSetIfChanged(ref _capturingVideo, value); } public bool CapturingPicture { get => _capturingPicture; set => this.RaiseAndSetIfChanged(ref _capturingPicture, value); } public Bitmap CaptureImage { get => _captureImage; set => this.RaiseAndSetIfChanged(ref _captureImage, value); } public bool ShowingCrosshair { get => _showingCrosshair; set => this.RaiseAndSetIfChanged(ref _showingCrosshair, value); } public bool ShowingGrid { get => _showingGrid; set => this.RaiseAndSetIfChanged(ref _showingGrid, value); } public double AcquisitionFps { get => _acquisitionFps; set => this.RaiseAndSetIfChanged(ref _acquisitionFps, value); } public int ImageCount { get => _imageCount; set => this.RaiseAndSetIfChanged(ref _imageCount, value); } public int DisplayImageCount { get => _displayImageCount; set => this.RaiseAndSetIfChanged(ref _displayImageCount, value); } public string Bandwidth { get => _bandwidth; set => this.RaiseAndSetIfChanged(ref _bandwidth, value); } public string OriginSize { get => _originSize; set => this.RaiseAndSetIfChanged(ref _originSize, value); } public string PixelCoordinate { get => _pixelCoordinate; set => this.RaiseAndSetIfChanged(ref _pixelCoordinate, value); } public Point CenterPixelPoint { get => _centerPixelPoint; set => this.RaiseAndSetIfChanged(ref _centerPixelPoint, value); } public int TuningStep { get => _tuningStep; set => this.RaiseAndSetIfChanged(ref _tuningStep, value); } public bool IsFullScreen { get => _isFullScreen; set => this.RaiseAndSetIfChanged(ref _isFullScreen, value); } public Stretch Stretch { get => _stretch; set => this.RaiseAndSetIfChanged(ref _stretch, value); } public int StretchIndex { get => _stretchIndex; set => this.RaiseAndSetIfChanged(ref _stretchIndex, value); } public bool RecordingVideo { get => _recordingVideo; set => this.RaiseAndSetIfChanged(ref _recordingVideo, value); } public string ScaleRatio { get => _scaleRatio; set => this.RaiseAndSetIfChanged(ref _scaleRatio, value); } public double CameraPositionX { get => _cameraPositionX; set => this.RaiseAndSetIfChanged(ref _cameraPositionX, value); } public double CameraPositionY { get => _cameraPositionY; set => this.RaiseAndSetIfChanged(ref _cameraPositionY, value); } public ReactiveCommand ToCapturePhoto { get; } public ReactiveCommand ToSavePhoto { get; } public ReactiveCommand ToggleCaptureVideo { get; } public ReactiveCommand ToToggleRecordingVideo { get; } public ReactiveCommand ToChangeStretch { get; } public ReactiveCommand ToggleShowCrosshair { get; } public ReactiveCommand ToggleShowGrid { get; } public ReactiveCommand ToMoveToWaferHeight { get; } public ReactiveCommand ToMoveToCrosshairCenter { get; } public ReactiveCommand ToMoveToSubstrateHeight { get; } public void InitViewer(ImageViewer imageViewer) { _imageViewer = imageViewer; } public void InitWindow(CameraViewerWindow window) { _window = window; } /// /// 切换图片拉伸模式 /// private void ChangeStretch() { StretchIndex = ++StretchIndex % _stretchs.Length; Stretch = _stretchs[StretchIndex]; } /// /// 保存截图 /// private void SavePhoto() { Task.Run(() => { try { var systemParams = _systemParamsService.GetSystemParams(); var dir = systemParams.CameraViewerStoragePath ?? Environment.GetFolderPath(Environment.SpecialFolder.MyPictures); var viewBox = _imageViewer.GetViewer(); var pixelSize = new PixelSize((int)viewBox.Bounds.Width, (int)viewBox.Bounds.Height); var dpi = new Vector(96, 96); var renderTargetBitmap = new RenderTargetBitmap(pixelSize, dpi); var context = renderTargetBitmap.CreateDrawingContext(); Dispatcher.UIThread.Invoke(() => { viewBox.Render(context); }); dir = Path.Combine(dir, "Dispenser", $"{DateTime.Now:yyyyMMdd}"); if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } using (var stream = File.OpenWrite(Path.Combine(dir, $"{DateTime.Now:yyyyMMddHHmmss}.bmp"))) { renderTargetBitmap.Save(stream); } Dispatcher.UIThread.InvokeAsync(() => { new CameraViewerNotifyWindow("图片保存成功", dir).Show(); }); } catch (Exception e) { Log.Error(e, "保存图片失败"); } }); } /// /// 采集单张图片 /// [Operation("采集单张图片")] private async Task CapturePhoto() { var camera = CameraManager.GetCamera()!; if (CapturingVideo) { // 如果正在采集则提示停止 var confirm = await ConfirmDialog.ToConfirm("正在采集中,是否停止采集?"); if (!confirm) return; // // 先停止采集 camera.StopCaptureVideo(); } // 采集单张图片 camera.TokePhoto(); if (camera.CapturingVideo) { camera.StartCaptureVideo(); } CapturingPicture = true; ShowingCrosshair = false; } /// /// 连续采集 /// [Operation("开启/停止连续采集")] private void CaptureVideo() { ImageCount = 0; DisplayImageCount = 0; _receivedPerSecond = 0; var camera = CameraManager.GetCamera()!; if (CapturingVideo) { // 先停止采集 camera.StopCaptureVideo(); // 开启定时任务去更新显示信息 JobScheduler.RemoveTask(TaskName); } else { // 开始连续采集 camera.StartCaptureVideo(); // 开启定时任务去更新显示信息 JobScheduler.AddTask(TaskName, UpdateDisplayInfo, 1000); } CapturingVideo = !CapturingVideo; ShowingCrosshair = false; } public void StopCaptureVideo() { CapturingVideo = false; var camera = CameraManager.GetCamera()!; // 先停止采集 camera.StopCaptureVideo(); // 开启定时任务去更新显示信息 JobScheduler.RemoveTask(TaskName); ShowingCrosshair = false; } /// /// 接受图像回调 /// /// /// private async Task CameraCallBack(EventType type, CameraData data) { await Task.Run(() => { if (!CapturingPicture && !CapturingVideo) return; var image = data.Image; if (image.IsNullOrEmpty()) return; _pixelFormat = image.PixelFormat; _receivedPerSecond++; // 更新接收到的图片数 ImageCount = CapturingVideo ? ImageCount + 1 : 1; // 照片尺寸 OriginSize = $"{image.Width}*{image.Height}"; // 更新展示的图片数 DisplayImageCount++; Dispatcher.UIThread.Invoke(() => { if (Blank) Blank = false; CaptureImage = image; if (CapturingPicture) CapturingPicture = false; }); }); } // 定时更新显示信息 private void UpdateDisplayInfo() { if (OriginSize.IsNullOrEmpty()) return; var pixelFormatSize = Image.GetPixelFormatSize(_pixelFormat) / 8; var size = OriginSize.Split("*"); // 开启定时任务,每隔1秒更新一次 Dispatcher.UIThread.InvokeAsync(() => { Bandwidth = GetByteSizeString( int.Parse(size[0]) * int.Parse(size[1]) * pixelFormatSize * _receivedPerSecond); AcquisitionFps = _receivedPerSecond; _receivedPerSecond = 0; }); } /// /// 动态就算带宽 /// /// /// static string GetByteSizeString(long sizeInBytes) { string[] sizeSuffixes = ["Bps", "Kps", "Mps", "Gps"]; if (sizeInBytes == 0) return "0" + sizeSuffixes[0]; var magnitude = (int)Math.Log(sizeInBytes, 1024); var adjustedSize = sizeInBytes / Math.Pow(1024, magnitude); return $"{adjustedSize:0.##} {sizeSuffixes[magnitude]}"; } /// /// 控制是否显示十字辅助现 /// private void ShowCrosshair() { ShowingCrosshair = !ShowingCrosshair; } /// /// 屏幕录屏 /// private void ToggleRecordingVideo() { if (RecordingVideo) { Task.Run(StopRecordingVideo); } else { Task.Run(StartRecordingVideo); } } /// /// 开始录屏 /// [Operation("开始录屏")] private void StartRecordingVideo() { if (RecordingVideo) return; RecordingVideo = true; var systemParams = _systemParamsService.GetSystemParams(); var dir = systemParams.CameraViewerStoragePath ?? Environment.GetFolderPath(Environment.SpecialFolder.MyPictures); dir = Path.Combine(dir, "Dispenser", $"{DateTime.Now:yyyyMMdd}"); if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } var lastFileName = Path.Combine(dir, DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss") + ".avi"); // ReSharper disable once InconsistentNaming FourCC MJPEG_IMAGE_SHARP = "IMG#"; _recorder = new ScreenRecorder(lastFileName, MJPEG_IMAGE_SHARP, 10, 100); OnWindowResize(); _recorder.Start(); } // 停止录屏 [Operation("停止录屏")] public void StopRecordingVideo() { if (!RecordingVideo || _recorder == null) return; RecordingVideo = false; var dir = _recorder.GetDirectory(); _recorder.Stop(); Dispatcher.UIThread.InvokeAsync(() => { new CameraViewerNotifyWindow("录屏保存成功", dir!).Show(); }); } public void OnWindowResize() { // 支取中间的90%画面内容 if (_recorder != null) { var container = _imageViewer.GetContainer(); var width = container.Bounds.Width - 50; var height = container.Bounds.Height - 50; var left = _window.Position.X + 20; var top = _window.Position.Y + 60; _recorder.Width = (int)width; _recorder.Height = (int)height; _recorder.Left = left; _recorder.Top = top; } } public void UpdateScaleRatio(double ratio) { if (ratio < 1) { ScaleRatio = ((1 - ratio) * -100).ToString("0.##") + "%"; } else { ScaleRatio = (ratio * 100).ToString("0.##") + "%"; } } /// /// 控制是否显示网格线 /// private void ShowGrid() { } /// /// 移除监听 /// public void DisableCameraListener() { EventBus.RemoveEventHandler(EventType.CameraData, CameraCallBack); JobScheduler.RemoveTask("CameraViewer#WatchCameraPosition"); } private void TriggerTakePhoto() { try { var camera = CameraManager.GetCamera()!; camera.TokePhoto(); if (camera.CapturingVideo) { camera.StartCaptureVideo(); } } catch (Exception e) { Log.Error(e, "坐标验证采集照片失败: "); } } }