using System;
using System.Buffers;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
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 字节足够;webp 需要 12 字节 (RIFF....WEBP), heic/avif 需要前 12 字节判断 ftyp brand。
private const int HeaderSize = 32;
/// <inheritdoc />
public ImageFormatInfo Detect(ReadOnlySpan<byte> bytes)
{
switch (bytes.Length)
{
case < 2:
return ImageFormatInfo.Unknown;
// JPEG: FF D8 FF
case >= 3 when bytes[0] == 0xFF && bytes[1] == 0xD8 && bytes[2] == 0xFF:
return 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 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 Gif;
}
// BMP: "BM"
if (bytes[0] == 0x42 && bytes[1] == 0x4D)
{
return 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 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 Tiff;
}
if (bytes[0] == 0x4D && bytes[1] == 0x4D && bytes[2] == 0x00 && bytes[3] == 0x2A)
{
return 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 Heic;
case (0x61, 0x76, 0x69, 0x66) or (0x61, 0x76, 0x69, 0x73):
return Avif;
}
}
// ICO: 00 00 01 00
if (
bytes.Length >= 4
&& bytes[0] == 0x00
&& bytes[1] == 0x00
&& bytes[2] == 0x01
&& bytes[3] == 0x00
)
{
return Ico;
}
return ImageFormatInfo.Unknown;
}
/// <inheritdoc />
public async ValueTask<ImageFormatInfo> 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 readonly ImageFormatInfo Jpeg = new("jpeg", "image/jpeg", "jpg");
private static readonly ImageFormatInfo Png = new("png", "image/png", "png");
private static readonly ImageFormatInfo Gif = new("gif", "image/gif", "gif");
private static readonly ImageFormatInfo Bmp = new("bmp", "image/bmp", "bmp");
private static readonly ImageFormatInfo Webp = new("webp", "image/webp", "webp");
private static readonly ImageFormatInfo Tiff = new("tiff", "image/tiff", "tiff");
private static readonly ImageFormatInfo Heic = new("heic", "image/heic", "heic");
private static readonly ImageFormatInfo Avif = new("avif", "image/avif", "avif");
private static readonly ImageFormatInfo Ico = new("ico", "image/x-icon", "ico");
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
这是一个基于文件头特征字节(Magic Number)的图片格式识别器类,用于检测各种常见的图片格式。以下是详细解释:
类的基本信息
类名: MagicNumberImageFormatDetector 命名空间: Dpz.Core.Infrastructure.Imaging 实现接口: IImageFormatDetector 特性: sealed(密封类,不可继承)
核心功能
1. 单例模式
public static readonly Lazy<MagicNumberImageFormatDetector> Instance = new(() =>
new MagicNumberImageFormatDetector()
);
- 使用懒加载单例模式
- 因为该类无状态,可以安全复用
2. 支持的图片格式
代码支持识别以下图片格式:
- JPEG (.jpg)
- PNG (.png)
- GIF (.gif)
- BMP (.bmp)
- WebP (.webp)
- TIFF (.tiff)
- HEIC (.heic) - 苹果的高效图片格式
- AVIF (.avif) - 新一代图片格式
- ICO (.ico) - 图标格式
3. 主要方法
Detect(ReadOnlySpan<byte> bytes)
- 功能: 同步检测字节数据的图片格式
- 参数: 图片文件的字节数据
- 返回:
ImageFormatInfo对象,包含格式信息
DetectAsync(Stream stream, CancellationToken cancellationToken)
- 功能: 异步从流中检测图片格式
- 特性: 支持取消令牌,自动恢复流位置
- 内存管理: 使用
ArrayPool<byte>避免频繁内存分配
识别原理
Magic Number 检测
每种图片格式都有独特的文件头特征字节:
// JPEG: 前3字节 FF D8 FF
case >= 3 when bytes[0] == 0xFF && bytes[1] == 0xD8 && bytes[2] == 0xFF:
return Jpeg;
// PNG: 前8字节 89 50 4E 47 0D 0A 1A 0A
case >= 8 when bytes[0] == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4E...
return Png;
特殊格式处理
RIFF容器格式(WebP):
- 检查 "RIFF" + "WEBP" 标识
ISO BMFF格式(HEIC/AVIF):
- 检查 "ftyp" 标识和品牌代码
- 根据不同的品牌代码区分HEIC和AVIF
TIFF格式:
- 支持大端和小端两种字节序
性能优化特性
- 内存池使用: 使用
ArrayPool<byte>.Shared减少GC压力 - 最小读取: 只读取必要的32字节头部数据
- 流位置恢复: 异步方法会自动恢复流的原始位置
- 无状态设计: 可以安全地复用实例
使用示例
// 使用单例
var detector = MagicNumberImageFormatDetector.Instance.Value;
// 从字节数组检测
byte[] imageBytes = File.ReadAllBytes("image.jpg");
var format = detector.Detect(imageBytes);
// 从流异步检测
using var stream = File.OpenRead("image.png");
var format = await detector.DetectAsync(stream);
这个类设计得非常实用,通过文件头特征字节快速准确地识别图片格式,支持现代和传统的各种图片格式,是一个高性能的图片格式检测器。
评论加载中...