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;

特殊格式处理

  1. RIFF容器格式(WebP):

    • 检查 "RIFF" + "WEBP" 标识
  2. ISO BMFF格式(HEIC/AVIF):

    • 检查 "ftyp" 标识和品牌代码
    • 根据不同的品牌代码区分HEIC和AVIF
  3. TIFF格式:

    • 支持大端和小端两种字节序

性能优化特性

  1. 内存池使用: 使用 ArrayPool<byte>.Shared 减少GC压力
  2. 最小读取: 只读取必要的32字节头部数据
  3. 流位置恢复: 异步方法会自动恢复流的原始位置
  4. 无状态设计: 可以安全地复用实例

使用示例

// 使用单例
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);

这个类设计得非常实用,通过文件头特征字节快速准确地识别图片格式,支持现代和传统的各种图片格式,是一个高性能的图片格式检测器。

评论加载中...