using System; using System.Threading; using System.Windows; using System.Windows.Controls; using System.Windows.Threading; using Prism.Ioc; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using XP.Common.Logging.Interfaces; namespace XP.Common.Controls.ImageHistogram { /// /// 图像灰度直方图通用控件 | Image grayscale histogram control /// 支持单帧静态图像和高频流式图像输入,使用 Telerik RadChartView 进行可视化渲染 /// public partial class ImageHistogramControl : UserControl { #region 依赖属性 | Dependency Properties /// /// 最大刷新帧率依赖属性 | MaxFrameRate dependency property /// public static readonly DependencyProperty MaxFrameRateProperty = DependencyProperty.Register( nameof(MaxFrameRate), typeof(int), typeof(ImageHistogramControl), new PropertyMetadata(15, OnMaxFrameRateChanged, CoerceMaxFrameRate)); /// /// 是否使用对数 Y 轴依赖属性 | IsLogarithmic dependency property /// public static readonly DependencyProperty IsLogarithmicProperty = DependencyProperty.Register( nameof(IsLogarithmic), typeof(bool), typeof(ImageHistogramControl), new PropertyMetadata(false)); /// /// 最大刷新帧率(fps),有效范围 1-60,默认 15 | Max frame rate (fps), valid range 1-60, default 15 /// public int MaxFrameRate { get => (int)GetValue(MaxFrameRateProperty); set => SetValue(MaxFrameRateProperty, value); } /// /// 是否使用对数 Y 轴,默认 false | Whether to use logarithmic Y axis, default false /// public bool IsLogarithmic { get => (bool)GetValue(IsLogarithmicProperty); set => SetValue(IsLogarithmicProperty, value); } private static void OnMaxFrameRateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is ImageHistogramControl control) { var newValue = (int)e.NewValue; control._frameThrottler.MaxFrameRate = newValue; } } private static object CoerceMaxFrameRate(DependencyObject d, object baseValue) { var value = (int)baseValue; var clamped = Math.Clamp(value, 1, 60); if (clamped != value && d is ImageHistogramControl control) { control._logger?.Warn( "MaxFrameRate 值 {Value} 超出有效范围,已钳位为 {Clamped} | MaxFrameRate value {Value} out of range, clamped to {Clamped}", value, clamped); } return clamped; } #endregion #region 私有字段 | Private Fields private readonly FrameThrottler _frameThrottler; private readonly HistogramEngine _histogramEngine; private ChartRenderer? _chartRenderer; private ILoggerService? _logger; private CancellationTokenSource? _currentCts; private readonly object _ctsLock = new(); #endregion #region 构造函数 | Constructor /// /// 构造函数 | Constructor /// public ImageHistogramControl() { InitializeComponent(); // 初始化内部组件 | Initialize internal components _frameThrottler = new FrameThrottler(); _histogramEngine = new HistogramEngine(); // 尝试解析日志服务 | Try to resolve logger service try { var loggerService = ContainerLocator.Current?.Resolve(); _logger = loggerService?.ForModule(); } catch { // 日志服务不可用,静默降级 | Logger service unavailable, silent degradation _logger = null; } // 订阅 Loaded 事件初始化 ChartRenderer | Subscribe Loaded event to initialize ChartRenderer Loaded += OnLoaded; Unloaded += OnUnloaded; } private void OnLoaded(object sender, RoutedEventArgs e) { // 初始化 ChartRenderer | Initialize ChartRenderer _chartRenderer = new ChartRenderer(HistogramChart, HistogramBarSeries, XAxis); } #endregion #region 公共 API | Public API /// /// 传入 ImageSharp 图像对象,计算并显示灰度直方图 | Update histogram from ImageSharp image /// /// ImageSharp 图像对象 | ImageSharp image object public void UpdateImage(Image image) { try { if (image == null) { _logger?.Warn("UpdateImage 收到 null 图像,已忽略 | UpdateImage received null image, ignored"); return; } SubmitComputation(() => _histogramEngine.ComputeAsync(image, GetOrCreateCancellationToken())); } catch (Exception ex) { _logger?.Error(ex, "UpdateImage(Image) 异常:{Message} | UpdateImage(Image) error: {Message}", ex.Message); } } /// /// 传入原始像素数组,计算并显示灰度直方图 | Update histogram from raw byte array /// /// 原始像素数据 | Raw pixel data /// 图像宽度 | Image width /// 图像高度 | Image height /// 位深度(8 或 16)| Bit depth (8 or 16) public void UpdateImage(byte[] rawData, int width, int height, int bitDepth) { try { // 参数有效性验证 | Parameter validation if (rawData == null) { _logger?.Warn("UpdateImage 收到 null rawData,已忽略 | UpdateImage received null rawData, ignored"); return; } if (width <= 0 || height <= 0) { _logger?.Warn( "UpdateImage 参数无效:width={Width}, height={Height} | Invalid params: width={Width}, height={Height}", width, height); return; } if (bitDepth != 8 && bitDepth != 16) { _logger?.Warn( "UpdateImage 参数无效:bitDepth={BitDepth},仅支持 8 或 16 | Invalid bitDepth={BitDepth}, only 8 or 16 supported", bitDepth); return; } int expectedLength = bitDepth == 8 ? width * height : width * height * 2; if (rawData.Length != expectedLength) { _logger?.Warn( "UpdateImage 参数无效:rawData.Length={Length}, 预期={Expected} | Invalid params: rawData.Length={Length}, expected={Expected}", rawData.Length, expectedLength); return; } SubmitComputation(() => _histogramEngine.ComputeAsync(rawData, width, height, bitDepth, GetOrCreateCancellationToken())); } catch (Exception ex) { _logger?.Error(ex, "UpdateImage(byte[]) 异常:{Message} | UpdateImage(byte[]) error: {Message}", ex.Message); } } /// /// 清空直方图显示,恢复初始空白状态 | Clear histogram display, restore initial blank state /// public void Clear() { try { // 取消正在执行的后台任务 | Cancel running background task CancelCurrentComputation(); // 取消帧率限流器中的待处理任务 | Cancel pending tasks in throttler _frameThrottler.Cancel(); // 清空图表(捕获局部引用避免异步执行时为 null)| Clear chart (capture local ref to avoid null during async) var renderer = _chartRenderer; if (renderer != null) { Dispatcher.InvokeAsync(() => { try { renderer.Clear(); // 显示无数据提示 | Show no-data placeholder NoDataPlaceholder.Visibility = Visibility.Visible; } catch { // 控件已卸载时忽略 | Ignore if control already unloaded } }); } } catch (Exception ex) { _logger?.Error(ex, "Clear() 异常:{Message} | Clear() error: {Message}", ex.Message); } } #endregion #region 私有方法 | Private Methods /// /// 通过帧率限流器提交计算任务 | Submit computation through frame throttler /// private void SubmitComputation(Func> computeFunc) { _frameThrottler.TrySubmit(() => { try { var task = computeFunc(); task.ContinueWith(t => { if (t.IsCompletedSuccessfully && t.Result != null) { var histogram = t.Result; var isLog = false; // 在 UI 线程获取 IsLogarithmic 值并更新图表 | Get IsLogarithmic on UI thread and update chart Dispatcher.InvokeAsync(() => { try { isLog = IsLogarithmic; _chartRenderer?.UpdateData(histogram, isLog); // 隐藏无数据提示 | Hide no-data placeholder NoDataPlaceholder.Visibility = Visibility.Collapsed; } catch (Exception ex) { _logger?.Error(ex, "图表更新异常:{Message} | Chart update error: {Message}", ex.Message); } }); } else if (t.IsFaulted) { _logger?.Error(t.Exception, "直方图计算异常:{Message} | Histogram computation error: {Message}", t.Exception?.InnerException?.Message ?? "Unknown"); } }, System.Threading.Tasks.TaskScheduler.Default); } catch (Exception ex) { _logger?.Error(ex, "提交计算任务异常:{Message} | Submit computation error: {Message}", ex.Message); } }); } /// /// 获取或创建取消令牌(取消上一个)| Get or create cancellation token (cancel previous) /// private CancellationToken GetOrCreateCancellationToken() { lock (_ctsLock) { _currentCts?.Cancel(); _currentCts?.Dispose(); _currentCts = new CancellationTokenSource(); return _currentCts.Token; } } /// /// 取消当前计算 | Cancel current computation /// private void CancelCurrentComputation() { lock (_ctsLock) { _currentCts?.Cancel(); _currentCts?.Dispose(); _currentCts = null; } } /// /// Unloaded 事件处理:释放所有资源 | Unloaded event handler: release all resources /// private void OnUnloaded(object sender, RoutedEventArgs e) { // 取消所有后台任务 | Cancel all background tasks CancelCurrentComputation(); // 释放帧率限流器 | Dispose frame throttler _frameThrottler.Cancel(); _frameThrottler.Dispose(); // 释放计算引擎 | Dispose histogram engine _histogramEngine.Dispose(); // 清空引用 | Clear references _chartRenderer = null; } #endregion } }