Files
XplorePlane/XP.Common/Controls/ImageHistogram/ChartRenderer.cs
T

289 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 → 100000350000 → 500000780000 → 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; }
}
}