586 lines
15 KiB
C#
586 lines
15 KiB
C#
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;
|
||
|
||
/// <summary>
|
||
/// 相机查看器
|
||
/// </summary>
|
||
[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<Unit, Unit> ToCapturePhoto { get; }
|
||
|
||
public ReactiveCommand<Unit, Unit> ToSavePhoto { get; }
|
||
|
||
public ReactiveCommand<Unit, Unit> ToggleCaptureVideo { get; }
|
||
public ReactiveCommand<Unit, Unit> ToToggleRecordingVideo { get; }
|
||
|
||
public ReactiveCommand<Unit, Unit> ToChangeStretch { get; }
|
||
|
||
public ReactiveCommand<Unit, Unit> ToggleShowCrosshair { get; }
|
||
|
||
public ReactiveCommand<Unit, Unit> ToggleShowGrid { get; }
|
||
|
||
public ReactiveCommand<Unit, Unit> ToMoveToWaferHeight { get; }
|
||
|
||
public ReactiveCommand<Unit, Unit> ToMoveToCrosshairCenter { get; }
|
||
|
||
public ReactiveCommand<Unit, Unit> ToMoveToSubstrateHeight { get; }
|
||
|
||
public void InitViewer(ImageViewer imageViewer)
|
||
{
|
||
_imageViewer = imageViewer;
|
||
}
|
||
|
||
public void InitWindow(CameraViewerWindow window)
|
||
{
|
||
_window = window;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 切换图片拉伸模式
|
||
/// </summary>
|
||
private void ChangeStretch()
|
||
{
|
||
StretchIndex = ++StretchIndex % _stretchs.Length;
|
||
Stretch = _stretchs[StretchIndex];
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存截图
|
||
/// </summary>
|
||
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, "保存图片失败");
|
||
}
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 采集单张图片
|
||
/// </summary>
|
||
[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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 连续采集
|
||
/// </summary>
|
||
[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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 接受图像回调
|
||
/// </summary>
|
||
/// <param name="type"></param>
|
||
/// <param name="data"></param>
|
||
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;
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 动态就算带宽
|
||
/// </summary>
|
||
/// <param name="sizeInBytes"></param>
|
||
/// <returns></returns>
|
||
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]}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 控制是否显示十字辅助现
|
||
/// </summary>
|
||
private void ShowCrosshair()
|
||
{
|
||
ShowingCrosshair = !ShowingCrosshair;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 屏幕录屏
|
||
/// </summary>
|
||
private void ToggleRecordingVideo()
|
||
{
|
||
if (RecordingVideo)
|
||
{
|
||
Task.Run(StopRecordingVideo);
|
||
}
|
||
else
|
||
{
|
||
Task.Run(StartRecordingVideo);
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 开始录屏
|
||
/// </summary>
|
||
[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.##") + "%";
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 控制是否显示网格线
|
||
/// </summary>
|
||
private void ShowGrid()
|
||
{
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 移除监听
|
||
/// </summary>
|
||
public void DisableCameraListener()
|
||
{
|
||
EventBus<CameraData>.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, "坐标验证采集照片失败: ");
|
||
}
|
||
}
|
||
} |