369 lines
12 KiB
C#
369 lines
12 KiB
C#
|
|
using System;
|
|||
|
|
using System.ComponentModel;
|
|||
|
|
using System.Runtime.CompilerServices;
|
|||
|
|
using System.Threading.Tasks;
|
|||
|
|
using System.Windows;
|
|||
|
|
using System.Windows.Input;
|
|||
|
|
|
|||
|
|
namespace SHH.CameraDashboard
|
|||
|
|
{
|
|||
|
|
public class CameraImgProcViewModel : INotifyPropertyChanged
|
|||
|
|
{
|
|||
|
|
private readonly WebApiCameraModel _sourceCamera;
|
|||
|
|
private readonly CameraEditInfo _editInfo;
|
|||
|
|
|
|||
|
|
public event Action RequestClose;
|
|||
|
|
|
|||
|
|
public string Title => $"{_sourceCamera.Name} - 图像处理";
|
|||
|
|
public string SourceResolutionText => $"{_sourceCamera.Width} x {_sourceCamera.Height}";
|
|||
|
|
|
|||
|
|
// --- 状态控制属性 ---
|
|||
|
|
|
|||
|
|
// 源是否是高清 (>= 1080P)
|
|||
|
|
public bool IsSourceHighRes => _sourceCamera.Width >= 1920 || _sourceCamera.Height >= 1080;
|
|||
|
|
|
|||
|
|
// 是否允许勾选“允许放大” (UI 绑定 IsEnabled)
|
|||
|
|
public bool CanCheckExpand => !IsSourceHighRes;
|
|||
|
|
|
|||
|
|
// 滑块是否可用 (只有开启了缩小 或 开启了放大,才允许拖动)
|
|||
|
|
// ★★★ 修正:使用 AllowEnlarge ★★★
|
|||
|
|
public bool IsSliderEnabled => AllowShrink || AllowEnlarge;
|
|||
|
|
|
|||
|
|
// 允许缩小
|
|||
|
|
public bool AllowShrink
|
|||
|
|
{
|
|||
|
|
get => _editInfo.AllowShrink;
|
|||
|
|
set
|
|||
|
|
{
|
|||
|
|
if (_editInfo.AllowShrink != value)
|
|||
|
|
{
|
|||
|
|
_editInfo.AllowShrink = value;
|
|||
|
|
OnPropertyChanged();
|
|||
|
|
OnPropertyChanged(nameof(IsSliderEnabled));
|
|||
|
|
ValidateAndSyncSlider();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ★★★ 修正:属性名为 AllowEnlarge,对应 Model 中的 AllowEnlarge ★★★
|
|||
|
|
public bool AllowEnlarge
|
|||
|
|
{
|
|||
|
|
get => _editInfo.AllowEnlarge;
|
|||
|
|
set
|
|||
|
|
{
|
|||
|
|
if (_editInfo.AllowEnlarge != value)
|
|||
|
|
{
|
|||
|
|
_editInfo.AllowEnlarge = value;
|
|||
|
|
OnPropertyChanged();
|
|||
|
|
OnPropertyChanged(nameof(IsSliderEnabled));
|
|||
|
|
// 开关变化时,重新校验当前尺寸是否合法
|
|||
|
|
ValidateAndSyncSlider();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 图像增强
|
|||
|
|
public bool EnhanceImage
|
|||
|
|
{
|
|||
|
|
get => _editInfo.EnhanceImage;
|
|||
|
|
set
|
|||
|
|
{
|
|||
|
|
if (_editInfo.EnhanceImage != value)
|
|||
|
|
{
|
|||
|
|
_editInfo.EnhanceImage = value;
|
|||
|
|
OnPropertyChanged();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- 分辨率与缩放属性 ---
|
|||
|
|
|
|||
|
|
private int _targetWidth;
|
|||
|
|
public int TargetWidth
|
|||
|
|
{
|
|||
|
|
get => _targetWidth;
|
|||
|
|
set
|
|||
|
|
{
|
|||
|
|
if (SetProperty(ref _targetWidth, value))
|
|||
|
|
{
|
|||
|
|
if (IsRatioLocked && _sourceCamera.Width > 0)
|
|||
|
|
{
|
|||
|
|
_targetHeight = (int)((double)value / _sourceCamera.Width * _sourceCamera.Height);
|
|||
|
|
OnPropertyChanged(nameof(TargetHeight));
|
|||
|
|
}
|
|||
|
|
ValidateAndSyncSlider();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private int _targetHeight;
|
|||
|
|
public int TargetHeight
|
|||
|
|
{
|
|||
|
|
get => _targetHeight;
|
|||
|
|
set
|
|||
|
|
{
|
|||
|
|
if (SetProperty(ref _targetHeight, value))
|
|||
|
|
{
|
|||
|
|
if (IsRatioLocked && _sourceCamera.Height > 0)
|
|||
|
|
{
|
|||
|
|
_targetWidth = (int)((double)value / _sourceCamera.Height * _sourceCamera.Width);
|
|||
|
|
OnPropertyChanged(nameof(TargetWidth));
|
|||
|
|
}
|
|||
|
|
ValidateAndSyncSlider();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private bool _isRatioLocked = true;
|
|||
|
|
public bool IsRatioLocked
|
|||
|
|
{
|
|||
|
|
get => _isRatioLocked;
|
|||
|
|
set => SetProperty(ref _isRatioLocked, value);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private double _scalePercent = 100;
|
|||
|
|
public double ScalePercent
|
|||
|
|
{
|
|||
|
|
get => _scalePercent;
|
|||
|
|
set
|
|||
|
|
{
|
|||
|
|
if (SetProperty(ref _scalePercent, value))
|
|||
|
|
{
|
|||
|
|
UpdateResolutionByScale(value);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// [新增] 亮度调节属性
|
|||
|
|
public int Brightness
|
|||
|
|
{
|
|||
|
|
get => _editInfo.Brightness;
|
|||
|
|
set
|
|||
|
|
{
|
|||
|
|
if (_editInfo.Brightness != value)
|
|||
|
|
{
|
|||
|
|
_editInfo.Brightness = value;
|
|||
|
|
OnPropertyChanged();
|
|||
|
|
// 如果需要,可以在这里限制范围,例如 0-100
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
public ICommand SaveCommand { get; }
|
|||
|
|
public ICommand CancelCommand { get; }
|
|||
|
|
public ICommand ApplyPresetCommand { get; }
|
|||
|
|
|
|||
|
|
public CameraImgProcViewModel(WebApiCameraModel source, CameraEditInfo detail)
|
|||
|
|
{
|
|||
|
|
_sourceCamera = source;
|
|||
|
|
|
|||
|
|
// 此时 detail 可能是不完整的,先赋值防止空引用
|
|||
|
|
_editInfo = detail ?? new CameraEditInfo { Id = source.Id };
|
|||
|
|
|
|||
|
|
SaveCommand = new RelayCommand(ExecuteSave);
|
|||
|
|
CancelCommand = new RelayCommand(o => RequestClose?.Invoke());
|
|||
|
|
ApplyPresetCommand = new RelayCommand(ExecutePreset);
|
|||
|
|
|
|||
|
|
InitializeState();
|
|||
|
|
|
|||
|
|
LoadDataAsync();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 实现加载逻辑
|
|||
|
|
private async void LoadDataAsync()
|
|||
|
|
{
|
|||
|
|
// 调用 Repository
|
|||
|
|
var remoteInfo = await ApiClient.Instance.Cameras.GetImageProcessingAsync(_sourceCamera.Id);
|
|||
|
|
|
|||
|
|
if (remoteInfo != null)
|
|||
|
|
{
|
|||
|
|
// --- A. 更新 ViewModel 的开关属性 ---
|
|||
|
|
// 注意:Setter 会自动触发 OnPropertyChanged 和 ValidateAndSyncSlider
|
|||
|
|
AllowShrink = remoteInfo.AllowShrink;
|
|||
|
|
AllowEnlarge = remoteInfo.AllowEnlarge; // 这里会自动更新 IsSliderEnabled
|
|||
|
|
EnhanceImage = remoteInfo.EnhanceImage;
|
|||
|
|
|
|||
|
|
// --- B. 更新分辨率 (解析 "W x H") ---
|
|||
|
|
if (!string.IsNullOrWhiteSpace(remoteInfo.TargetResolution))
|
|||
|
|
{
|
|||
|
|
var parts = remoteInfo.TargetResolution.Split('x');
|
|||
|
|
if (parts.Length == 2 && int.TryParse(parts[0], out int w) && int.TryParse(parts[1], out int h))
|
|||
|
|
{
|
|||
|
|
// 赋值 TargetWidth/Height 会自动触发 ValidateAndSyncSlider
|
|||
|
|
// 从而更新 ScalePercent 和 滑块位置
|
|||
|
|
TargetWidth = w;
|
|||
|
|
TargetHeight = h;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void InitializeState()
|
|||
|
|
{
|
|||
|
|
// 1. 强制规则:如果源分辨率 >= 1080P,不允许放大
|
|||
|
|
if (IsSourceHighRes)
|
|||
|
|
{
|
|||
|
|
_editInfo.AllowEnlarge = false; // ★★★ 修正引用 ★★★
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 检查历史设置
|
|||
|
|
// ★★★ 修正引用 ★★★
|
|||
|
|
bool hasPreviousSettings = _editInfo.AllowShrink || _editInfo.AllowEnlarge;
|
|||
|
|
|
|||
|
|
if (hasPreviousSettings)
|
|||
|
|
{
|
|||
|
|
if (!string.IsNullOrWhiteSpace(_editInfo.TargetResolution))
|
|||
|
|
{
|
|||
|
|
var parts = _editInfo.TargetResolution.ToLower().Split('x');
|
|||
|
|
if (parts.Length == 2 && int.TryParse(parts[0], out int w) && int.TryParse(parts[1], out int h))
|
|||
|
|
{
|
|||
|
|
_targetWidth = w;
|
|||
|
|
_targetHeight = h;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
// 默认逻辑
|
|||
|
|
if (_sourceCamera.Width > 1280)
|
|||
|
|
{
|
|||
|
|
_targetWidth = 1280;
|
|||
|
|
_targetHeight = _sourceCamera.Width > 0
|
|||
|
|
? (int)((double)1280 / _sourceCamera.Width * _sourceCamera.Height)
|
|||
|
|
: 720;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
_targetWidth = _sourceCamera.Width;
|
|||
|
|
_targetHeight = _sourceCamera.Height;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ValidateAndSyncSlider();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void UpdateResolutionByScale(double percent)
|
|||
|
|
{
|
|||
|
|
if (_sourceCamera.Width <= 0 || _sourceCamera.Height <= 0) return;
|
|||
|
|
|
|||
|
|
double w = _sourceCamera.Width * (percent / 100.0);
|
|||
|
|
double h = _sourceCamera.Height * (percent / 100.0);
|
|||
|
|
|
|||
|
|
_targetWidth = (int)w;
|
|||
|
|
_targetHeight = (int)h;
|
|||
|
|
OnPropertyChanged(nameof(TargetWidth));
|
|||
|
|
OnPropertyChanged(nameof(TargetHeight));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void ValidateAndSyncSlider()
|
|||
|
|
{
|
|||
|
|
if (_sourceCamera.Width <= 0) return;
|
|||
|
|
|
|||
|
|
// ★★★ 修正引用:使用 AllowEnlarge ★★★
|
|||
|
|
int maxWidth = AllowEnlarge ? 1920 : _sourceCamera.Width;
|
|||
|
|
int maxHeight = AllowEnlarge ? 1080 : _sourceCamera.Height;
|
|||
|
|
int minWidth = 160;
|
|||
|
|
|
|||
|
|
bool changed = false;
|
|||
|
|
|
|||
|
|
// 规则A:如果未开启放大,且当前 > 源 -> 强制回退到源
|
|||
|
|
// ★★★ 修正引用 ★★★
|
|||
|
|
if (!AllowEnlarge && (_targetWidth > _sourceCamera.Width))
|
|||
|
|
{
|
|||
|
|
_targetWidth = _sourceCamera.Width;
|
|||
|
|
_targetHeight = _sourceCamera.Height;
|
|||
|
|
changed = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!AllowShrink && (_targetWidth < _sourceCamera.Width))
|
|||
|
|
{
|
|||
|
|
_targetWidth = _sourceCamera.Width;
|
|||
|
|
_targetHeight = _sourceCamera.Height;
|
|||
|
|
changed = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (_targetWidth > 1920) { _targetWidth = 1920; changed = true; }
|
|||
|
|
if (_targetHeight > 1080) { _targetHeight = 1080; changed = true; }
|
|||
|
|
if (_targetWidth < minWidth) { _targetWidth = minWidth; changed = true; }
|
|||
|
|
|
|||
|
|
if (changed)
|
|||
|
|
{
|
|||
|
|
OnPropertyChanged(nameof(TargetWidth));
|
|||
|
|
OnPropertyChanged(nameof(TargetHeight));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
double currentPercent = ((double)_targetWidth / _sourceCamera.Width) * 100.0;
|
|||
|
|
|
|||
|
|
if (Math.Abs(_scalePercent - currentPercent) > 0.1)
|
|||
|
|
{
|
|||
|
|
_scalePercent = currentPercent;
|
|||
|
|
OnPropertyChanged(nameof(ScalePercent));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void ExecutePreset(object parameter)
|
|||
|
|
{
|
|||
|
|
if (parameter is string preset)
|
|||
|
|
{
|
|||
|
|
var parts = preset.Split('x');
|
|||
|
|
if (parts.Length != 2) return;
|
|||
|
|
|
|||
|
|
if (int.TryParse(parts[0], out int w) && int.TryParse(parts[1], out int h))
|
|||
|
|
{
|
|||
|
|
if (w > _sourceCamera.Width)
|
|||
|
|
{
|
|||
|
|
if (IsSourceHighRes)
|
|||
|
|
{
|
|||
|
|
MessageBox.Show("当前源分辨率已超过或等于 1080P,不支持放大。", "提示");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
AllowEnlarge = true; // ★★★ 修正引用 ★★★
|
|||
|
|
}
|
|||
|
|
else if (w < _sourceCamera.Width)
|
|||
|
|
{
|
|||
|
|
AllowShrink = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TargetWidth = w;
|
|||
|
|
TargetHeight = h;
|
|||
|
|
IsRatioLocked = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private async void ExecuteSave(object obj)
|
|||
|
|
{
|
|||
|
|
// 1. 准备数据 (拼接分辨率字符串)
|
|||
|
|
_editInfo.TargetResolution = $"{TargetWidth}x{TargetHeight}";
|
|||
|
|
|
|||
|
|
// 2. 调用专用的 Processing 接口
|
|||
|
|
bool success = await ApiClient.Instance.Cameras.UpdateImageProcessingAsync(_editInfo); // <--- 换成这就行
|
|||
|
|
|
|||
|
|
if (success)
|
|||
|
|
{
|
|||
|
|
AppGlobal.RequestRefresh();
|
|||
|
|
RequestClose?.Invoke();
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
MessageBox.Show("保存图像处理配置失败,请检查网络或后端日志。", "错误");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public event PropertyChangedEventHandler PropertyChanged;
|
|||
|
|
|
|||
|
|
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string name = null)
|
|||
|
|
{
|
|||
|
|
if (Equals(field, value)) return false;
|
|||
|
|
field = value;
|
|||
|
|
OnPropertyChanged(name);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
protected void OnPropertyChanged([CallerMemberName] string name = null)
|
|||
|
|
{
|
|||
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|