C++ Builder 编程技巧

C++ Builder 通过 easyexif 读取照片 EXI

2022-03-28  本文已影响0人  玄坴

1. 下载 easyexif

easyexif 源码和例子在这里下载:https://github.com/mayanklahiri/easyexif

2. 把 easyexif 添加到 C++ Builder 项目

下载的 easyexif 解压缩之后,在 easyexif-master 里面的主要文件:

只要有前面两个 exif.h 和 exif.cpp 就可以在项目里面使用了,把 exif.cpp 添加到已有项目,如下截图所示:

把 exif.cpp 添加到项目

3. 在 Form 上添加控件

在 Form 上放控件:
StringGrid1 用于显示 EXIF 信息;
Label1 用于显示文件名;
OpenDialog1 用于选择打开图片文件;
Button1:是按照 easyexif 的例子读取 JPEG 图片的 EXIF 信息;
Button2:读取任意图片的 EXIF 信息。

剪貼簿02.png

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 格式的图片。
点击这个按钮的运行结果:

点击 Button1 读取 JPEG 图片的 EXIF

按钮 Button2 的点击事件:
按照 easyexif 给出的方法:照片里面的 EXIF 都是一样的格式,只要能找到 EXIF 在照片中的位置,就可以利用 easyexif::EXIFInfo::parseFromEXIFSegment 解析 EXIF 信息,所以在这个例子里面,在照片里面搜索 EXIF 的标志 "Exif" 所在位置,然后尝试解析这个位置的数据,如果解析失败 (只是一个字符串而已,不是 EXIF 信息) 再找下一个,一直到解析成功或者文件结束。
点击这个按钮,选择一个使用 iPhone 拍摄的 HEIC 格式照片的运行结果:

点击 Button2 读取 HEIC 图片的 EXIF

下载本文例子源码 easyexif-src-cbuilder.rar (7,968,861 字节)


C++ Builder 基础知识
C++ Builder 编程技巧
C++ Builder 参考手册

上一篇下一篇

猜你喜欢

热点阅读