必须先检查图片是否支持exif:jpeg通常支持,png/webp基本不带,gif完全不支持;调用getpropertyitem前需验证image.propertyidlist.contains(271);优先用image.fromfile(path, false)避免元数据丢失;经纬度需手动解析三组有理数并结合ref字段判断正负;.net 6+跨平台需安装libgdiplus或改用metadataextractor库。

用 Image.GetPropertyItem 读取 Exif 标签前必须检查支持性
不是所有图片格式都完整保留 Exif,也不是所有 Image 实例都能直接读取。JPEG 通常可以,PNG 和 WebP 基本不带 Exif(即使有也常被解码器丢弃),GIF 完全不支持。调用 GetPropertyItem 前务必先确认图片是否含该 ID:image.PropertyIdList.Contains(271)(271 是制造商字段,可快速验证 Exif 是否可用)。
常见错误现象:ArgumentException: Property not found —— 不是因为 ID 写错,而是图片本身没嵌入对应 Exif 数据,或加载时被 GDI+ 自动剥离(比如从剪贴板粘贴、经某些缩略图 API 加载)。
- 优先用
Image.FromFile(path, false),第二个参数设为false防止自动解码丢弃元数据 - 避免用
Bitmap.Clone()或绘图后保存——这会彻底丢失 Exif - 对网络流加载的图片,确保原始字节未被解码重编码;推荐用
MemoryStream包裹原始 byte[] 后传给Image.FromStream
提取经纬度要手动解析 GPSLatitude 和 GPSLongitude 字段
Exif 中的经纬度不是字符串或浮点数,而是三组有理数(Rational,即分子/分母组成的 uint 数组),分别存于 PropertyTagGpsLatitude(ID 2 = 0x2)和 PropertyTagGpsLongitude(ID 4 = 0x4)。C# 的 GetPropertyItem 返回的是原始字节,需自己按 TIFF 格式规则解析。
容易踩的坑:直接把字节数组当 ASCII 字符串转——结果是乱码;或误以为 Value 字段是已解码的 double——它只是原始二进制。
-
PropertyTagGpsLatitudeRef(ID 1)和PropertyTagGpsLongitudeRef(ID 3)决定正负:'N'/'S' 和 'E'/'W' - 每组经纬度占 3 × 8 字节(三个
uint32分子 + 三个uint32分母),共 6 个值,需按顺序两两配对计算度分秒 - 示例片段:
var lat = DecodeRationalArray(prop.Value, 0); // 取前 3 对,其中DecodeRationalArray需自行实现除法逻辑
System.Drawing.Common 在 .NET 6+ Linux/macOS 上默认禁用 Exif 支持
即使代码逻辑完全正确,在非 Windows 平台运行时仍可能返回空或抛出 NotSupportedException——因为 System.Drawing.Common 底层依赖平台图像库(libgdiplus),而其 Exif 解析模块常被编译时关闭或运行时缺失依赖。
验证方法:在目标环境执行 var prop = image.GetPropertyItem(271),若抛异常或返回 null,基本可判定是运行时限制。
- Linux 需安装
libgdiplus(Ubuntu/Debian:`apt install libgdiplus`),且版本 ≥ 6.0.4 - macOS 上更不稳定,建议改用跨平台方案(见下一条)
- 容器部署时注意基础镜像是否包含对应 native 依赖,Alpine 镜像默认不支持
生产环境建议换用 MetadataExtractor 库替代原生 System.Drawing
System.Drawing 对 Exif 的支持本质是 GDI+ 的副产品,脆弱、不透明、平台差异大。而 MetadataExtractor(NuGet 包名:MetadataExtractor)专为元数据设计,纯托管实现,不依赖系统库,能稳定读取 JPEG/TIFF/RAW 等格式的 GPS、时间、相机型号等全部字段。
关键差异:它返回的是结构化对象(如 GpsDirectory),经纬度直接暴露为 double,无需手动解析 Rational 数组。
- 安装:
dotnet add package MetadataExtractor - 用法极简:
var directories = ImageMetadataReader.ReadMetadata(fileStream); var gps = directories.OfType<gpsdirectory>().FirstOrDefault(); var lat = gps?.GetGeoLocation()?.Latitude;</gpsdirectory> - 性能影响小:仅解析元数据区,不加载像素数据;比完整解码图片快一个数量级
实际项目里,Exif 的“存在性”比“怎么读”更难保障——同一张手机照片,用微信发一遍、用 QQ 压缩一遍、再保存到相册,Exif 很可能就只剩创建时间了。别在运行时假设它一定在。










