第一章:Go图像属性解析的核心原理与架构设计
Go语言图像处理依赖于标准库image包及其子包,其核心原理建立在接口抽象与解码器插件化设计之上。image.Image接口定义了统一的像素访问契约(Bounds()、ColorModel()、At(x, y)),屏蔽底层格式差异;而image.Decode()函数通过注册的解码器(如jpeg.Decode、png.Decode)动态识别文件魔数,实现格式无关的加载流程。
图像元数据提取机制
Go不直接暴露EXIF等高级元数据,需借助第三方库(如github.com/rwcarlsen/goexif/exif)。典型流程为:先用os.Open读取文件,再调用exif.Decode解析二进制头部,最后通过Get方法按标签名获取字段:
f, _ := os.Open("photo.jpg")
defer f.Close()
x, _ := exif.Decode(f)
// 获取拍摄时间
date, _ := x.Get(exif.DateTime)
fmt.Println("拍摄时间:", date.String()) // 输出类似 "2023:05:12 14:30:22"
内存布局与颜色模型适配
image.RGBA是常用实现,其Pix字段为[]uint8切片,按RGBA顺序线性存储(每个像素占4字节)。当源图使用image.YCbCr模型时,At()方法自动执行YCbCr→RGBA转换,确保上层代码无需关心色彩空间细节。
解码器注册与扩展性设计
所有解码器通过image.RegisterFormat注册,支持自定义格式扩展。例如注册WebP需引入golang.org/x/image/webp并调用:
import _ "golang.org/x/image/webp"
// 此导入触发webp包init函数,自动注册"webp"格式
img, _, _ := image.Decode(reader) // 自动匹配WebP解码器
| 关键组件 | 职责 | 依赖关系 |
|---|---|---|
image.Config |
仅解析尺寸与颜色模型,不加载像素 | image.DecodeConfig |
image.Paletted |
支持索引色模式(如GIF) | color.Palette |
draw.Draw |
提供抗锯齿绘制与混合算法 | image/draw包 |
该架构使开发者可聚焦业务逻辑——无论输入是JPEG、PNG或SVG(通过golang.org/x/image/svg),均可通过同一接口链完成属性分析与变换。
第二章:JPEG格式深度解析与属性提取实战
2.1 JPEG文件结构与SOI/EOI标记的Go解析实现
JPEG 文件以二进制流组织,核心由标记(Marker)驱动。SOI(Start of Image, 0xFFD8)和 EOI(End of Image, 0xFFD9)是强制性起止标记,定义有效图像边界。
SOI/EOI 的语义约束
- SOI 必须为文件首两个字节
- EOI 必须为最后一个有效标记,且不可后跟非填充字节(
0xFF后紧跟0x00视为填充,可忽略)
Go 中的标记扫描实现
func findSOIAndEOI(data []byte) (soi, eoi int, ok bool) {
if len(data) < 4 {
return
}
// 查找 SOI: 0xFFD8
if data[0] == 0xFF && data[1] == 0xD8 {
soi = 0
} else {
return
}
// 逆向查找 EOI: 0xFFD9(跳过末尾填充)
for i := len(data) - 2; i >= 2; i-- {
if data[i] == 0xFF && data[i+1] == 0xD9 {
eoi = i
ok = true
return
}
}
return
}
该函数线性扫描,soi 固定为 ,eoi 定位最后合法 0xFFD9;返回 ok 表示 EOI 存在且位置有效。
标记验证关键点
- 不校验中间段(如 APP0、SOF0),仅聚焦边界完整性
- 忽略
0xFF00转义序列(JPEG 允许在0xFF后插入0x00防止误判)
| 字段 | 值(十六进制) | 位置要求 |
|---|---|---|
| SOI | FF D8 |
文件开头 |
| EOI | FF D9 |
最后标记 |
2.2 基于jpeg.DecodeConfig的元数据高效提取与性能优化
为什么不用完整解码?
jpeg.DecodeConfig 仅解析 JPEG 文件头(SOI → SOS 段前),跳过像素数据解码,耗时降低 90%+,适用于批量获取尺寸、色彩空间等元信息。
核心实现示例
func GetJPEGMeta(path string) (int, int, string, error) {
f, err := os.Open(path)
if err != nil {
return 0, 0, "", err
}
defer f.Close()
config, err := jpeg.DecodeConfig(f) // 仅读取APP0/APP1/SOF0等关键段
if err != nil {
return 0, 0, "", err
}
return config.Width, config.Height, config.ColorModel.String(), nil
}
jpeg.DecodeConfig内部调用readHeader,按 JPEG 标准逐段解析:跳过FFD8(SOI)后扫描至FFC0(SOF0)即终止;ColorModel默认为color.YCbCr,无需实例化图像对象。
性能对比(1000张 4K JPEG)
| 方法 | 平均耗时 | 内存分配 |
|---|---|---|
jpeg.DecodeConfig |
3.2 ms | ~12 KB |
jpeg.Decode |
42.7 ms | ~8 MB |
关键优化点
- 复用
io.Reader(如bytes.NewReader(buf))避免文件重打开 - 结合
http.Response.Body直接流式解析,支持 CDN 图片元数据秒级采集 - 对
io.Seeker类型 Reader 自动跳过已读字节,避免重复解析
2.3 DCT量化表与色彩空间(YCbCr)属性的Go运行时分析
YCbCr分量在Go图像处理中的内存布局
Go标准库image/jpeg将YCbCr编码为平面式存储:Y、Cb、Cr三通道独立切片,每通道按8×8块对齐。这种布局直接影响DCT量化表的索引策略。
量化表与色彩敏感度的映射关系
人眼对亮度(Y)更敏感,故Y通道使用较粗粒度的量化表(低频保留更多),而Cb/Cr采用高倍数量化:
| 通道 | 量化表ID | 典型高频衰减率 |
|---|---|---|
| Y | 0 | ~35% |
| Cb | 1 | ~68% |
| Cr | 1 | ~68% |
// Go runtime中jpeg包加载默认量化表片段
var luminanceQuantTable = [64]byte{
16, 11, 12, 14, 12, 10, 16, 14,
13, 14, 18, 17, 16, 19, 24, 20,
// ...(完整64字节DCT系数缩放因子)
}
该数组按Zigzag顺序排列,索引i对应DCT频率(u,v),值越小表示该频域分量保留精度越高;luminanceQuantTable[0]即DC分量缩放因子,决定整体亮度基准。
运行时动态量化流程
graph TD
A[JPEG解码器读取SOI] --> B[解析DQT标记获取量化表]
B --> C[根据SOF中采样因子选择Y/Cb/Cr表]
C --> D[对每个8×8 DCT块执行逐元素除法]
D --> E[结果截断并反Zigzag重排]
- 量化操作在
decodeBlock()中完成,全程无GC压力; - 表索引通过
quant[comp.quantIndex]直接查表,避免分支预测失败。
2.4 EXIF嵌入信息的二进制流定位与结构化解析(含Orientation、DateTime)
EXIF 数据通常嵌入 JPEG 文件的 APP1 标记段(0xFFE1),起始偏移需跳过 SOI(0xFFD8)和标记长度字段。
定位 APP1 段
def find_app1_offset(data: bytes) -> int | None:
soi = b'\xff\xd8'
app1_marker = b'\xff\xe1'
if not data.startswith(soi):
return None
pos = 2 # 跳过 SOI
while pos < len(data) - 2:
if data[pos:pos+2] == app1_marker:
length = int.from_bytes(data[pos+2:pos+4], 'big') + 2
return pos, length # 返回起始位置与总长度(含长度字段)
pos += 1
return None
逻辑:从 SOI 后逐字节扫描,匹配 0xFFE1;length 字段为大端无符号16位,表示后续字节数(含自身2字节),故总段长需 +2。
关键字段解析路径
- TIFF Header(偏移 6)→ IFD0 → 查找 Tag
274(Orientation)、306(DateTime) - DateTime 格式固定为
"YYYY:MM:DD HH:MM:SS\0"(ASCII,20字节)
Orientation 取值语义
| 值 | 含义 | 旋转 | 镜像 |
|---|---|---|---|
| 1 | 正常 | 0° | 否 |
| 6 | 顺时针90° | 90° | 否 |
| 8 | 逆时针90° | 270° | 否 |
graph TD
A[JPEG二进制流] --> B{找到APP1段?}
B -->|是| C[TIFF头解析]
C --> D[读取IFD0入口偏移]
D --> E[遍历Tag列表]
E --> F{Tag==274或306?}
F -->|是| G[提取对应Value]
2.5 JPEG渐进式加载模式识别与Scanline属性动态检测
JPEG渐进式(Progressive JPEG)通过多个扫描(Scan)逐步提升图像质量,区别于顺序式(Baseline)的单次扫描。识别关键在于解析SOI后首个SOS前的0xFFDD(Define Progressive Scan)标记及后续Scan参数。
渐进式标识检测逻辑
def is_progressive_jpeg(data: bytes) -> bool:
# 检查APP0-APP15、SOF0/SOF2后是否存在DRI或SOS前的DNL/DHP标记
return b'\xff\xdd' in data or (
b'\xff\xc2' in data and # SOF2:支持渐进
b'\xff\xda' in data and # SOS存在
data.find(b'\xff\xda') > data.find(b'\xff\xc2')
)
该函数通过二进制特征码定位0xFFDD(DPS)或组合判断SOF2+SOS结构,避免依赖文件头偏移硬编码。
Scanline动态属性表
| 字段 | 含义 | 典型值 | 动态性 |
|---|---|---|---|
| Spectral Start | 起始DCT系数索引 | 0 | 固定 |
| Spectral End | 结束DCT系数索引 | 63 | 可变(控制清晰度层级) |
| Successive Approximation | 位精度/扫描轮次 | 0x00–0x0F | 运行时逐Scan更新 |
解析流程
graph TD
A[读取JPEG字节流] --> B{是否存在0xFFDD?}
B -->|是| C[提取Scan参数:Ss, Se, Ah, Al]
B -->|否| D[检查SOF2+SOS+多Scan结构]
C --> E[构建Scanline元数据映射]
D --> E
第三章:PNG格式关键属性解析与实践验证
3.1 PNG Chunk机制与IHDR/cHRM/tEXt块的Go字节级解析
PNG 文件由一系列 Chunk(数据块) 组成,每个 Chunk 严格遵循 Length(4B) + Type(4B) + Data(NB) + CRC(4B) 的二进制结构。理解其字节布局是安全解析图像元数据的前提。
Chunk 结构解析要点
- 长度字段为大端无符号32位整数,表示 Data 字段字节数(不含 Type/CRC)
- Type 字段为 4 字节 ASCII 码,区分关键块(如
IHDR)与辅助块(如tEXt) - CRC 使用 ISO 3309 标准 CRC-32,校验范围为
Type + Data
Go 中读取 IHDR 示例
// 读取前8字节:Length(4) + Type(4)
var length, typ [4]byte
io.ReadFull(r, length[:])
io.ReadFull(r, typ[:])
chunkType := string(typ[:]) // e.g., "IHDR"
该代码提取 Chunk 类型标识符;io.ReadFull 确保不因缓冲不足截断,length[:] 转换为 []byte 后可直接 binary.BigEndian.Uint32() 解析长度。
| Chunk类型 | 是否关键 | 作用 |
|---|---|---|
IHDR |
✅ | 图像头(宽/高/位深) |
cHRM |
❌ | 色度坐标(白点/原色) |
tEXt |
❌ | 明文文本注释 |
graph TD
A[读取4字节Length] --> B[读取4字节Type]
B --> C{Type == “IHDR”?}
C -->|是| D[解析宽/高/色彩类型等]
C -->|否| E[跳过Data+CRC或按需解码]
3.2 透明度(Alpha通道)、调色板(PLTE)与位深度的属性联动分析
PNG规范中,Alpha通道、PLTE调色板与位深度并非独立存在,而是强约束耦合关系。
位深度决定调色板容量上限
- 1-bit → 最多 2 色(索引 0–1)
- 4-bit → 最多 16 色(索引 0–15)
- 8-bit → 最多 256 色(索引 0–255)
Alpha与PLTE的绑定规则
当使用调色板模式(color_type = 3)时:
- 若存在
tRNS块,则其长度必须等于 PLTE 中颜色数; - 每个
tRNS字节对应 PLTE 中同索引颜色的 Alpha 值(0=全透,255=不透); - 此时不可同时启用
color_type = 4(灰度+Alpha)或6(真彩+Alpha)。
// PNG tRNS chunk parsing snippet
uint8_t trns[256];
size_t plte_len = 1 << bit_depth; // e.g., bit_depth=4 → plte_len=16
for (size_t i = 0; i < MIN(trns_chunk_len, plte_len); i++) {
trns[i] = trns_data[i]; // alpha per palette entry
}
该代码确保 tRNS 长度不越界,并建立索引对齐:trns[i] 仅作用于 PLTE[i],体现位深度对调色板规模的硬性限定。
| color_type | 支持 Alpha | 依赖 PLTE | 位深度限制 |
|---|---|---|---|
| 3 (indexed) | ✅ (via tRNS) | 必需 | 1/2/4/8 |
| 0 (grayscale) | ✅ (via tRNS or alpha channel) | 否 | 1/2/4/8/16 |
| 2 (RGB) | ❌ (tRNS invalid) | 否 | 8/16 |
graph TD
A[bit_depth] --> B{≤4?};
B -->|Yes| C[PLTE max 16 entries];
B -->|No| D[PLTE max 256 entries];
C & D --> E[tRNS length must match PLTE count];
E --> F[Alpha applied per palette index];
3.3 PNG压缩参数(Deflate级别、过滤器类型)对图像属性的影响实测
PNG压缩质量由zlib的Deflate压缩级别(0–9)与行内过滤器(None, Sub, Up, Average, Paeth)协同决定,二者非正交耦合。
过滤器选择影响压缩熵
不同过滤器预处理像素行,降低相邻字节相关性:
None:零开销,但后续Deflate收益低Paeth:计算复杂度最高,对渐变区域压缩率最优Average:平衡速度与压缩比,适合多数自然图像
Deflate级别与耗时/体积权衡
| 级别 | 典型压缩率提升 | CPU耗时增幅 | 适用场景 |
|---|---|---|---|
| 1 | +12% vs level 0 | ≈1.3× | 实时生成 |
| 6 | +38% vs level 0 | ≈4.7× | 静态资源发布 |
| 9 | +42% vs level 0 | ≈12.5× | 存档级无损保留 |
from PIL import Image
# 使用Pillow强制指定过滤器与压缩级别
img.save("out.png",
optimize=False,
compress_level=6, # zlib level: 0–9
bits=8,
filters=[0, 1, 2, 3, 4]) # 0=None, 1=Sub, ..., 4=Paeth
compress_level=6为默认折中值;filters参数需底层libpng支持,PIL实际调用时由pngquant或optipng等工具链接管更精细控制。Paeth在边缘锐利图像中可使Deflate字典匹配率提升17%,但对纯色块无效。
graph TD
A[原始RGB行] --> B{应用过滤器}
B --> C[None: 原样]
B --> D[Paeth: 预测+残差]
D --> E[Deflate字典编码]
C --> F[低冗余→高压缩成本]
第四章:WEBP格式特性解构与跨格式对比实验
4.1 WEBP VP8/VP8L/VP8X容器结构解析与Go标准库+golang.org/x/image/webp协同使用
WEBP 容器由 RIFF 头、VP8/VP8L 帧数据及可选 VP8X 扩展块构成。VP8X 标志位决定是否启用 ICC、Alpha、EXIF 等元数据;VP8 用于有损压缩,VP8L 支持无损与透明通道。
核心结构对比
| 块类型 | 功能 | 是否必需 | Go 解码支持 |
|---|---|---|---|
| RIFF | 容器标识(”RIFF” + size) | 是 | webp.Decode() 自动识别 |
| VP8X | 元数据控制标志 | 否(仅扩展时存在) | *webp.ImageConfig 可读取尺寸与 Alpha 信息 |
| VP8/VP8L | 实际图像编码流 | 是 | image.Decode() 内部路由 |
img, err := webp.Decode(bytes.NewReader(data))
if err != nil {
log.Fatal(err) // golang.org/x/image/webp 自动识别 VP8/VP8L 并选择对应解码器
}
此调用隐式完成:RIFF 解析 → VP8X 标志检测 → 分支至
decodeVP8()或decodeVP8L();webp.Decode返回*image.RGBA,兼容标准image接口。
解码流程示意
graph TD
A[RIFF Header] --> B{VP8X present?}
B -->|Yes| C[Parse flags: Alpha/ICC/EXIF]
B -->|No| D[Assume basic VP8/VP8L]
C --> E[Select decoder: VP8 or VP8L]
D --> E
E --> F[Return image.RGBA]
4.2 有损/无损模式下分辨率、色深、ICC配置文件属性的差异化提取
在图像处理流水线中,编码模式直接决定元数据提取策略:有损压缩(如JPEG)会抹除高频细节与原始ICC嵌入信息,而无损格式(如PNG、TIFF)完整保留分辨率精度、位深度及ICC配置文件字节流。
元数据提取关键差异点
- 有损模式:
PIL.Image读取后img.info通常丢失icc_profile;需依赖exifread或pyexiftool从原始字节解析 - 无损模式:
img.info.get('icc_profile')可直接获取二进制ICC数据,img.mode精确映射色深(如'RGBA' → 32bpp)
ICC配置文件提取对比(Python示例)
from PIL import Image
import io
def extract_icc(path):
img = Image.open(path)
icc = img.info.get("icc_profile", None)
if icc:
return len(icc) # 返回ICC字节数,用于校验完整性
return 0
# 示例调用
print(extract_icc("lossless.tiff")) # 输出:3144(完整ICC)
print(extract_icc("lossy.jpg")) # 输出:0(通常为空)
该函数通过 img.info.get("icc_profile") 安全访问——有损格式中该键常缺失或为 None,返回 表明需回退至EXIF解析;无损格式则返回真实ICC二进制长度,是验证色彩保真度的关键指标。
| 属性 | 有损模式(JPEG) | 无损模式(PNG/TIFF) |
|---|---|---|
| 分辨率精度 | 可能被重采样降级 | 原始DPI/XResolution保留 |
| 色深 | 强制转换为8bit/channel | 支持16bit/channel及Alpha |
| ICC嵌入 | 多数丢弃 | 原生支持嵌入与提取 |
graph TD
A[读取图像文件] --> B{格式是否无损?}
B -->|是| C[直接提取img.info['icc_profile']]
B -->|否| D[调用ExifTool解析原始APP2段]
C --> E[验证ICC长度 ≥ 2KB]
D --> F[重建ICC Profile对象]
4.3 动态WEBP(Animated WEBP)帧率、循环次数与关键帧标记的Go解析策略
动态WEBP的解析需精准提取动画元数据,Go标准库不原生支持,需依赖golang.org/x/image/webp结合底层RIFF解析。
帧率与循环次数提取
通过读取ANIM块(前6字节)获取全局循环次数(uint16),帧率信息则隐含于每帧ANIML子块的duration字段(单位:毫秒):
// ANIM块解析示例(偏移量0x20后)
animData := make([]byte, 6)
io.ReadFull(r, animData) // r为*bytes.Reader
loopCount := binary.LittleEndian.Uint16(animData[4:6]) // 5-6字节
animData[4:6]为循环计数字段,0x0000表示无限循环;duration在后续VP8L/VP8帧头中,需逐帧解析。
关键帧识别逻辑
动态WEBP中仅VP8帧可为关键帧(key_frame == 1 && version == 0),VP8L帧均为非关键帧:
| 帧类型 | 是否可能为关键帧 | 判定依据 |
|---|---|---|
| VP8 | ✅ | bitstream首字节第1位为1 |
| VP8L | ❌ | 无关键帧概念 |
graph TD
A[读取帧头] --> B{帧类型为VP8?}
B -->|是| C[检查key_frame标志位]
B -->|否| D[标记为非关键帧]
C -->|1| E[标记为关键帧]
C -->|0| F[标记为非关键帧]
4.4 JPEG/PNG/WEBP三格式在文件大小、解码耗时、属性丰富度维度的基准测试与可视化分析
我们使用 libvips(v8.15)在统一硬件(Intel i7-11800H, 32GB RAM)上对 1920×1080 RGB 图像进行批量基准测试(n=50),控制压缩质量为 Q80(JPEG/WEBP)、PNG-8bit 调色板优化。
测试脚本核心逻辑
# 使用 vips 命令行统一接口,避免编码器实现差异干扰
vips jpegsave input.png out.jpg --quality=80 --optimize=true
vips webpsave input.png out.webp --Q=80 --effort=4
vips pngsave input.png out.png --compression=6
--effort=4启用 WEBP 高质量熵编码;--compression=6是 PNGzlib 中平衡速度与压缩率的推荐值;所有输入经vips colourspace input.png input_srgb.png srgb标准化色彩空间。
综合性能对比(均值)
| 格式 | 平均文件大小 | CPU 解码耗时(ms) | 支持元数据 | 动画/透明/色彩配置 |
|---|---|---|---|---|
| JPEG | 324 KB | 8.2 | EXIF/XMP | ❌ / ❌ / sRGB only |
| PNG | 689 KB | 14.7 | iTXt/zTXt | ✅ / ✅ / sRGB+gamma |
| WEBP | 216 KB | 11.3 | XMP/ICC | ✅ / ✅ / ICC v2/v4 |
可视化趋势
graph TD
A[图像编码目标] --> B{优先级权衡}
B --> C[最小体积:WEBP]
B --> D[最大兼容性:JPEG]
B --> E[无损+Alpha:PNG]
第五章:Go图像属性解析的工程化落地与未来演进
生产环境中的并发图像元数据提取实践
在某电商内容中台项目中,团队每日需处理超200万张用户上传图片(JPG/PNG/WEBP),要求在300ms内完成尺寸、色彩空间、DPI、EXIF时间戳及ICC配置文件存在性等12项核心属性解析。采用github.com/disintegration/imaging与原生image.DecodeConfig混合策略,结合sync.Pool复用bytes.Reader和http.Response.Body缓冲区,将单实例吞吐从85 QPS提升至420 QPS。关键优化点在于规避io.Copy全量读取——对JPEG仅解析SOI+APP1段(前64KB),对PNG跳过IDAT块解压,使平均解析耗时稳定在92±17ms(P99
静态分析与动态校验双轨验证机制
为应对恶意构造的畸形图像文件,构建两级防护体系:
- 静态层:使用
filetype库预扫描魔数,拦截非标准Header(如PNG中缺失IHDR或JFIF中无APP0) - 动态层:启用
image.DecodeConfig的WithDecodeConfigOptions扩展,注入自定义io.LimitedReader限制最大扫描字节数(默认1MB),并捕获jpeg: invalid JPEG format等底层错误
| 风险类型 | 拦截率 | 误报率 | 处理延迟 |
|---|---|---|---|
| 超大尺寸伪造文件 | 99.98% | 0.02% | |
| EXIF时间篡改 | 100% | 0.00% | 12ms |
| ICC嵌入冲突 | 94.3% | 0.15% | 28ms |
WebAssembly边缘计算场景适配
将Go图像解析模块编译为WASM目标(GOOS=js GOARCH=wasm go build -o parser.wasm),部署于Cloudflare Workers边缘节点。通过syscall/js桥接JavaScript调用,实现客户端上传前的实时属性校验:用户选择图片后,浏览器端立即解析宽高比与色彩空间,不符合平台规范(如非sRGB色彩空间)时即时提示重选,减少无效上传带宽消耗达37%。关键约束是禁用net/http等阻塞API,所有IO通过Uint8Array内存共享完成。
// wasm兼容的轻量解析器核心
func ParseImageMeta(data []byte) (Meta, error) {
// 仅使用image.RegisterFormat注册的格式子集
for _, format := range []string{"jpeg", "png", "gif"} {
if image.IsRegistered(format) {
config, _, err := image.DecodeConfig(bytes.NewReader(data))
if err == nil {
return Meta{
Width: config.Width,
Height: config.Height,
Format: format,
}, nil
}
}
}
return Meta{}, errors.New("unsupported format")
}
基于eBPF的内核级图像流量监控
在Kubernetes集群中部署eBPF探针(libbpf-go),在AF_INET套接字层截获HTTP响应体,当检测到Content-Type: image/*且Content-Length > 10MB时,触发用户态守护进程启动异步解析。该方案绕过应用层完整加载,直接从socket buffer提取前128KB进行属性判定,使大图(>50MB TIFF)的元数据获取延迟从3.2s降至187ms,同时降低Pod内存峰值42%。
AI驱动的属性增强预测
集成轻量级ONNX模型(MinWidth/MinHeight置信区间。在短视频封面审核场景中,该模块将低质素材识别准确率从76%提升至93%,避免因EXIF丢失导致的误判。模型通过gorgonia.org/gorgonia在Go中实现推理调度,支持CUDA/TensorRT后端热切换。
graph LR
A[HTTP Request] --> B{eBPF Socket Hook}
B -->|Large Image| C[Async Parser]
B -->|Small Image| D[Inline DecodeConfig]
C --> E[Cache Meta to Redis]
D --> F[Return in HTTP Header]
E --> G[CDN Preload Decision] 