feat&fix: 完善JPEGClient逻辑,前端新增编码器组件

This commit is contained in:
alivender
2025-08-17 14:55:38 +08:00
parent 3644c75304
commit fdfc5729ec
5 changed files with 541 additions and 109 deletions

View File

@@ -258,12 +258,12 @@ public class Image
/// <summary>
/// 将原始 JPEG 数据补全 JPEG 头部,生成完整的 JPEG 图片
/// </summary>
/// <param name="jpegData">原始 JPEG 数据(可能缺少完整头部</param>
/// <param name="jpegData">原始 JPEG 扫描数据(不含头尾</param>
/// <param name="width">图像宽度</param>
/// <param name="height">图像高度</param>
/// <param name="quality">JPEG质量1-100默认80</param>
/// <param name="quantizationTable">量化表数组Y0-Y63, Cb0-Cb63, Cr0-Cr63共192个值</param>
/// <returns>完整的 JPEG 图片数据</returns>
public static Result<byte[]> CompleteJpegData(byte[] jpegData, int width, int height, int quality = 80)
public static Result<byte[]> CompleteJpegData(byte[] jpegData, int width, int height, uint[] quantizationTable)
{
if (jpegData == null)
return new(new ArgumentNullException(nameof(jpegData)));
@@ -271,95 +271,148 @@ public class Image
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"));
if (quantizationTable == null || quantizationTable.Length != 192)
return new(new ArgumentException("Quantization table must contain exactly 192 values (64 Y + 64 Cb + 64 Cr)"));
try
{
// 检查是否已经是完整的 JPEG 文件(以 FFD8 开头FFD9 结尾)
if (jpegData.Length >= 4 &&
jpegData[0] == 0xFF && jpegData[1] == 0xD8 &&
jpegData[jpegData.Length - 2] == 0xFF && jpegData[jpegData.Length - 1] == 0xD9)
var jpegBytes = new List<byte>();
// SOI (Start of Image)
jpegBytes.AddRange(new byte[] { 0xFF, 0xD8 });
// APP0 段
jpegBytes.AddRange(new byte[] {
0xFF, 0xE0, // APP0 marker
0x00, 0x10, // Length (16 bytes)
0x4A, 0x46, 0x49, 0x46, 0x00, // "JFIF\0"
0x01, 0x01, // Version 1.1
0x00, // Units: 0 = no units
0x00, 0x01, // X density (1)
0x00, 0x01, // Y density (1)
0x00, // Thumbnail width
0x00 // Thumbnail height
});
// DQT (Define Quantization Table) - Y table
jpegBytes.AddRange(new byte[] { 0xFF, 0xDB }); // DQT marker
jpegBytes.AddRange(new byte[] { 0x00, 0x43 }); // Length (67 bytes)
jpegBytes.Add(0x00); // Table ID (0 = Y table)
// 添加Y量化表 (quantizationTable[0-63])
for (int i = 0; i < 64; i++)
{
// 已经是完整的 JPEG 文件,直接返回
return jpegData;
jpegBytes.Add((byte)Math.Min(255, quantizationTable[i]));
}
// 创建一个临时的 RGB24 图像用于生成 JPEG 头部
using var tempImage = new SixLabors.ImageSharp.Image<Rgb24>(new Configuration
// DQT (Define Quantization Table) - CbCr table
jpegBytes.AddRange(new byte[] { 0xFF, 0xDB }); // DQT marker
jpegBytes.AddRange(new byte[] { 0x00, 0x43 }); // Length (67 bytes)
jpegBytes.Add(0x01); // Table ID (1 = CbCr table)
// 添加Cb量化表 (quantizationTable[64-127])但这里使用Cr表的数据作为CbCr共用
for (int i = 128; i < 192; i++) // 使用Cr量化表 (quantizationTable[128-191])
{
}, width, height);
// 填充临时图像(使用简单的渐变色作为占位符)
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
tempImage[x, y] = new Rgb24((byte)(x % 256), (byte)(y % 256), 128);
}
jpegBytes.Add((byte)Math.Min(255, quantizationTable[i]));
}
using var stream = new MemoryStream();
tempImage.SaveAsJpeg(stream, new JpegEncoder { Quality = quality });
var completeJpeg = stream.ToArray();
// SOF0 (Start of Frame)
jpegBytes.AddRange(new byte[] {
0xFF, 0xC0, // SOF0 marker
0x00, 0x11, // Length (17 bytes)
0x08, // Precision (8 bits)
(byte)((height >> 8) & 0xFF), (byte)(height & 0xFF), // Height
(byte)((width >> 8) & 0xFF), (byte)(width & 0xFF), // Width
0x03, // Number of components
0x01, 0x11, 0x00, // Y component
0x02, 0x11, 0x01, // Cb component
0x03, 0x11, 0x01 // Cr component
});
// 如果原始数据看起来是 JPEG 扫描数据,尝试替换扫描数据部分
if (jpegData.Length > 0)
{
// 查找 JPEG 扫描数据开始位置SOS 标记 0xFFDA 后)
int sosIndex = -1;
for (int i = 0; i < completeJpeg.Length - 1; i++)
{
if (completeJpeg[i] == 0xFF && completeJpeg[i + 1] == 0xDA)
{
// 跳过 SOS 段头部,找到实际扫描数据开始位置
i += 2; // 跳过 FF DA
if (i < completeJpeg.Length - 1)
{
int segmentLength = (completeJpeg[i] << 8) | completeJpeg[i + 1];
sosIndex = i + segmentLength;
break;
}
}
}
// DHT (Define Huffman Table) - DC Y table
jpegBytes.AddRange(new byte[] {
0xFF, 0xC4, // DHT marker
0x00, 0x1F, // Length
0x00, // Table class and ID (DC table 0)
// DC Y Huffman table
0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B
});
// 查找 EOI 标记位置0xFFD9
int eoiIndex = -1;
for (int i = completeJpeg.Length - 2; i >= 0; i--)
{
if (completeJpeg[i] == 0xFF && completeJpeg[i + 1] == 0xD9)
{
eoiIndex = i;
break;
}
}
// DHT (Define Huffman Table) - AC Y table (简化版)
jpegBytes.AddRange(new byte[] {
0xFF, 0xC4, // DHT marker
0x00, 0xB5, // Length
0x10 // Table class and ID (AC table 0)
});
// AC Y Huffman table数据
jpegBytes.AddRange(new byte[] {
0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D,
0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0,
0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5,
0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2,
0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8,
0xF9, 0xFA
});
if (sosIndex > 0 && eoiIndex > sosIndex)
{
// 替换扫描数据部分
var headerLength = sosIndex;
var footerStart = eoiIndex;
var footerLength = completeJpeg.Length - footerStart;
// DHT (Define Huffman Table) - DC CbCr table
jpegBytes.AddRange(new byte[] {
0xFF, 0xC4, // DHT marker
0x00, 0x1F, // Length
0x01, // Table class and ID (DC table 1)
// DC CbCr Huffman table
0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B
});
var newJpegLength = headerLength + jpegData.Length + footerLength;
var newJpegData = new byte[newJpegLength];
// DHT (Define Huffman Table) - AC CbCr table
jpegBytes.AddRange(new byte[] {
0xFF, 0xC4, // DHT marker
0x00, 0xB5, // Length
0x11 // Table class and ID (AC table 1)
});
// AC CbCr Huffman table数据与AC Y table相同
jpegBytes.AddRange(new byte[] {
0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D,
0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0,
0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5,
0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2,
0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8,
0xF9, 0xFA
});
// 复制头部
Array.Copy(completeJpeg, 0, newJpegData, 0, headerLength);
// SOS (Start of Scan)
jpegBytes.AddRange(new byte[] {
0xFF, 0xDA, // SOS marker
0x00, 0x0C, // Length (12 bytes)
0x03, // Number of components
0x01, 0x00, // Y component, DC/AC table
0x02, 0x11, // Cb component, DC/AC table
0x03, 0x11, // Cr component, DC/AC table
0x00, 0x3F, 0x00 // Start of spectral, End of spectral, Ah/Al
});
// 复制原始扫描数据
Array.Copy(jpegData, 0, newJpegData, headerLength, jpegData.Length);
// 添加原始 JPEG 扫描数据
jpegBytes.AddRange(jpegData);
// 复制尾部
Array.Copy(completeJpeg, footerStart, newJpegData, headerLength + jpegData.Length, footerLength);
// EOI (End of Image)
jpegBytes.AddRange(new byte[] { 0xFF, 0xD9 });
return newJpegData;
}
}
// 如果无法智能合并,返回完整的模板 JPEG
return completeJpeg;
return jpegBytes.ToArray();
}
catch (Exception ex)
{