feat&fix: 完善JPEGClient逻辑,前端新增编码器组件
This commit is contained in:
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user