通用基础设施XP.Common新增 ImageHistogramControl 图像灰度直方图通用控件(使用SixLabors.ImageSharp 3.1.12),支持 Image<Rgba32> 和 byte[] 输入,支持多线程调用,Telerik RadChartView 渲染。
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace XP.Common.Controls.ImageHistogram
|
||||
{
|
||||
/// <summary>
|
||||
/// 直方图后台计算引擎 | Histogram background computation engine
|
||||
/// 负责在后台线程中执行灰度值遍历和统计计算
|
||||
/// </summary>
|
||||
internal sealed class HistogramEngine : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 单帧计算超时时间(毫秒)| Single frame computation timeout (ms)
|
||||
/// </summary>
|
||||
private const int ComputeTimeoutMs = 5000;
|
||||
|
||||
private CancellationTokenSource? _timeoutCts;
|
||||
private readonly object _lock = new();
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// 从 Image<Rgba32> 计算灰度直方图 | Compute histogram from Image<Rgba32>
|
||||
/// 使用 ITU-R BT.601 亮度公式:Gray = 0.299R + 0.587G + 0.114B
|
||||
/// </summary>
|
||||
/// <param name="image">输入图像 | Input image</param>
|
||||
/// <param name="ct">取消令牌 | Cancellation token</param>
|
||||
/// <returns>256 长度的频次数组,失败返回 null | 256-length frequency array, null on failure</returns>
|
||||
public Task<long[]?> ComputeAsync(Image<Rgba32> image, CancellationToken ct)
|
||||
{
|
||||
if (image == null)
|
||||
return Task.FromResult<long[]?>(null);
|
||||
|
||||
// 创建超时令牌 | Create timeout token
|
||||
var linkedCts = CreateLinkedTimeoutToken(ct);
|
||||
var linkedToken = linkedCts.Token;
|
||||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var width = image.Width;
|
||||
var height = image.Height;
|
||||
var histogram = new long[256];
|
||||
|
||||
// 遍历像素,使用亮度公式计算灰度值 | Iterate pixels, compute grayscale using luminance formula
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
linkedToken.ThrowIfCancellationRequested();
|
||||
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
var pixel = image[x, y];
|
||||
// ITU-R BT.601 亮度公式 | ITU-R BT.601 luminance formula
|
||||
var gray = (int)(0.299 * pixel.R + 0.587 * pixel.G + 0.114 * pixel.B);
|
||||
// 钳位到 0-255 范围 | Clamp to 0-255 range
|
||||
gray = Math.Clamp(gray, 0, 255);
|
||||
histogram[gray]++;
|
||||
}
|
||||
}
|
||||
|
||||
return (long[]?)histogram;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 超时或取消,返回 null | Timeout or cancelled, return null
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 所有异常内部捕获,不向外抛出 | Catch all exceptions internally
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
linkedCts.Dispose();
|
||||
}
|
||||
}, linkedToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从原始字节数组计算灰度直方图 | Compute 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>
|
||||
/// <param name="ct">取消令牌 | Cancellation token</param>
|
||||
/// <returns>频次数组(8位:256长度,16位:65536长度),失败返回 null | Frequency array, null on failure</returns>
|
||||
public Task<long[]?> ComputeAsync(byte[] rawData, int width, int height, int bitDepth, CancellationToken ct)
|
||||
{
|
||||
// 参数有效性验证 | Parameter validation
|
||||
if (rawData == null || width <= 0 || height <= 0)
|
||||
return Task.FromResult<long[]?>(null);
|
||||
|
||||
if (bitDepth != 8 && bitDepth != 16)
|
||||
return Task.FromResult<long[]?>(null);
|
||||
|
||||
int expectedLength = bitDepth == 8 ? width * height : width * height * 2;
|
||||
if (rawData.Length != expectedLength)
|
||||
return Task.FromResult<long[]?>(null);
|
||||
|
||||
// 创建超时令牌 | Create timeout token
|
||||
var linkedCts = CreateLinkedTimeoutToken(ct);
|
||||
var linkedToken = linkedCts.Token;
|
||||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (bitDepth == 8)
|
||||
{
|
||||
return ComputeHistogram8Bit(rawData, width, height, linkedToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ComputeHistogram16Bit(rawData, width, height, linkedToken);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
linkedCts.Dispose();
|
||||
}
|
||||
}, linkedToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算 8 位灰度直方图 | Compute 8-bit grayscale histogram
|
||||
/// </summary>
|
||||
private static long[]? ComputeHistogram8Bit(byte[] rawData, int width, int height, CancellationToken ct)
|
||||
{
|
||||
var histogram = new long[256];
|
||||
int totalPixels = width * height;
|
||||
|
||||
for (int i = 0; i < totalPixels; i++)
|
||||
{
|
||||
if (i % 65536 == 0)
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
histogram[rawData[i]]++;
|
||||
}
|
||||
|
||||
return histogram;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算 16 位灰度直方图 | Compute 16-bit grayscale histogram
|
||||
/// </summary>
|
||||
private static long[]? ComputeHistogram16Bit(byte[] rawData, int width, int height, CancellationToken ct)
|
||||
{
|
||||
var histogram = new long[65536];
|
||||
int totalPixels = width * height;
|
||||
|
||||
for (int i = 0; i < totalPixels; i++)
|
||||
{
|
||||
if (i % 65536 == 0)
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
// 小端序读取 16 位值 | Read 16-bit value in little-endian
|
||||
int offset = i * 2;
|
||||
ushort value = (ushort)(rawData[offset] | (rawData[offset + 1] << 8));
|
||||
histogram[value]++;
|
||||
}
|
||||
|
||||
return histogram;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建带超时的链接取消令牌 | Create linked cancellation token with timeout
|
||||
/// </summary>
|
||||
private CancellationTokenSource CreateLinkedTimeoutToken(CancellationToken externalToken)
|
||||
{
|
||||
var timeoutCts = new CancellationTokenSource(ComputeTimeoutMs);
|
||||
var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(externalToken, timeoutCts.Token);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_timeoutCts?.Dispose();
|
||||
_timeoutCts = timeoutCts;
|
||||
}
|
||||
|
||||
return linkedCts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源 | Dispose resources
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_timeoutCts?.Cancel();
|
||||
_timeoutCts?.Dispose();
|
||||
_timeoutCts = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user