通用基础设施XP.Common新增 ImageHistogramControl 图像灰度直方图通用控件(使用SixLabors.ImageSharp 3.1.12),支持 Image<Rgba32> 和 byte[] 输入,支持多线程调用,Telerik RadChartView 渲染。

This commit is contained in:
QI Mingxuan
2026-05-18 09:17:39 +08:00
parent 346f4d9a9b
commit a9d56ebfbd
7 changed files with 2408 additions and 1 deletions
@@ -0,0 +1,331 @@
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
{
/// <summary>
/// 图像灰度直方图通用控件 | Image grayscale histogram control
/// 支持单帧静态图像和高频流式图像输入,使用 Telerik RadChartView 进行可视化渲染
/// </summary>
public partial class ImageHistogramControl : UserControl
{
#region | Dependency Properties
/// <summary>
/// 最大刷新帧率依赖属性 | MaxFrameRate dependency property
/// </summary>
public static readonly DependencyProperty MaxFrameRateProperty =
DependencyProperty.Register(
nameof(MaxFrameRate),
typeof(int),
typeof(ImageHistogramControl),
new PropertyMetadata(15, OnMaxFrameRateChanged, CoerceMaxFrameRate));
/// <summary>
/// 是否使用对数 Y 轴依赖属性 | IsLogarithmic dependency property
/// </summary>
public static readonly DependencyProperty IsLogarithmicProperty =
DependencyProperty.Register(
nameof(IsLogarithmic),
typeof(bool),
typeof(ImageHistogramControl),
new PropertyMetadata(false));
/// <summary>
/// 最大刷新帧率(fps),有效范围 1-60,默认 15 | Max frame rate (fps), valid range 1-60, default 15
/// </summary>
public int MaxFrameRate
{
get => (int)GetValue(MaxFrameRateProperty);
set => SetValue(MaxFrameRateProperty, value);
}
/// <summary>
/// 是否使用对数 Y 轴,默认 false | Whether to use logarithmic Y axis, default false
/// </summary>
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
/// <summary>
/// 构造函数 | Constructor
/// </summary>
public ImageHistogramControl()
{
InitializeComponent();
// 初始化内部组件 | Initialize internal components
_frameThrottler = new FrameThrottler();
_histogramEngine = new HistogramEngine();
// 尝试解析日志服务 | Try to resolve logger service
try
{
var loggerService = ContainerLocator.Current?.Resolve<ILoggerService>();
_logger = loggerService?.ForModule<ImageHistogramControl>();
}
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
/// <summary>
/// 传入 ImageSharp 图像对象,计算并显示灰度直方图 | Update histogram from ImageSharp image
/// </summary>
/// <param name="image">ImageSharp 图像对象 | ImageSharp image object</param>
public void UpdateImage(Image<Rgba32> 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);
}
}
/// <summary>
/// 传入原始像素数组,计算并显示灰度直方图 | Update histogram from raw byte array
/// </summary>
/// <param name="rawData">原始像素数据 | Raw pixel data</param>
/// <param name="width">图像宽度 | Image width</param>
/// <param name="height">图像高度 | Image height</param>
/// <param name="bitDepth">位深度(8 或 16| Bit depth (8 or 16)</param>
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);
}
}
/// <summary>
/// 清空直方图显示,恢复初始空白状态 | Clear histogram display, restore initial blank state
/// </summary>
public void Clear()
{
try
{
// 取消正在执行的后台任务 | Cancel running background task
CancelCurrentComputation();
// 取消帧率限流器中的待处理任务 | Cancel pending tasks in throttler
_frameThrottler.Cancel();
// 清空图表 | Clear chart
if (_chartRenderer != null)
{
Dispatcher.InvokeAsync(() => _chartRenderer.Clear());
}
}
catch (Exception ex)
{
_logger?.Error(ex, "Clear() 异常:{Message} | Clear() error: {Message}", ex.Message);
}
}
#endregion
#region | Private Methods
/// <summary>
/// 通过帧率限流器提交计算任务 | Submit computation through frame throttler
/// </summary>
private void SubmitComputation(Func<System.Threading.Tasks.Task<long[]?>> 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);
}
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);
}
});
}
/// <summary>
/// 获取或创建取消令牌(取消上一个)| Get or create cancellation token (cancel previous)
/// </summary>
private CancellationToken GetOrCreateCancellationToken()
{
lock (_ctsLock)
{
_currentCts?.Cancel();
_currentCts?.Dispose();
_currentCts = new CancellationTokenSource();
return _currentCts.Token;
}
}
/// <summary>
/// 取消当前计算 | Cancel current computation
/// </summary>
private void CancelCurrentComputation()
{
lock (_ctsLock)
{
_currentCts?.Cancel();
_currentCts?.Dispose();
_currentCts = null;
}
}
/// <summary>
/// Unloaded 事件处理:释放所有资源 | Unloaded event handler: release all resources
/// </summary>
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
}
}