通用基础设施XP.Common新增 ImageHistogramControl 图像灰度直方图通用控件(使用SixLabors.ImageSharp 3.1.12),支持 Image<Rgba32> 和 byte[] 输入,支持多线程调用,Telerik RadChartView 渲染。
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Telerik.Windows.Controls;
|
||||
using Telerik.Windows.Controls.ChartView;
|
||||
|
||||
namespace XP.Common.Controls.ImageHistogram
|
||||
{
|
||||
/// <summary>
|
||||
/// RadChartView 渲染适配器 | RadChartView rendering adapter
|
||||
/// 负责将直方图频次数据渲染到 Telerik RadCartesianChart 控件
|
||||
/// </summary>
|
||||
internal sealed class ChartRenderer
|
||||
{
|
||||
private readonly RadCartesianChart _chart;
|
||||
private readonly BarSeries _barSeries;
|
||||
private readonly LinearAxis _xAxis;
|
||||
|
||||
/// <summary>
|
||||
/// 16 位数据聚合因子 | 16-bit data aggregation factor
|
||||
/// </summary>
|
||||
private const int AggregationFactor = 256;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数,接收 RadCartesianChart 实例 | Constructor, receives RadCartesianChart instance
|
||||
/// </summary>
|
||||
/// <param name="chart">图表控件实例 | Chart control instance</param>
|
||||
/// <param name="barSeries">柱状图系列 | Bar series</param>
|
||||
/// <param name="xAxis">X 轴 | X axis</param>
|
||||
public ChartRenderer(RadCartesianChart chart, BarSeries barSeries, LinearAxis xAxis)
|
||||
{
|
||||
_chart = chart ?? throw new ArgumentNullException(nameof(chart));
|
||||
_barSeries = barSeries ?? throw new ArgumentNullException(nameof(barSeries));
|
||||
_xAxis = xAxis ?? throw new ArgumentNullException(nameof(xAxis));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新直方图数据 | Update histogram data
|
||||
/// </summary>
|
||||
/// <param name="histogram">频次数组(256 或 65536 长度)| Frequency array (256 or 65536 length)</param>
|
||||
/// <param name="isLogarithmic">是否使用对数 Y 轴 | Whether to use logarithmic Y axis</param>
|
||||
public void UpdateData(long[] histogram, bool isLogarithmic)
|
||||
{
|
||||
if (histogram == null || histogram.Length == 0)
|
||||
return;
|
||||
|
||||
// 确定是否需要聚合(16 位数据)| Determine if aggregation needed (16-bit data)
|
||||
long[] displayData;
|
||||
int xAxisMax;
|
||||
|
||||
if (histogram.Length == 65536)
|
||||
{
|
||||
// 16 位数据聚合为 256 个柱体 | Aggregate 16-bit data to 256 bars
|
||||
displayData = Aggregate16BitHistogram(histogram);
|
||||
xAxisMax = 65535;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 8 位数据直接显示 | Display 8-bit data directly
|
||||
displayData = histogram;
|
||||
xAxisMax = 255;
|
||||
}
|
||||
|
||||
// 设置 X 轴范围 | Set X axis range
|
||||
_xAxis.Minimum = 0;
|
||||
_xAxis.Maximum = xAxisMax;
|
||||
|
||||
// 构建数据点 | Build data points
|
||||
var dataPoints = new List<HistogramDataPoint>();
|
||||
|
||||
if (isLogarithmic)
|
||||
{
|
||||
// 对数模式:频次为 0 的不绘制 | Logarithmic mode: skip zero frequency
|
||||
for (int i = 0; i < displayData.Length; i++)
|
||||
{
|
||||
if (displayData[i] > 0)
|
||||
{
|
||||
double xValue = histogram.Length == 65536
|
||||
? i * AggregationFactor + AggregationFactor / 2.0
|
||||
: i;
|
||||
dataPoints.Add(new HistogramDataPoint
|
||||
{
|
||||
GrayLevel = xValue,
|
||||
Frequency = displayData[i]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 线性模式:所有灰度级别都绘制 | Linear mode: draw all gray levels
|
||||
for (int i = 0; i < displayData.Length; i++)
|
||||
{
|
||||
double xValue = histogram.Length == 65536
|
||||
? i * AggregationFactor + AggregationFactor / 2.0
|
||||
: i;
|
||||
dataPoints.Add(new HistogramDataPoint
|
||||
{
|
||||
GrayLevel = xValue,
|
||||
Frequency = displayData[i]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 更新图表数据 | Update chart data
|
||||
_barSeries.ItemsSource = dataPoints;
|
||||
|
||||
// 设置 Y 轴范围 | Set Y axis range
|
||||
UpdateYAxis(displayData, isLogarithmic);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空图表,恢复初始状态 | Clear chart, restore initial state
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_barSeries.ItemsSource = null;
|
||||
|
||||
// X 轴范围重置为 0-255 | Reset X axis range to 0-255
|
||||
_xAxis.Minimum = 0;
|
||||
_xAxis.Maximum = 255;
|
||||
|
||||
// Y 轴范围重置为 0-1 | Reset Y axis range to 0-1
|
||||
SetYAxisRange(0, 1, isLogarithmic: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前数据点数量 | Get current data point count
|
||||
/// </summary>
|
||||
public int DataPointCount
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_barSeries.ItemsSource is ICollection<HistogramDataPoint> collection)
|
||||
return collection.Count;
|
||||
if (_barSeries.ItemsSource is IEnumerable<HistogramDataPoint> enumerable)
|
||||
return enumerable.Count();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 65536 长度的频次数组聚合为 256 个柱体 | Aggregate 65536-length array to 256 bars
|
||||
/// </summary>
|
||||
private static long[] Aggregate16BitHistogram(long[] histogram)
|
||||
{
|
||||
var aggregated = new long[256];
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
long sum = 0;
|
||||
int startIndex = i * AggregationFactor;
|
||||
for (int j = 0; j < AggregationFactor; j++)
|
||||
{
|
||||
sum += histogram[startIndex + j];
|
||||
}
|
||||
aggregated[i] = sum;
|
||||
}
|
||||
return aggregated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新 Y 轴范围 | Update Y axis range
|
||||
/// </summary>
|
||||
private void UpdateYAxis(long[] displayData, bool isLogarithmic)
|
||||
{
|
||||
long maxValue = 0;
|
||||
for (int i = 0; i < displayData.Length; i++)
|
||||
{
|
||||
if (displayData[i] > maxValue)
|
||||
maxValue = displayData[i];
|
||||
}
|
||||
|
||||
if (maxValue == 0)
|
||||
maxValue = 1;
|
||||
|
||||
SetYAxisRange(0, maxValue, isLogarithmic);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置 Y 轴范围和刻度类型 | Set Y axis range and scale type
|
||||
/// </summary>
|
||||
private void SetYAxisRange(double minimum, double maximum, bool isLogarithmic)
|
||||
{
|
||||
// 获取或创建 Y 轴 | Get or create Y axis
|
||||
var verticalAxis = _chart.VerticalAxis;
|
||||
|
||||
if (isLogarithmic)
|
||||
{
|
||||
// 对数刻度 | Logarithmic scale
|
||||
if (verticalAxis is LogarithmicAxis logAxis)
|
||||
{
|
||||
logAxis.Minimum = 1;
|
||||
logAxis.Maximum = maximum;
|
||||
logAxis.LogarithmBase = 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 需要切换为对数轴 | Need to switch to logarithmic axis
|
||||
var newLogAxis = new LogarithmicAxis
|
||||
{
|
||||
Minimum = 1,
|
||||
Maximum = maximum,
|
||||
LogarithmBase = 10
|
||||
};
|
||||
_chart.VerticalAxis = newLogAxis;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 线性刻度 | Linear scale
|
||||
if (verticalAxis is LinearAxis linearAxis)
|
||||
{
|
||||
linearAxis.Minimum = minimum;
|
||||
linearAxis.Maximum = maximum;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 需要切换为线性轴 | Need to switch to linear axis
|
||||
var newLinearAxis = new LinearAxis
|
||||
{
|
||||
Minimum = minimum,
|
||||
Maximum = maximum
|
||||
};
|
||||
_chart.VerticalAxis = newLinearAxis;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 直方图数据点模型 | Histogram data point model
|
||||
/// </summary>
|
||||
internal class HistogramDataPoint
|
||||
{
|
||||
/// <summary>
|
||||
/// 灰度级别(X 轴值)| Gray level (X axis value)
|
||||
/// </summary>
|
||||
public double GrayLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 像素频次(Y 轴值)| Pixel frequency (Y axis value)
|
||||
/// </summary>
|
||||
public long Frequency { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user