EXIF (Exchangeable Image File Format) 嵌入 JPEG 影像檔頭,記錄拍攝參數 (光圈、快門、ISO、白平衡)、時間、相機型號、GPS 座標等。.NET 透過 Image.PropertyItems 存取。
| ID (Hex) | 名稱 | 說明 | 型別 |
| 0x010E | ImageDescription | 影像描述 | ASCII |
| 0x010F | Make | 相機製造商 | ASCII |
| 0x0110 | Model | 相機型號 | ASCII |
| 0x0131 | Software | 處理軟體 | ASCII |
| 0x0132 | DateTime | 修改時間 (YYYY:MM:DD HH:MM:SS) | ASCII |
| 0x829A | ExposureTime | 曝光時間 (秒,有理數) | RATIONAL |
| 0x829D | FNumber | 光圈值 (F-number,有理數) | RATIONAL |
| 0x8827 | ISOSpeedRatings | ISO 感光度 | SHORT |
| 0x9000 | ExifVersion | EXIF 版本 | UNDEFINED |
| 0x9003 | DateTimeOriginal | 原始拍攝時間 | ASCII |
| 0x9004 | DateTimeDigitized | 數位化時間 | ASCII |
| 0x920A | FocalLength | 焦距 (mm,有理數) | RATIONAL |
| 0xA001 | ColorSpace | 色彩空間 (1=sRGB) | SHORT |
| 0xA432 | LensModel | 鏡頭型號 | ASCII |
| 0x8825 | GPSInfo | GPS 資訊子 IFD 指標 | LONG |
完整解析程式碼
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text;
public class ExifReader
{
public ExifData Read(string filePath)
{
using (var img = Image.FromFile(filePath))
{
var data = new ExifData();
foreach (var prop in img.PropertyItems)
{
switch (prop.Id)
{
case 0x010F: data.Make = GetString(prop); break;
case 0x0110: data.Model = GetString(prop); break;
case 0x0132: data.DateTime = GetString(prop); break;
case 0x9003: data.DateTimeOriginal = GetString(prop); break;
case 0x829A: data.ExposureTime = GetRational(prop); break;
case 0x829D: data.FNumber = GetRational(prop); break;
case 0x8827: data.ISOSpeed = GetShort(prop); break;
case 0x920A: data.FocalLength = GetRational(prop); break;
case 0xA432: data.LensModel = GetString(prop); break;
case 0x8825: data.GpsInfo = ParseGps(prop, img.PropertyItems); break;
}
}
return data;
}
}
private string GetString(PropertyItem prop)
{
// ASCII 字串以 null 結尾,移除結尾 \0
return Encoding.ASCII.GetString(prop.Value).TrimEnd('\0');
}
private double GetRational(PropertyItem prop)
{
// Rational = 兩個 uint (分子、分母)
if (prop.Value.Length < 8) return 0;
uint num = BitConverter.ToUInt32(prop.Value, 0);
uint den = BitConverter.ToUInt32(prop.Value, 4);
return den == 0 ? 0 : (double)num / den;
}
private short GetShort(PropertyItem prop)
{
return BitConverter.ToInt16(prop.Value, 0);
}
private GpsData ParseGps(PropertyItem gpsPtr, PropertyItem[] allProps)
{
// GPS 資訊存放在子 IFD,需對應子標籤 (0x0001~0x001D)
// 這裡簡化:實際需遞迴解析子目錄,建議使用 MetadataExtractor 套件
return new GpsData();
}
}
public class ExifData
{
public string Make { get; set; }
public string Model { get; set; }
public string DateTime { get; set; }
public string DateTimeOriginal { get; set; }
public double ExposureTime { get; set; } // 秒
public double FNumber { get; set; }
public short ISOSpeed { get; set; }
public double FocalLength { get; set; } // mm
public string LensModel { get; set; }
public GpsData GpsInfo { get; set; }
public override string ToString()
{
return $"{Make} {Model}\n拍攝時間: {DateTimeOriginal}\n光圈: f/{FNumber:F1}\n快門: 1/{1/ExposureTime:F0}s\nISO: {ISOSpeed}\n焦距: {FocalLength}mm\n鏡頭: {LensModel}";
}
}
public class GpsData
{
public double Latitude { get; set; }
public double Longitude { get; set; }
public double Altitude { get; set; }
}
使用範例
var reader = new ExifReader();
var exif = reader.Read(@"C:\Photos\DSC_001.jpg");
Console.WriteLine(exif);
// 輸出範例:
// NIKON D850
// 拍攝時間: 2024:03:15 14:30:22
// 光圈: f/2.8
// 快門: 1/250s
// ISO: 100
// 焦距: 85mm
// 鏡頭: AF-S NIKKOR 85mm f/1.4G
進階建議:使用 MetadataExtractor 套件
手寫解析 GPS、MakerNote (廠商私有標籤) 非常繁瑣。建議安裝 NuGet:
Install-Package MetadataExtractor
using MetadataExtractor;
using MetadataExtractor.Formats.Exif;
var directories = ImageMetadataReader.ReadMetadata(filePath);
var exifDir = directories.OfType().FirstOrDefault();
var model = exifDir?.GetDescription(ExifDirectoryBase.TagModel);
var dateTime = exifDir?.GetDescription(ExifDirectoryBase.TagDateTime);
var gpsDir = directories.OfType().FirstOrDefault();
var lat = gpsDir?.GetGeoLocation()?.Latitude;
var lon = gpsDir?.GetGeoLocation()?.Longitude;
注意事項
Image.FromFile 會鎖定檔案至物件 Dispose,若需移動/刪除檔案,請先複製到 MemoryStream:using var ms = new MemoryStream(File.ReadAllBytes(path)); using var img = Image.FromStream(ms);
- 部分影像無 EXIF (截圖、網頁圖片),
PropertyItems 可能為空或拋例外,需 try-catch。
- 大量批次處理時,考慮平行化
Parallel.ForEach。
- 上一篇文章: C# TreeView 建立多層級樹狀目錄完整實作:資料庫設計與遞迴載入
- 下一篇文章: C# 2.0 語言規範深度解析:匿名方法完整指南
Copyright ©
程式碼之家 All Rights Reserved