This repository has been archived on 2025-10-29. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
FPGA_WebLab/server/src/Common/Image.cs

359 lines
12 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.Text;
using DotNext;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats;
namespace Common;
/// <summary>
/// 图像处理工具
/// </summary>
public class Image
{
/// <summary>
/// 将 RGB565 格式转换为 RGB24 格式
/// RGB565: 5位红色 + 6位绿色 + 5位蓝色 = 16位 (2字节)
/// RGB24: 8位红色 + 8位绿色 + 8位蓝色 = 24位 (3字节)
/// </summary>
/// <param name="rgb565Data">RGB565格式的原始数据</param>
/// <param name="width">图像宽度</param>
/// <param name="height">图像高度</param>
/// <param name="isLittleEndian">是否为小端序默认为true</param>
/// <returns>RGB24格式的转换后数据</returns>
public static Result<byte[]> ConvertRGB565ToRGB24(byte[] rgb565Data, int width, int height, bool isLittleEndian = true)
{
if (rgb565Data == null)
return new(new ArgumentNullException(nameof(rgb565Data)));
if (width <= 0 || height <= 0)
return new(new ArgumentException("Width and height must be positive"));
// 计算像素数量
var expectedPixelCount = width * height;
var actualPixelCount = rgb565Data.Length / 2;
if (actualPixelCount < expectedPixelCount)
{
return new(new ArgumentException(
$"RGB565 data length insufficient. Expected: {expectedPixelCount * 2} bytes, Actual: {rgb565Data.Length} bytes"));
}
try
{
var pixelCount = Math.Min(actualPixelCount, expectedPixelCount);
var rgb24Data = new byte[pixelCount * 3];
for (int i = 0; i < pixelCount; i++)
{
// 读取 RGB565 数据
var rgb565Index = i * 2;
if (rgb565Index + 1 >= rgb565Data.Length) break;
// 组合成16位值
UInt16 rgb565;
if (isLittleEndian)
{
rgb565 = (UInt16)(rgb565Data[rgb565Index] | (rgb565Data[rgb565Index + 1] << 8));
}
else
{
rgb565 = (UInt16)((rgb565Data[rgb565Index] << 8) | rgb565Data[rgb565Index + 1]);
}
// 提取各颜色分量
var r5 = (rgb565 >> 11) & 0x1F; // 高5位为红色
var g6 = (rgb565 >> 5) & 0x3F; // 中间6位为绿色
var b5 = rgb565 & 0x1F; // 低5位为蓝色
// 转换为8位颜色值
var r8 = (byte)((r5 * 255) / 31); // 5位扩展到8位
var g8 = (byte)((g6 * 255) / 63); // 6位扩展到8位
var b8 = (byte)((b5 * 255) / 31); // 5位扩展到8位
// 存储到 RGB24 数组
var rgb24Index = i * 3;
rgb24Data[rgb24Index] = r8; // R
rgb24Data[rgb24Index + 1] = g8; // G
rgb24Data[rgb24Index + 2] = b8; // B
}
return rgb24Data;
}
catch (Exception ex)
{
return new(ex);
}
}
/// <summary>
/// 将 RGB24 格式转换为 RGB565 格式
/// RGB24: 8位红色 + 8位绿色 + 8位蓝色 = 24位 (3字节)
/// RGB565: 5位红色 + 6位绿色 + 5位蓝色 = 16位 (2字节)
/// </summary>
/// <param name="rgb24Data">RGB24格式的原始数据</param>
/// <param name="width">图像宽度</param>
/// <param name="height">图像高度</param>
/// <param name="isLittleEndian">是否为小端序默认为true</param>
/// <returns>RGB565格式的转换后数据</returns>
public static Result<byte[]> ConvertRGB24ToRGB565(byte[] rgb24Data, int width, int height, bool isLittleEndian = true)
{
if (rgb24Data == null)
return new(new ArgumentNullException(nameof(rgb24Data)));
if (width <= 0 || height <= 0)
return new(new ArgumentException("Width and height must be positive"));
var expectedPixelCount = width * height;
var actualPixelCount = rgb24Data.Length / 3;
if (actualPixelCount < expectedPixelCount)
{
return new(new ArgumentException(
$"RGB24 data length insufficient. Expected: {expectedPixelCount * 3} bytes, Actual: {rgb24Data.Length} bytes"));
}
try
{
var pixelCount = Math.Min(actualPixelCount, expectedPixelCount);
var rgb565Data = new byte[pixelCount * 2];
for (int i = 0; i < pixelCount; i++)
{
var rgb24Index = i * 3;
if (rgb24Index + 2 >= rgb24Data.Length) break;
// 读取 RGB24 数据
var r8 = rgb24Data[rgb24Index];
var g8 = rgb24Data[rgb24Index + 1];
var b8 = rgb24Data[rgb24Index + 2];
// 转换为5位、6位、5位
var r5 = (UInt16)((r8 * 31) / 255);
var g6 = (UInt16)((g8 * 63) / 255);
var b5 = (UInt16)((b8 * 31) / 255);
// 组合成16位值
var rgb565 = (UInt16)((r5 << 11) | (g6 << 5) | b5);
// 存储到 RGB565 数组
var rgb565Index = i * 2;
if (isLittleEndian)
{
rgb565Data[rgb565Index] = (byte)(rgb565 & 0xFF);
rgb565Data[rgb565Index + 1] = (byte)(rgb565 >> 8);
}
else
{
rgb565Data[rgb565Index] = (byte)(rgb565 >> 8);
rgb565Data[rgb565Index + 1] = (byte)(rgb565 & 0xFF);
}
}
return rgb565Data;
}
catch (Exception ex)
{
return new(ex);
}
}
/// <summary>
/// 将 RGB24 数据转换为 JPEG 格式
/// </summary>
/// <param name="rgb24Data">RGB24格式的图像数据</param>
/// <param name="width">图像宽度</param>
/// <param name="height">图像高度</param>
/// <param name="quality">JPEG质量1-100默认80</param>
/// <returns>JPEG格式的字节数组</returns>
public static Result<byte[]> ConvertRGB24ToJpeg(byte[] rgb24Data, int width, int height, int quality = 80)
{
if (rgb24Data == null)
return new(new ArgumentNullException(nameof(rgb24Data)));
if (width <= 0 || height <= 0)
return new(new ArgumentException("Width and height must be positive"));
if (quality < 1 || quality > 100)
return new(new ArgumentException("Quality must be between 1 and 100"));
var expectedDataLength = width * height * 3;
if (rgb24Data.Length < expectedDataLength)
{
return new(new ArgumentException(
$"RGB24 data length insufficient. Expected: {expectedDataLength} bytes, Actual: {rgb24Data.Length} bytes"));
}
try
{
using var image = new SixLabors.ImageSharp.Image<Rgb24>(width, height);
// 将 RGB 数据复制到 ImageSharp 图像
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int index = (y * width + x) * 3;
if (index + 2 < rgb24Data.Length)
{
var pixel = new Rgb24(rgb24Data[index], rgb24Data[index + 1], rgb24Data[index + 2]);
image[x, y] = pixel;
}
}
}
using var stream = new MemoryStream();
image.SaveAsJpeg(stream, new JpegEncoder { Quality = quality });
return stream.ToArray();
}
catch (Exception ex)
{
return new(ex);
}
}
/// <summary>
/// 将 RGB565 数据直接转换为 JPEG 格式
/// </summary>
/// <param name="rgb565Data">RGB565格式的图像数据</param>
/// <param name="width">图像宽度</param>
/// <param name="height">图像高度</param>
/// <param name="quality">JPEG质量1-100默认80</param>
/// <param name="isLittleEndian">是否为小端序默认为true</param>
/// <returns>JPEG格式的字节数组</returns>
public static Result<byte[]> ConvertRGB565ToJpeg(byte[] rgb565Data, int width, int height, int quality = 80, bool isLittleEndian = true)
{
// 先转换为RGB24
var rgb24Result = ConvertRGB565ToRGB24(rgb565Data, width, height, isLittleEndian);
if (!rgb24Result.IsSuccessful)
{
return new(rgb24Result.Error);
}
// 再转换为JPEG
return ConvertRGB24ToJpeg(rgb24Result.Value, width, height, quality);
}
/// <summary>
/// 创建 MJPEG 帧头部
/// </summary>
/// <param name="frameDataLength">帧数据长度</param>
/// <param name="boundary">边界字符串(默认为"--boundary"</param>
/// <returns>MJPEG帧头部字节数组</returns>
public static byte[] CreateMjpegFrameHeader(int frameDataLength, string boundary = "--boundary")
{
var header = $"{boundary}\r\nContent-Type: image/jpeg\r\nContent-Length: {frameDataLength}\r\n\r\n";
return Encoding.ASCII.GetBytes(header);
}
/// <summary>
/// 创建 MJPEG 帧尾部
/// </summary>
/// <returns>MJPEG帧尾部字节数组</returns>
public static byte[] CreateMjpegFrameFooter()
{
return Encoding.ASCII.GetBytes("\r\n");
}
/// <summary>
/// 创建完整的 MJPEG 帧数据
/// </summary>
/// <param name="jpegData">JPEG数据</param>
/// <param name="boundary">边界字符串(默认为"--boundary"</param>
/// <returns>完整的MJPEG帧数据</returns>
public static Result<byte[]> CreateMjpegFrame(byte[] jpegData, string boundary = "--boundary")
{
if (jpegData == null)
return new(new ArgumentNullException(nameof(jpegData)));
try
{
var header = CreateMjpegFrameHeader(jpegData.Length, boundary);
var footer = CreateMjpegFrameFooter();
var totalLength = header.Length + jpegData.Length + footer.Length;
var frameData = new byte[totalLength];
var offset = 0;
Array.Copy(header, 0, frameData, offset, header.Length);
offset += header.Length;
Array.Copy(jpegData, 0, frameData, offset, jpegData.Length);
offset += jpegData.Length;
Array.Copy(footer, 0, frameData, offset, footer.Length);
return frameData;
}
catch (Exception ex)
{
return new(ex);
}
}
/// <summary>
/// 验证图像数据长度是否正确
/// </summary>
/// <param name="data">图像数据</param>
/// <param name="width">图像宽度</param>
/// <param name="height">图像高度</param>
/// <param name="bytesPerPixel">每像素字节数</param>
/// <returns>验证结果</returns>
public static bool ValidateImageDataLength(byte[] data, int width, int height, int bytesPerPixel)
{
if (data == null || width <= 0 || height <= 0 || bytesPerPixel <= 0)
return false;
var expectedLength = width * height * bytesPerPixel;
return data.Length >= expectedLength;
}
/// <summary>
/// 获取图像格式信息
/// </summary>
/// <param name="format">图像格式枚举</param>
/// <returns>格式信息</returns>
public static ImageFormatInfo GetImageFormatInfo(ImageFormat format)
{
return format switch
{
ImageFormat.RGB565 => new ImageFormatInfo("RGB565", 2, "16-bit RGB format (5R+6G+5B)"),
ImageFormat.RGB24 => new ImageFormatInfo("RGB24", 3, "24-bit RGB format (8R+8G+8B)"),
ImageFormat.RGBA32 => new ImageFormatInfo("RGBA32", 4, "32-bit RGBA format (8R+8G+8B+8A)"),
ImageFormat.Grayscale8 => new ImageFormatInfo("Grayscale8", 1, "8-bit grayscale format"),
_ => new ImageFormatInfo("Unknown", 0, "Unknown image format")
};
}
}
/// <summary>
/// 图像格式枚举
/// </summary>
public enum ImageFormat
{
/// <summary>
/// RGB565
/// </summary>
RGB565,
/// <summary>
/// RGB888 / RGB24
/// </summary>
RGB24,
/// <summary>
/// RGBA8888 / RGBA32
/// </summary>
RGBA32,
/// <summary>
/// 灰度图
/// </summary>
Grayscale8
}
/// <summary>
/// 图像格式信息
/// </summary>
public record ImageFormatInfo(string Name, int BytesPerPixel, string Description);