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
}
}