289 lines
10 KiB
C#
289 lines
10 KiB
C#
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 ScatterAreaSeries _areaSeries;
|
||
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="areaSeries">面积图系列 | Area series</param>
|
||
/// <param name="xAxis">X 轴 | X axis</param>
|
||
public ChartRenderer(RadCartesianChart chart, ScatterAreaSeries areaSeries, LinearAxis xAxis)
|
||
{
|
||
_chart = chart ?? throw new ArgumentNullException(nameof(chart));
|
||
_areaSeries = areaSeries ?? throw new ArgumentNullException(nameof(areaSeries));
|
||
_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;
|
||
// 根据范围自动设置刻度间隔(保持 4-5 个刻度)| Auto set major step based on range (keep 4-5 ticks)
|
||
_xAxis.MajorStep = xAxisMax <= 255 ? 64 : 16384;
|
||
|
||
// 构建数据点 | 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
|
||
_areaSeries.ItemsSource = dataPoints;
|
||
|
||
// 设置 Y 轴范围 | Set Y axis range
|
||
UpdateYAxis(displayData, isLogarithmic);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清空图表,恢复初始状态 | Clear chart, restore initial state
|
||
/// </summary>
|
||
public void Clear()
|
||
{
|
||
_areaSeries.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 (_areaSeries.ItemsSource is ICollection<HistogramDataPoint> collection)
|
||
return collection.Count;
|
||
if (_areaSeries.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;
|
||
|
||
// 计算取整的 MajorStep(约 4 个刻度,对齐到 K/M 整数倍)| Calculate rounded MajorStep (~4 ticks, aligned to K/M multiples)
|
||
if (_chart.VerticalAxis is LinearAxis linearAxis)
|
||
{
|
||
long rawStep = maxValue / 4;
|
||
long step = RoundStepToNice(rawStep);
|
||
if (step < 1) step = 1;
|
||
linearAxis.MajorStep = step;
|
||
|
||
// 将最大值向上取整到 step 的整数倍 | Round max up to multiple of step
|
||
long roundedMax = ((maxValue / step) + 1) * step;
|
||
SetYAxisRange(0, roundedMax, isLogarithmic);
|
||
}
|
||
else
|
||
{
|
||
SetYAxisRange(0, maxValue, isLogarithmic);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将步长取整为"好看"的数值(1, 2, 5 的倍数 × 10^n)| Round step to "nice" value (multiples of 1, 2, 5 × 10^n)
|
||
/// 例如:123456 → 100000,350000 → 500000,780000 → 1000000
|
||
/// </summary>
|
||
private static long RoundStepToNice(long rawStep)
|
||
{
|
||
if (rawStep <= 0) return 1;
|
||
|
||
// 找到数量级 | Find order of magnitude
|
||
double magnitude = Math.Pow(10, Math.Floor(Math.Log10(rawStep)));
|
||
double normalized = rawStep / magnitude;
|
||
|
||
// 取整到 1, 2, 5 中最近的 | Round to nearest of 1, 2, 5
|
||
double niceNormalized;
|
||
if (normalized <= 1.5)
|
||
niceNormalized = 1;
|
||
else if (normalized <= 3.5)
|
||
niceNormalized = 2;
|
||
else if (normalized <= 7.5)
|
||
niceNormalized = 5;
|
||
else
|
||
niceNormalized = 10;
|
||
|
||
return (long)(niceNormalized * magnitude);
|
||
}
|
||
|
||
/// <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; }
|
||
}
|
||
}
|