美文网首页C++ Builder 编程技巧
C++ Builder 通过 easyexif 读取照片 EXI

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

作者: 玄坴 | 来源:发表于2022-03-28 09:02 被阅读0次
    • 读取 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 信息。

    剪貼簿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 参考手册

    相关文章

      网友评论

        本文标题:C++ Builder 通过 easyexif 读取照片 EXI

        本文链接:https://www.haomeiwen.com/subject/itiyjrtx.html