C++ Builder 通过 easyexif 读取照片 EXI
- 读取 JPEG 图片的 EXIF 信息
- 读取任意格式图片 (例如 TIFF 或苹果 HEIC 等) 的 EXIF 信息
- Windows 32-bit 和 Windows 64-bit 平台测试通过
- 下载本文例子源码
1. 下载 easyexif
easyexif 源码和例子在这里下载:https://github.com/mayanklahiri/easyexif
2. 把 easyexif 添加到 C++ Builder 项目
下载的 easyexif 解压缩之后,在 easyexif-master 里面的主要文件:
- exif.h 头文件
- exif.cpp 源码
- demo.cpp 例子程序
只要有前面两个 exif.h 和 exif.cpp 就可以在项目里面使用了,把 exif.cpp 添加到已有项目,如下截图所示:
把 exif.cpp 添加到项目3. 在 Form 上添加控件
在 Form 上放控件:
StringGrid1 用于显示 EXIF 信息;
Label1 用于显示文件名;
OpenDialog1 用于选择打开图片文件;
Button1:是按照 easyexif 的例子读取 JPEG 图片的 EXIF 信息;
Button2:读取任意图片的 EXIF 信息。
4. 修改 Form 的头文件
在 Form 的 .h 文件里面需要添加
#include "exif.h"
在 Form 的 .h 文件的 Form 类里面添加几个需要使用的函数声明:
这些函数主要用来添加 EXIF 信息到 StringGrid1 里面,把一些 EXIF 里面的编码信息转为可读的信息,把错误编码转为可读的信息等。
private: // User declarations
void ShowExif(const easyexif::EXIFInfo &Exif);
void ShowGridLine(int iRowIdx, const UnicodeString sKey, const std::string &sValue);
void ShowGridLine(int iRowIdx, const UnicodeString sKey, const UnicodeString sValue);
UnicodeString OrientationDesc(int iOrientation); // 拍照方向
UnicodeString ExposureTimeDesc(double lfExposureTime); // 曝光时间
UnicodeString ExposureProgDesc(unsigned short uExposureProgram); // 曝光模式
UnicodeString FlashReturnedLightDesc(unsigned short uFlashReturnedLight); // 闪光灯反馈信号
UnicodeString FlashModeDesc(unsigned short uFlashMode); // 闪光模式
UnicodeString MeteringModeDesc(unsigned short uMeteringMode); // 测光模式
UnicodeString ParseResultDesc(int iResult); // 解析错误编码转提示信息
5. 修改 Form 的 .cpp 文件
Form 的 .cpp 文件:
__fastcall TFormHsuanluExif::TFormHsuanluExif(TComponent* Owner)
: TForm(Owner)
{
Font->Name = L"微软雅黑";
Font->Size = 9;
double lfRatio = std::abs(StringGrid1->Font->Height)/12.0;
if(lfRatio<1.0)lfRatio = 1.0;
StringGrid1->ColCount = 3;
StringGrid1->ColWidths[0] = 40 * lfRatio + 0.5;
StringGrid1->ColWidths[1] = 150 * lfRatio + 0.5;
StringGrid1->ColWidths[2] = 300 * lfRatio + 0.5;
StringGrid1->RowCount = 2;
StringGrid1->Cells[0][0] = L"序号";
StringGrid1->Cells[1][0] = L"项目";
StringGrid1->Cells[2][0] = L"数值";
}
//---------------------------------------------------------------------------
UnicodeString TFormHsuanluExif::OrientationDesc(int iOrientation) // 拍照方向
{
switch(iOrientation)
{
case 1: return L"0°"; // 1: upper left of image
case 2: return L"0° + 镜像";
case 3: return L"180°"; // 3: lower right of image
case 4: return L"180° + 镜像";
case 5: return L"顺时针 90° + 镜像";
case 6: return L"顺时针 90°"; // 6: upper right of image
case 7: return L"逆时针 90° + 镜像";
case 8: return L"逆时针 90°"; // 8: lower left of image
default: return UnicodeString().sprintf(L"未知拍照方向 (%d)", iOrientation); // 0: unspecified in EXIF data // 9: undefined
}
}
//---------------------------------------------------------------------------
UnicodeString TFormHsuanluExif::ExposureTimeDesc(double lfExposureTime) // 曝光时间
{
UnicodeString sDesc;
if(lfExposureTime > 0.0)
sDesc.sprintf(L"1/%.0f 秒", 1.0/lfExposureTime);
else
sDesc = lfExposureTime;
return sDesc;
}
//---------------------------------------------------------------------------
UnicodeString TFormHsuanluExif::ExposureProgDesc(unsigned short uExposureProgram) // 曝光模式
{
switch(uExposureProgram)
{
case 1: return L"手动"; break; // 1: Manual
case 2: return L"自动"; break; // 2: Normal program
case 3: return L"光圈优先"; break; // 3: Aperture priority
case 4: return L"快门优先"; break; // 4: Shutter priority
case 5: return L"艺术"; break; // 5: Creative program
case 6: return L"动作"; break; // 6: Action program
case 7: return L"肖像"; break; // 7: Portrait mode
case 8: return L"风景"; break; // 8: Landscape mode
default: return UnicodeString().sprintf(L"未知曝光模式 (%u)", uExposureProgram); break; // 0: Not defined
}
}
//---------------------------------------------------------------------------
UnicodeString TFormHsuanluExif::FlashReturnedLightDesc(unsigned short uFlashReturnedLight) // 闪光灯反馈信号
{
switch(uFlashReturnedLight)
{
case 0: return L"无信号反馈功能"; // 0: No strobe return detection function
// case 1: return L"预留 (1)"; // 1: Reserved
case 2: return L"未检测到反馈信号"; // 2: Strobe return light not detected
case 3: return L"检测到反馈信号"; // 3: Strobe return light detected
default: return UnicodeString().sprintf(L"未知信号反馈模式 (%u)", uFlashReturnedLight);
}
}
//---------------------------------------------------------------------------
UnicodeString TFormHsuanluExif::FlashModeDesc(unsigned short uFlashMode) // 闪光模式
{
switch(uFlashMode)
{
case 1: return L"启用 (强行触发)"; // 1: Compulsory flash firing
case 2: return L"禁用 (强行抑制)"; // 2: Compulsory flash suppression
case 3: return L"自动"; // 3: Automatic mode
default: return UnicodeString().sprintf(L"未知闪光模式 (%u)", uFlashMode); // 0: Unknown
}
}
//---------------------------------------------------------------------------
UnicodeString TFormHsuanluExif::MeteringModeDesc(unsigned short uMeteringMode) // 测光模式
{
switch(uMeteringMode)
{
case 1: return L"平均"; // 1: average
case 2: return L"中央重点加权平均"; // 2: center weighted average
case 3: return L"点测光"; // 3: spot
case 4: return L"多点测光"; // 4: multi-spot
case 5: return L"多段测光"; // 5: multi-segment
default: return UnicodeString().sprintf(L"未知测光模式 (%u)", uMeteringMode);
}
}
//---------------------------------------------------------------------------
UnicodeString TFormHsuanluExif::ParseResultDesc(int iResult) // 解析错误编码转提示信息
{
UnicodeString sDesc;
if(iResult != PARSE_EXIF_SUCCESS)
{
switch(iResult)
{
case PARSE_EXIF_ERROR_NO_JPEG: sDesc = L"不是 JPEG 图片" ; break; // 1982: No JPEG markers found in buffer, possibly invalid JPEG file
case PARSE_EXIF_ERROR_NO_EXIF: sDesc = L"没找到 EXIF 信息" ; break; // 1983: No EXIF header found in JPEG file.
case PARSE_EXIF_ERROR_UNKNOWN_BYTEALIGN: sDesc = L"未知的字节对齐方式"; break; // 1984: Byte alignment specified in EXIF file was unknown (not Motorola or Intel).
case PARSE_EXIF_ERROR_CORRUPT: sDesc = L"EXIF 数据损坏" ; break; // 1985: EXIF header was found, but data was corrupted.
default: sDesc.sprintf(L"未知错误 (%d)", iResult);
}
}
return sDesc;
}
//---------------------------------------------------------------------------
void TFormHsuanluExif::ShowGridLine(int iRowIdx, const UnicodeString sKey, const std::string &sValue)
{
UnicodeString s = UTF8String(sValue.c_str());
ShowGridLine(iRowIdx, sKey, s);
}
//---------------------------------------------------------------------------
void TFormHsuanluExif::ShowGridLine(int iRowIdx, const UnicodeString sKey, const UnicodeString sValue)
{
if(StringGrid1->RowCount < iRowIdx + 1)
StringGrid1->RowCount = iRowIdx + 1;
StringGrid1->Cells[0][iRowIdx] = iRowIdx;
StringGrid1->Cells[1][iRowIdx] = sKey;
StringGrid1->Cells[2][iRowIdx] = sValue;
}
//---------------------------------------------------------------------------
void TFormHsuanluExif::ShowExif(const easyexif::EXIFInfo &Exif)
{
int iRowIdx = 1;
UnicodeString s;
ShowGridLine(iRowIdx++, L"相机厂家" , Exif.Make); // Camera make
ShowGridLine(iRowIdx++, L"相机型号" , Exif.Model); // Camera model
ShowGridLine(iRowIdx++, L"相机软件" , Exif.Software); // Software
ShowGridLine(iRowIdx++, L"采样位数" , s.sprintf(L"%u", Exif.BitsPerSample)); // Bits per sample
ShowGridLine(iRowIdx++, L"照片宽度" , s.sprintf(L"%u 像素", Exif.ImageWidth)); // Image width
ShowGridLine(iRowIdx++, L"照片高度" , s.sprintf(L"%u 像素", Exif.ImageHeight)); // Image height
ShowGridLine(iRowIdx++, L"照片描述" , Exif.ImageDescription); // Image description
ShowGridLine(iRowIdx++, L"拍照方向" , OrientationDesc(Exif.Orientation)); // Image orientation
ShowGridLine(iRowIdx++, L"照片版权" , Exif.Copyright); // Image copyright
ShowGridLine(iRowIdx++, L"照片时间" , Exif.DateTime); // Image date/time
ShowGridLine(iRowIdx++, L"创建时间" , Exif.DateTimeOriginal); // Original date/time
ShowGridLine(iRowIdx++, L"数字化时间" , Exif.DateTimeDigitized); // Digitize date/time
ShowGridLine(iRowIdx++, L"创建时间亚秒", Exif.SubSecTimeOriginal); // Subsecond time
ShowGridLine(iRowIdx++, L"曝光时间" , ExposureTimeDesc(Exif.ExposureTime)); // Exposure time
ShowGridLine(iRowIdx++, L"光圈" , s.sprintf(L"f/%.2f", Exif.FNumber)); // F-stop
ShowGridLine(iRowIdx++, L"曝光模式" , ExposureProgDesc(Exif.ExposureProgram)); // Exposure program
ShowGridLine(iRowIdx++, L"ISO 感光度" , s.sprintf(L"%u", Exif.ISOSpeedRatings)); // ISO speed
ShowGridLine(iRowIdx++, L"物距" , s.sprintf(L"%f 米", Exif.SubjectDistance)); // Subject distance
ShowGridLine(iRowIdx++, L"曝光偏差" , s.sprintf(L"%f EV", Exif.ExposureBiasValue)); // Exposure bias
ShowGridLine(iRowIdx++, L"使用闪光灯" , Exif.Flash ? L"使用" : L"未使用"); // Flash used?
ShowGridLine(iRowIdx++, L"闪光灯反馈信号", FlashReturnedLightDesc(Exif.FlashReturnedLight)); // Flash returned light
ShowGridLine(iRowIdx++, L"闪光模式" , FlashModeDesc(Exif.FlashMode)); // Flash mode
ShowGridLine(iRowIdx++, L"测光模式" , MeteringModeDesc(Exif.MeteringMode)); // Metering mode
ShowGridLine(iRowIdx++, L"镜头焦距" , s.sprintf(L"%.2f 毫米", Exif.FocalLength)); // Lens focal length
ShowGridLine(iRowIdx++, L"35mm等效焦距", s.sprintf(L"%.2f 毫米", Exif.FocalLengthIn35mm)); // 35mm focal length
ShowGridLine(iRowIdx++, L"GPS 纬度" , s.sprintf(L"%c: %f° (%.0f° %.0f′ %.3f″)",
Exif.GeoLocation.LatComponents.direction, Exif.GeoLocation.Latitude,
Exif.GeoLocation.LatComponents.degrees, Exif.GeoLocation.LatComponents.minutes, Exif.GeoLocation.LatComponents.seconds)); // GPS Latitude
ShowGridLine(iRowIdx++, L"GPS 经度" , s.sprintf(L"%c: %f° (%.0f° %.0f′ %.3f″)",
Exif.GeoLocation.LonComponents.direction, Exif.GeoLocation.Longitude,
Exif.GeoLocation.LonComponents.degrees, Exif.GeoLocation.LonComponents.minutes, Exif.GeoLocation.LonComponents.seconds)); // GPS Longitude
ShowGridLine(iRowIdx++, L"GPS 海拔高度", s.sprintf(L"%.3f 米", Exif.GeoLocation.Altitude)); // GPS Altitude
ShowGridLine(iRowIdx++, L"GPS 精度因子 (DOP)", s.sprintf(L"%.2f", Exif.GeoLocation.DOP)); // GPS degree of precision (DOP)
ShowGridLine(iRowIdx++, L"镜头最小焦距", s.sprintf(L"%.2f 毫米", Exif.LensInfo.FocalLengthMin)); // Lens min focal length
ShowGridLine(iRowIdx++, L"镜头最大焦距", s.sprintf(L"%.2f 毫米", Exif.LensInfo.FocalLengthMax)); // Lens max focal length
ShowGridLine(iRowIdx++, L"镜头最小光圈", s.sprintf(L"f/%.2f", Exif.LensInfo.FStopMin)); // Lens f-stop min
ShowGridLine(iRowIdx++, L"镜头最大光圈", s.sprintf(L"f/%.2f", Exif.LensInfo.FStopMax)); // Lens f-stop max
ShowGridLine(iRowIdx++, L"镜头厂家" , Exif.LensInfo.Make); // Lens make
ShowGridLine(iRowIdx++, L"镜头型号" , Exif.LensInfo.Model); // Lens model
ShowGridLine(iRowIdx++, L"CCD/CMOS X 轴分辨率", s.sprintf(L"%f", Exif.LensInfo.FocalPlaneXResolution)); // Focal plane XRes
ShowGridLine(iRowIdx++, L"CCD/CMOS Y 轴分辨率", s.sprintf(L"%f", Exif.LensInfo.FocalPlaneYResolution)); // Focal plane YRes
}
//---------------------------------------------------------------------------
void __fastcall TFormHsuanluExif::Button1Click(TObject *Sender)
{
OpenDialog1->Filter = L"JPEG 图片|*.jpg;*.jpeg";
if(OpenDialog1->Execute(Handle))
{
std::auto_ptr<TMemoryStream> Img(new TMemoryStream);
Img->LoadFromFile(OpenDialog1->FileName);
unsigned long uImgSize = Img->Size;
unsigned char *pImgBuf = (unsigned char *)Img->Memory;
easyexif::EXIFInfo Exif;
int iResult = Exif.parseFrom(pImgBuf, uImgSize);
if(iResult != PARSE_EXIF_SUCCESS)
{
ShowMessage(L"文件 \"" + OpenDialog1->FileName + L"\"\r\nEXIF 解析失败,错误信息:" + ParseResultDesc(iResult));
return;
}
Label1->Caption = OpenDialog1->FileName;
ShowExif(Exif);
}
}
//---------------------------------------------------------------------------
void __fastcall TFormHsuanluExif::Button2Click(TObject *Sender)
{
OpenDialog1->Filter = L"任意图片|*.*";
if(OpenDialog1->Execute(Handle))
{
std::auto_ptr<TMemoryStream> Img(new TMemoryStream);
Img->LoadFromFile(OpenDialog1->FileName);
unsigned long uImgSize = Img->Size;
unsigned char *pImgBuf = (unsigned char *)Img->Memory;
easyexif::EXIFInfo Exif;
int iResult;
unsigned uOffset = 0;
while(uOffset < uImgSize-4)
{
while(uOffset < uImgSize-4)
{
if(std::memcmp(pImgBuf+uOffset, "Exif", 4)==0)
break;
uOffset++;
}
iResult = Exif.parseFromEXIFSegment(pImgBuf+uOffset, uImgSize-uOffset);
if(iResult == PARSE_EXIF_SUCCESS)
break;
uOffset += 4;
}
if(iResult != PARSE_EXIF_SUCCESS)
{
ShowMessage(L"文件 \"" + OpenDialog1->FileName + L"\"\r\nEXIF 解析失败,错误信息:" + ParseResultDesc(iResult));
return;
}
Label1->Caption = OpenDialog1->FileName;
ShowExif(Exif);
}
}
//---------------------------------------------------------------------------
6. 运行结果及说明
按钮 Button1 的点击事件:
按照 easyexif 给出的例子读取 JPEG 图片的 EXIF 信息,使用 easyexif::EXIFInfo::parseFrom 函数,这个函数只能解析 JPEG 格式的图片。
点击这个按钮的运行结果:
按钮 Button2 的点击事件:
按照 easyexif 给出的方法:照片里面的 EXIF 都是一样的格式,只要能找到 EXIF 在照片中的位置,就可以利用 easyexif::EXIFInfo::parseFromEXIFSegment 解析 EXIF 信息,所以在这个例子里面,在照片里面搜索 EXIF 的标志 "Exif" 所在位置,然后尝试解析这个位置的数据,如果解析失败 (只是一个字符串而已,不是 EXIF 信息) 再找下一个,一直到解析成功或者文件结束。
点击这个按钮,选择一个使用 iPhone 拍摄的 HEIC 格式照片的运行结果:
下载本文例子源码 easyexif-src-cbuilder.rar (7,968,861 字节)