using System;
using System.Buffers;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Dpz.Core.Entity.Base.Image;
namespace Dpz.Core.Infrastructure.Imaging;
/// <summary>
/// 基于 magic-byte (文件头特征字节) 的图片格式识别器。
/// 支持 jpg / png / gif / webp / bmp / tiff / tga / heic / avif / ico 等常见格式。
/// </summary>
public sealed class MagicNumberImageFormatDetector : IImageFormatDetector
{
/// <summary>
/// 单例实例,无状态可复用。
/// </summary>
public static readonly Lazy<MagicNumberImageFormatDetector> Instance = new(() =>
new MagicNumberImageFormatDetector()
);
// 二进制格式通常 12 字节足够;SVG 可能带 XML 声明或注释,因此多读一点文本头。
private const int HeaderSize = 512;
/// <inheritdoc />
public ImageFormat Detect(ReadOnlySpan<byte> bytes)
{
switch (bytes.Length)
{
case < 2:
return ImageFormat.Unknown;
// JPEG: FF D8 FF
case >= 3 when bytes[0] == 0xFF && bytes[1] == 0xD8 && bytes[2] == 0xFF:
return ImageFormat.Jpeg;
// PNG: 89 50 4E 47 0D 0A 1A 0A
case >= 8
when bytes[0] == 0x89
&& bytes[1] == 0x50
&& bytes[2] == 0x4E
&& bytes[3] == 0x47
&& bytes[4] == 0x0D
&& bytes[5] == 0x0A
&& bytes[6] == 0x1A
&& bytes[7] == 0x0A:
return ImageFormat.Png;
// GIF: "GIF87a" or "GIF89a"
case >= 6
when bytes[0] == 0x47
&& bytes[1] == 0x49
&& bytes[2] == 0x46
&& bytes[3] == 0x38
&& (bytes[4] == 0x37 || bytes[4] == 0x39)
&& bytes[5] == 0x61:
return ImageFormat.Gif;
}
// BMP: "BM"
if (bytes[0] == 0x42 && bytes[1] == 0x4D)
{
return ImageFormat.Bmp;
}
// RIFF container: "RIFF" .... "WEBP"
if (
bytes.Length >= 12
&& bytes[0] == 0x52
&& bytes[1] == 0x49
&& bytes[2] == 0x46
&& bytes[3] == 0x46
&& bytes[8] == 0x57
&& bytes[9] == 0x45
&& bytes[10] == 0x42
&& bytes[11] == 0x50
)
{
return ImageFormat.Webp;
}
// TIFF: "II*\0" (little-endian) or "MM\0*" (big-endian)
if (bytes.Length >= 4)
{
if (bytes[0] == 0x49 && bytes[1] == 0x49 && bytes[2] == 0x2A && bytes[3] == 0x00)
{
return ImageFormat.Tiff;
}
if (bytes[0] == 0x4D && bytes[1] == 0x4D && bytes[2] == 0x00 && bytes[3] == 0x2A)
{
return ImageFormat.Tiff;
}
}
// ISO BMFF (heic/avif): bytes 4..7 = "ftyp", bytes 8..11 = brand
if (
bytes.Length >= 12
&& bytes[4] == 0x66
&& bytes[5] == 0x74
&& bytes[6] == 0x79
&& bytes[7] == 0x70
)
{
// heic / heix / hevc / hevx / mif1 / msf1 -> HEIC family
// avif / avis -> AVIF family
var brand = (bytes[8], bytes[9], bytes[10], bytes[11]);
switch (brand)
{
// msf1
case
(0x68, 0x65, 0x69, 0x63) // heic
or
(0x68, 0x65, 0x69, 0x78) // heix
or
(0x68, 0x65, 0x76, 0x63) // hevc
or
(0x68, 0x65, 0x76, 0x78) // hevx
or
(0x6D, 0x69, 0x66, 0x31) // mif1
or (0x6D, 0x73, 0x66, 0x31):
return ImageFormat.Heic;
case (0x61, 0x76, 0x69, 0x66) or (0x61, 0x76, 0x69, 0x73):
return ImageFormat.Avif;
}
}
// ICO: 00 00 01 00
if (
bytes.Length >= 4
&& bytes[0] == 0x00
&& bytes[1] == 0x00
&& bytes[2] == 0x01
&& bytes[3] == 0x00
)
{
return ImageFormat.Ico;
}
if (LooksLikeSvg(bytes))
{
return ImageFormat.Svg;
}
return ImageFormat.Unknown;
}
/// <inheritdoc />
public async ValueTask<ImageFormat> DetectAsync(
Stream stream,
CancellationToken cancellationToken = default
)
{
ArgumentNullException.ThrowIfNull(stream);
var buffer = ArrayPool<byte>.Shared.Rent(HeaderSize);
try
{
long? originalPosition = null;
if (stream.CanSeek)
{
originalPosition = stream.Position;
}
var read = 0;
while (read < HeaderSize)
{
var n = await stream
.ReadAsync(buffer.AsMemory(read, HeaderSize - read), cancellationToken)
.ConfigureAwait(false);
if (n == 0)
{
break;
}
read += n;
}
if (originalPosition.HasValue)
{
stream.Position = originalPosition.Value;
}
return Detect(buffer.AsSpan(0, read));
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}
private static bool LooksLikeSvg(ReadOnlySpan<byte> bytes)
{
if (bytes.Length < 4)
{
return false;
}
var sampleLength = Math.Min(bytes.Length, HeaderSize);
var text = Encoding.UTF8.GetString(bytes[..sampleLength]).TrimStart('\uFEFF').TrimStart();
if (text.Length == 0)
{
return false;
}
return StartsWithSvgElement(SkipXmlPreamble(text));
}
private static bool StartsWithSvgElement(string text)
{
if (!text.StartsWith("<svg", StringComparison.OrdinalIgnoreCase))
{
return false;
}
return text.Length == 4 || char.IsWhiteSpace(text[4]) || text[4] is '>' or '/';
}
private static string SkipXmlPreamble(string text)
{
while (text.Length > 0)
{
if (text.StartsWith("<?", StringComparison.Ordinal))
{
var end = text.IndexOf("?>", StringComparison.Ordinal);
if (end < 0)
{
return text;
}
text = text[(end + 2)..].TrimStart();
continue;
}
if (text.StartsWith("<!--", StringComparison.Ordinal))
{
var end = text.IndexOf("-->", StringComparison.Ordinal);
if (end < 0)
{
return text;
}
text = text[(end + 3)..].TrimStart();
continue;
}
if (text.StartsWith("<!DOCTYPE", StringComparison.OrdinalIgnoreCase))
{
var end = text.IndexOf('>');
if (end < 0)
{
return text;
}
text = text[(end + 1)..].TrimStart();
continue;
}
return text;
}
return text;
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
这是一个专业的图像格式检测器类,基于文件头的"魔术字节"(magic bytes)来识别不同的图像格式。让我详细解释这个代码:
主要功能
这个类实现了 IImageFormatDetector 接口,用于识别各种常见的图像格式,包括:
- JPEG, PNG, GIF, WebP, BMP, TIFF
- 现代格式:HEIC, AVIF, ICO
- 矢量格式:SVG
核心设计特点
1. 单例模式
public static readonly Lazy<MagicNumberImageFormatDetector> Instance = new(() =>
new MagicNumberImageFormatDetector()
);
使用懒加载单例,因为这个检测器是无状态的,可以安全复用。
2. 魔术字节检测原理
每种图像格式都有特定的文件头标识:
- JPEG:
FF D8 FF - PNG:
89 50 4E 47 0D 0A 1A 0A("PNG"的变体) - GIF:
47 49 46 38 37/39 61("GIF87a"或"GIF89a") - BMP:
42 4D("BM") - WebP: RIFF容器格式,包含"RIFF"和"WEBP"标识
关键方法分析
3. 同步检测方法 Detect
使用 switch 表达式和模式匹配,高效地检测不同格式:
case >= 3 when bytes[0] == 0xFF && bytes[1] == 0xD8 && bytes[2] == 0xFF:
return ImageFormat.Jpeg;
这种写法既检查了长度又验证了特定字节序列。
4. 现代格式支持
对于 HEIC/AVIF 等基于 ISO BMFF 容器的格式:
// 检测 "ftyp" 标识符,然后根据 brand 确定具体格式
var brand = (bytes[8], bytes[9], bytes[10], bytes[11]);
5. 异步流处理 DetectAsync
- 使用
ArrayPool<byte>.Shared避免频繁内存分配 - 支持可定位流的位置恢复
- 正确处理取消令牌
- 循环读取确保获得足够的头部数据
6. SVG 特殊处理
SVG 是文本格式,需要特殊处理:
- 跳过 UTF-8 BOM 标记
- 处理 XML 声明 (
<?xml ... ?>) - 处理注释 (
<!-- ... -->) - 处理 DOCTYPE 声明
- 最终检查是否以
<svg开头
性能优化亮点
- 最小读取量: 只读取 512 字节的文件头,对大文件很高效
- 短路求值: 按照常见程度排序检测条件
- 内存池: 异步方法使用
ArrayPool减少 GC 压力 - 零分配: 同步方法使用
ReadOnlySpan<byte>避免不必要的内存分配
错误处理
- 对空流参数抛出
ArgumentNullException - 优雅处理流读取异常
- 确保资源正确释放(try-finally 模式)
这个实现展现了现代 C# 的最佳实践,包括性能优化、内存管理和异步编程模式。
AI 正在分析代码…
评论加载中...