C++ で撮影場所をマッピング

(1/1)
>C++で撮影場所をマッピング
スマホやデジカメ等で撮影したJPEG画像ファイルを読み込み、撮影場所を地図上にマッピングしたり、露出やレンズ情報などを記録したExif情報を一覧表示するアプリケーションをC++を使って作る。ユーザーがAPIキーを取得することでGoogleマップも利用できる。「PHPで撮影場所をマッピング」で作ったPHPプログラムをC++に移植したものである。

(2025年3月16日)ネット接続チェック強化,キャッシュシステム不具合修正,使用ライブラリ更新
(2024年10月26日)Orientation情報があれば表示画像を回転させる,使用ライブラリ更新
(2024年10月13日)UTCとの時差,その他表記方法見直し,使用ライブラリ更新
(2024年8月17日)使用ライブラリ更新
(2024年5月3日)API入力処理を改良
(2024年4月27日)Edgeブラウザ対応,64ビット対応
(2024年3月23日)使用ライブラリ更新

目次

サンプル・プログラム

圧縮ファイルの内容
photomapwin.msiインストーラ
bin/photomapwin.exe実行プログラム本体
bin/WebView2Loader.dll
bin/libcurl-x64.dll
実行時に必要になるDLL
bin/etc/help.chmヘルプ・ファイル
sour/photomapwin.cppソース・プログラム
sour/resource.hリソース・ヘッダ
sour/resource.rcリソース・ファイル
sour/application.icoアプリケーション・アイコン
sour/mystrings.cpp汎用文字列処理関数など(ソース)
sour/mystrings.h汎用文字列処理関数など(ヘッダ)
sour/pahooGeocode.cpp住所・緯度・経度に関わるクラス(ソース)
sour/pahooGeocode.hpp住所・緯度・経度に関わるクラス(ヘッダ)
sour/apikey.cppAPIキーの管理(ソース)
sour/apikey.hppAPIキーの管理(ヘッダ)
sour/TinyEXIF.hTinyEXIFソース・プログラム
sour/TinyEXIF.cppTinyEXIFソース・プログラム
sour/tinyxml2.htinyxml2ソース・プログラム
sour/tinyxml2.cpptinyxml2ソース・プログラム
sour/WebView2.hWebView2に関わるヘッダ
sour/event.hWebView2用インターフェース(ヘッダ)
sour/event.cppWebView2用インターフェース(ソース)
sour/pahooWebView2.cppWebView2に関わる関数(ソース)
sour/pahooWebView2.hppWebView2に関わる関数(ヘッダ)
photomapwin.cpp 更新履歴
バージョン 更新日 内容
2.4.0 2025/03/15 Leafletアクセス可否チェック追加, UserAgent追加, 使用ライブラリ更新
2.3.0 2024/10/26 loadImageOnMemory()--Orientation情報があれば表示画像を回転させる
2.2.0 2024/10/13 UTCとの時差,その他表記方法見直し,使用ライブラリ更新
2.1.1 2024/08/17 使用ライブラリ更新
2.1.0 2024/05/03 enum eActionクラス導入,API入力処理を改良
pahooGeocode.cpp 更新履歴
バージョン 更新日 内容
1.9.0 2025/03/16 HTTPステータス・エラーをキャッチアップ
1.8.1 2025/02/23 setError() -- bug-fix
1.8.0 2024/05/03 getMyPath()をapikey.appのgetMyPath()関数に変更
1.7.0 2024/04/27 Edge対応に伴いデバッグ用コードを廃棄
1.6.0 2023/07/02 getPointsGSI()メソッド追加
mystrings.cpp 更新履歴
バージョン 更新日 内容
1.3.1 2025/03/16 readWebContents() リダイレクト有効に
1.3.0 2025/03/16 readWebContents() 引数httpStatus追加
1.2.0 2024/05/06 getModulePath() 追加
1.12 2021/01/31 readWebContents() 引数post追加
1.11 2020/10/17 htmlspecialchars() 追加
pahooWebView2.cpp 更新履歴
バージョン 更新日 内容
1.0.0 2024/04/27 初版
apikey.cpp 更新履歴
バージョン 更新日 内容
2.0.0 2024/04/29 createSetAPIkey, processSetAPIkey に統合
1.0 2020/09/30 初版

使用ライブラリ

文字列リテラルの操作に、ープンソースのライブラリ Boost C++ライブラリが必要になる。導入方法等については、「C++ 開発環境の準備」をご覧いただきたい。
Exif情報を解釈するのに、Seacave氏による TinyEXIF と、このモジュールが呼び出す inyxml2 を、https://github.com/cdcseacave/TinyEXIFhttps://github.com/leethomason/tinyxml2 から、それぞれダウンロードする。後述するとおり、TinyEXIF には手を加える。
また、地図表示にWebブラウザ・コントロールを利用するため "WebView2Loader.dll" を利用する。jchv / webview2-in-mingw からダウンロードできる。

リソースの準備

64ビット版の開発環境を用いる。
Eclipse を起動し、新規プロジェクト photomapwin を用意する。
ResEdit を起動し、resource.rc を用意する。

Eclipse に戻り、ソース・プログラム "photomapwin.cpp" を追加する。
リンカー・フラグを -Wl,--enable-stdcall-fixup -mwindows -lgdiplus -lole32 -static -lstdc++ -lgcc -lwinpthread -lcurl -lssl "C:\(libcurl-x64.dllのフォルダ)\libcurl-x64.dll" "C:\Windows\system32\GdiPlus.dll" "C:\(WebView2Loader.dllのフォルダ)\WebView2Loader.dll" に設定する。

解説:定数など

photomapwin.cpp

  45: // 定数など ==================================================================
  46: #define MAKER       "pahoo.org"             // 作成者
  47: #define APPNAME     "photomapwin"           // アプリケーション名
  48: #define APPNAMEJP   "撮影場所をマッピング"  // アプリケーション名(日本語)
  49: #define APPVERSION  "2.4.0"                 // バージョン
  50: #define APPYEAR     "2020-25"               // 作成年
  51: #define REFERENCE   "https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-13-01.shtm"   // 参考サイト
  52: 
  53: // char*バッファサイズ
  54: #define SIZE_BUFF       512
  55: 
  56: // ListViewItemの最大文字長:変更不可
  57: #define MAX_LISTVIEWITEM    258
  58: 
  59: // ヘルプ・ファイル
  60: #define HELPFILE    ".\\etc\\help.chm"
  61: 
  62: // デフォルト保存ファイル名
  63: #define SAVEFILE    "photomapwin.csv"
  64: 
  65: // マップID
  66: #define MAP_ID          "map_id"
  67: // 地図の大きさ
  68: #define MAP_WIDTH       650     // 地図の幅(ピクセル)
  69: #define MAP_HEIGHT      345     // 地図の高さ(ピクセル)
  70: // 経度・緯度(初期値)
  71: #define DEF_LONGITUDE   139.766667
  72: double Longitude = DEF_LONGITUDE;
  73: #define DEF_LATITUDE    35.681111
  74: double Latitude  = DEF_LATITUDE;
  75: // 地図拡大率(初期値)
  76: #define DEF_ZOOM    13
  77: int Zoom = DEF_ZOOM;
  78: // 地図の種類(初期値)
  79: #define DEF_MAPTYPE     "GSISTD"
  80: string Maptype = DEF_MAPTYPE;

とくに注意記載が無い限り、定数は自由に変更できる。

解説:データ構造

photomapwin.cpp

 144: // Exif格納クラス
 145: #define SIZE_EXIFS      100     // 最大格納数
 146: class _Exif {
 147: public:
 148:     wstring label = L"";        // データ項目
 149:     wstring data  = L"";        // データ本体
 150:     wstring unit  = L"";        // 単位
 151: Exif[SIZE_EXIFS];

取得したExif情報はクラス配列 Exif に格納する。

解説:フルパスからファイル名を取り出す

photomapwin.cpp

 360: /**
 361:  * ファイル名(フルパス)からファイル名(拡張子を除く)を取り出す
 362:  * @param   wstring path ファイル名(フルパス)
 363:  * @return  wstring ファイル名(拡張子を除く)
 364: */
 365: wstring wgetBasename(wstring path) {
 366:     wstring basename = L"";
 367: 
 368:     // 最後のディレクトリ識別子を探す
 369:     size_t pos = path.rfind(_SW("\\"));
 370:     if (pos == string::npos) {
 371:         return basename;
 372:     }
 373:     // ファイル名+拡張子
 374:     wstring fname = path.substr(pos + 1);
 375: 
 376:     // 拡張子を除く
 377:     pos = fname.rfind(_SW("."));
 378:     if (pos == string::npos) {
 379:         basename = fname;
 380:     } else {
 381:         basename = fname.substr(0, pos);
 382:     }
 383:     return basename;
 384: }

Exif情報をCSVファイルに保存する際、ファイル名を読み込んだ画像ファイル名に合わせようと考え、この関数を用意した。
Boost C++ライブラリにも同様関数があるが、うまくコンパイルできなかったので、自力でコーディングした。シフトJIS文字列では、ディレクトリ識別子 ¥\0x5C)を含む全角文字があるため、ファイル名はワイド文字列に変換して処理する。

解説:画像ファイルを読み込む

photomapwin.cpp

 579: /**
 580:  * 画像ファイルの表示
 581:  * @param   HWND  hWnd 表示するウィンドウ・ハンドル
 582:  * @param   const char *fname 画像ファイル名
 583:  * @return  bool TRUE:表示成功/FALSE:失敗
 584: */
 585: bool onPaint(HWND hWnd, const char *fname) {
 586:     HDC hdc;
 587:     PAINTSTRUCT ps;
 588:     static HDC hdcBMP;
 589:     static int ix,iy;
 590:     int sx, sy;
 591:     RECT rect;
 592:     bool ret = TRUE;
 593: 
 594:     // 画像ファイル読み込み
 595:     hdc = GetDC(hWnd);
 596:     if(loadImageOnMemory((TCHAR*)fname, &hdcBMP, hdc, &ix, &iy) == FALSE) {
 597:         ErrorMessage = "画像ファイル " + (string)fname + " の読み込みに失敗";
 598:         return FALSE;
 599:     }
 600:     InvalidateRect(hWnd, 0, TRUE);
 601:     hdc = BeginPaint(hWnd, &ps);
 602:     GetClientRect(hWnd, &rect);
 603: 
 604:     // 領域クリア
 605:     HPEN   hNewPen   = (HPEN)CreatePen(PS_INSIDEFRAME, 4, RGB(255, 255, 255));
 606:     HPEN   hOldPen   = (HPEN)SelectObject(hdc,hNewPen);
 607:     HBRUSH hNewBrush = (HBRUSH)CreateSolidBrush(RGB(255, 255, 255));
 608:     HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, hNewBrush);
 609:     Rectangle(hdc, 0, 0, rect.right, rect.bottom);
 610:     DeleteObject(SelectObject(hdc, hOldBrush));
 611:     DeleteObject(SelectObject(hdc, hOldPen));
 612: 
 613:     // 縮小表示
 614:     reSizeImage(&sx, &sy, ix, iy, (rect.right - rect.left), (rect.bottom - rect.top));
 615:     SetStretchBltMode(hdc, HALFTONE);
 616:     StretchBlt(hdc, 0, 0, sx, sy, hdcBMP, 0, 0, ix, iy, SRCCOPY);
 617:     EndPaint(hWnd, &ps);
 618:     ReleaseDC(hWnd, hdc);
 619: 
 620:     return ret;
 621: }

photomapwin.cpp

 452: /**
 453:  * 画像ファイルをGDI+を使って読み込む
 454:  * Orientation情報があれば,画像を回転させる
 455:  * @param   TCHAR* szFile   画像ファイル名
 456:  * @param   HDC* hdcBMP     オフスクリーン用デバイスコンテキストを格納
 457:  * @param   HDC  hdc        デバイスコンテキスト
 458:  * @param   int* ix, iy     読み込んだ画像の横・縦ピクセル数
 459:  * @return  なし
 460: */
 461: bool loadImageOnMemory(TCHAR* szFile, HDC* hdcBMP, HDC hdc, int* ix, int* iy) {
 462:     Image* imageP;
 463:     WCHAR wTitle[MAX_PATH];
 464:     BITMAPINFOHEADER bmih;
 465:     HBITMAP hBitmap;
 466:     BYTE *pBits;
 467: 
 468:     // 読み込むファイル名
 469:     MultiByteToWideChar(932, 0, szFile, -1, wTitle, sizeof(wTitle) / sizeof(TCHAR));
 470:     imageP = Bitmap::FromFile(wTitle);
 471:     if (imageP == 0) {
 472:         *ix = 0;
 473:         *iy = 0;
 474:         *hdcBMP = 0;
 475:         return FALSE;
 476:     }
 477: 
 478:     // ExifのOrientationを取得
 479:     UINT size = imageP->GetPropertyItemSize(PropertyTagOrientation);
 480:     if (size > 0) {
 481:         PropertyItem* prop = (PropertyItem*)malloc(size);
 482:         // malloc成功の場合
 483:         if (prop) {
 484:             // sizeがPropertyItem構造体のvalue領域を含む正しいサイズの場合
 485:             if (imageP->GetPropertyItem(PropertyTagOrientation, size, prop) == Ok) {
 486:                 // prop->valueがnullptrでない場合
 487:                 if (! prop->value) {
 488:                     int orientation = *(short*)prop->value;
 489:                     cout << "GetPropertyItem" << endl;
 490: 
 491:                     // Orientationに基づいて回転
 492:                     switch (orientation) {
 493:                     case 3:     // 180度回転
 494:                         imageP->RotateFlip(Rotate180FlipNone);
 495:                         break;
 496:                     case 6:     // 90度時計回り
 497:                         imageP->RotateFlip(Rotate90FlipNone);
 498:                         break;
 499:                     case 8:     // 90度反時計回り
 500:                         imageP->RotateFlip(Rotate270FlipNone);
 501:                         break;
 502:                     case 2:     // 水平方向に反転
 503:                         imageP->RotateFlip(RotateNoneFlipX);
 504:                         break;
 505:                     case 4:     // 垂直方向に反転
 506:                         imageP->RotateFlip(RotateNoneFlipY);
 507:                         break;
 508:                     case 5:     // 90度反時計回り+垂直反転
 509:                         imageP->RotateFlip(Rotate270FlipX);
 510:                         break;
 511:                     case 7:     // 90度時計回り+垂直反転
 512:                         imageP->RotateFlip(Rotate90FlipX);
 513:                         break;
 514:                     default:    // Orientationが1の場合はそのまま
 515:                         break;
 516:                     }
 517:                 }
 518:             }
 519:             free(prop);
 520:         }
 521:     }
 522: 
 523:     // 画像サイズを取得
 524:     bmih.biSize = sizeof(bmih);
 525:     bmih.biWidth = *ix = imageP->GetWidth();
 526:     bmih.biHeight = *iy = imageP->GetHeight();
 527:     bmih.biPlanes = 1;
 528:     bmih.biBitCount = 32;
 529:     bmih.biCompression = BI_RGB;
 530:     bmih.biSizeImage = 0;
 531:     bmih.biXPelsPerMeter = 0;
 532:     bmih.biYPelsPerMeter = 0;
 533:     bmih.biClrUsed = 0;
 534:     bmih.biClrImportant = 0;
 535:     hBitmap = CreateDIBSection(NULL, (BITMAPINFO*)&bmih, 0, (void**)&pBits, NULL, 0);
 536:     cout << "CreateDIBSection" << endl;
 537:     if (pBits == NULL) {
 538:         ErrorMessage = "メモリ不足";
 539:         DeleteObject(hBitmap);
 540:         delete imageP;
 541:         *ix = 0;
 542:         *iy = 0;
 543:         return FALSE;
 544:     }
 545:     *hdcBMP = CreateCompatibleDC(hdc);
 546: //  HOldBitmap = (HBITMAP)SelectObject(*hdcBMP, hBitmap);
 547:     SelectObject(*hdcBMP, hBitmap);
 548:     Graphics MyGraphics(*hdcBMP);
 549:     MyGraphics.DrawImage(imageP, 0, 0, imageP->GetWidth(), imageP->GetHeight());
 550:     delete imageP;
 551: 
 552:     return TRUE;
 553: }

photomapwin.cpp

 555: /**
 556:  * 縦横比を保った画像サイズ計算
 557:  * @param   int* sx, sy  変換後画像サイズを格納
 558:  * @param   int  px, py  元画像の縦横サイズ
 559:  * @param   int  dx, dy  変換後領域の縦横サイズ
 560:  * @return  なし
 561: */
 562: void reSizeImage(int* sx, int* sy, int px, int py, int dx, int dy) {
 563:     double pxy = double(px) / double(py);   // 元画像の横縦比
 564:     double dxy = double(dx) / double(dy);   // 変換後の横縦比
 565:     int dx2, dy2;
 566: 
 567:     // 横を基準に縦を縮小
 568:     if(pxy > dxy) {
 569:         dx2 = dx;
 570:         dy2 = (int)double(dx / pxy);
 571:     } else {
 572:         dy2 = dy;
 573:         dx2 = (int)double(dy * pxy);
 574:     }
 575:     *sx = dx2;
 576:     *sy = dy2;
 577: }

C++でTeX画像を作成」で GDI+ を使った画像ファイル読み込みと表示について紹介したが、今回は、読み込んだ画像をサムネイル領域のサイズに合わせて縮小する必要がある。

まず、GDI+ を使って画像データをメモリに読み込むのがユーザー関数 loadImageOnMemory である。
読み込んだ画像の縦横のピクセル数を取り出し、ユーザー関数 reSizeImage を使って、サムネイル領域のサイズに縦横比が合うように縮小する。メモリ上の画像データを縮小してサムネイル領域に表示するのは StretchBlt 関数である。
縮小したら、

Exif規格とTinyExifの改良

Exif規格 は、富士フイルムが開発し、1995年(平成7年)にCIPA(一般社団法人カメラ映像機器工業会)と JEITA(電子情報技術産業協会)が規格化した。最新バージョンは2019年(平成31年)5月に改訂された2.32で、CIPAのサイトでドラフト版を読むことができる。
改定履歴を読むと、2000年代に入っても改良が続いているのだが、ダウンロードした TinyExif は最新の仕様に対応していない。とくバージョン2.31で追加された UTC(協定世界時)との時差は大きな改良ポイントであるので、TinyExif を独自改良した。"TinyEXIF.h", "TinyEXIF.cpp" の改良箇所を下記に示す。

TinyEXIF.h

 318:     // modified by pahoo.org -- from here
 319:     std::string OffsetTime;             // Offset DateTime
 320:     std::string OffsetTimeOriginal;     // Offset DateTimeOriginal
 321:     std::string OffsetTimeDigitized;        // Offset DateTimeDigitized
 322:     // -- upto here

TinyEXIF.cpp

 486:     // modified by pahoo.org -- from here
 487:     case 0x9010:
 488:         // Offset DateTime
 489:         parser.Fetch(OffsetTime);
 490:         break;
 491: 
 492:     case 0x9011:
 493:         // Offset DateTimeOriginal
 494:         parser.Fetch(OffsetTimeOriginal);
 495:         break;
 496: 
 497:     case 0x9012:
 498:         // Offset DateTimeDigitized
 499:         parser.Fetch(OffsetTimeDigitized);
 500:         break;
 501:     // -- upto here

解説:Exif情報を取得する

photomapwin.cpp

 624: /**
 625:  * 画像ファイルを開いてExif情報を取得する(実作業)
 626:  * @param   const char *fname 保存ファイル名
 627:  * @return  bool TRUE:取得成功/FALSE:失敗
 628: */
 629: bool loadExif(const char *fname) {
 630:     double f;
 631:     string ss;
 632:     char buff[SIZE_BUFF + 1];
 633:     bool ret = TRUE;
 634: 
 635:     // 画像ファイルのオープン
 636:     EXIFStreamFile stream(fname);
 637:     if (! stream.IsValid()) {
 638:         ErrorMessage = "画像ファイル " + (string)fname + " の読み込みに失敗";
 639:         return FALSE;
 640:     }
 641:     // Exif情報の取得
 642:     TinyEXIF::EXIFInfo imageEXIF(stream);
 643: 
 644:     // Exif情報を配列Exifへ代入
 645:     // 初期化
 646:     for (int i = 0i < SIZE_EXIFSi++) {
 647:         Exif[i].label = L"";
 648:         Exif[i].data  = L"";
 649:         Exif[i].unit  = L"";
 650:     }
 651:     // 代入
 652:     int i = 0;
 653: 
 654:     // 位置情報
 655:     if (imageEXIF.GeoLocation.hasLatLon()) {
 656:         Exif[i].label = _SW("緯度");
 657:         Exif[i].data  = _SW(to_string(imageEXIF.GeoLocation.Latitude));
 658:         Exif[i].unit  = _SW("度");
 659:         Latitude = imageEXIF.GeoLocation.Latitude;
 660:         i++;
 661:         Exif[i].label = _SW("経度");
 662:         Exif[i].data  = _SW(to_string(imageEXIF.GeoLocation.Longitude));
 663:         Exif[i].unit  = _SW("度");
 664:         Longitude = imageEXIF.GeoLocation.Longitude;
 665:         i++;
 666:     } else {
 667:         ErrorMessage = "画像ファイル " + (string)fname + " に位置情報が存在しない";
 668:         ret = FALSE;
 669:     }
 670:     if (imageEXIF.GeoLocation.hasAltitude()) {
 671:         snprintf(buff, SIZE_BUFF, "%.1f", imageEXIF.GeoLocation.Altitude);
 672:         switch (imageEXIF.GeoLocation.AltitudeRef) {
 673:             case 0:
 674:                 Exif[i].unit  = _SW("メートル(海抜)");
 675:                 break;
 676:             case 1:
 677:                 Exif[i].unit  = _SW("メートル(海面下)");
 678:                 break;
 679:             default:
 680:                 Exif[i].unit  = _SW("メートル");
 681:                 break;
 682:         }
 683:         Exif[i].label = _SW("高度");
 684:         Exif[i].data  = _SW((string)buff);
 685:         i++;
 686:     }
 687:     if (! imageEXIF.GeoLocation.GPSMapDatum.empty()) {
 688:         Exif[i].label = _SW("測地系");
 689:         Exif[i].data  = _SW(imageEXIF.GeoLocation.GPSMapDatum);
 690:         i++;
 691:     }
 692:     // 画像情報
 693:     if (imageEXIF.ImageWidth || imageEXIF.ImageHeight) {
 694:         Exif[i].label = _SW("画像の幅");
 695:         Exif[i].data  = _SW(to_string(imageEXIF.ImageWidth));
 696:         Exif[i].unit  = _SW("ピクセル");
 697:         i++;
 698:         Exif[i].label = _SW("画像の高さ");
 699:         Exif[i].data  = _SW(to_string(imageEXIF.ImageHeight));
 700:         Exif[i].unit  = _SW("ピクセル");
 701:         i++;
 702:     }
 703:     if (imageEXIF.RelatedImageWidth || imageEXIF.RelatedImageHeight) {
 704:         Exif[i].label = _SW("画像の幅");
 705:         Exif[i].data  = _SW(to_string(imageEXIF.RelatedImageWidth));
 706:         Exif[i].unit  = _SW("ピクセル");
 707:         i++;
 708:         Exif[i].label = _SW("画像の高さ");
 709:         Exif[i].data  = _SW(to_string(imageEXIF.RelatedImageHeight));
 710:         Exif[i].unit  = _SW("ピクセル");
 711:         i++;
 712:     }
 713:     if (! imageEXIF.ImageDescription.empty()) {
 714:         Exif[i].label = _SW("画像タイトル");
 715:         Exif[i].data  = _SW(imageEXIF.ImageDescription);
 716:         i++;
 717:     }
 718:     if (! imageEXIF.Make.empty() || ! imageEXIF.Model.empty()) {
 719:         Exif[i].label = _SW("カメラのメーカー名");
 720:         Exif[i].data  = _SW(imageEXIF.Make);
 721:         i++;
 722:         Exif[i].label = _SW("カメラのモデル名");
 723:         Exif[i].data  = _SW(imageEXIF.Model);
 724:         i++;
 725:     }
 726:     if (! imageEXIF.SerialNumber.empty()) {
 727:         Exif[i].label = _SW("シリアル番号");
 728:         Exif[i].data  = _SW(imageEXIF.SerialNumber);
 729:         i++;
 730:     }
 731:     if (imageEXIF.Orientation) {
 732:         Exif[i].label = _SW("画像方向");
 733:         switch (imageEXIF.Orientation) {
 734:             case 1:
 735:                 Exif[i].data  = _SW("左上");
 736:                 break;
 737:             case 3:
 738:                 Exif[i].data  = _SW("右下");
 739:                 break;
 740:             case 6:
 741:                 Exif[i].data  = _SW("右上");
 742:                 break;
 743:             case 8:
 744:                 Exif[i].data  = _SW("左下");
 745:                 break;
 746:             default:
 747:                 Exif[i].data  = _SW("不明");
 748:                 break;
 749:         }
 750:         i++;
 751:     }
 752:     if (imageEXIF.XResolution || imageEXIF.YResolution || imageEXIF.ResolutionUnit) {
 753:         ss = "";
 754:         switch (imageEXIF.ResolutionUnit) {
 755:             case 2:
 756:                 ss = "インチ";
 757:                 break;
 758:             case 3:
 759:                 ss = "センチ";
 760:                 break;
 761:             default:
 762:                 ss = "";
 763:                 break;
 764:         }
 765:         Exif[i].label = _SW("幅の解像度");
 766:         Exif[i].data  = _SW(to_string((int)imageEXIF.XResolution));
 767:         Exif[i].unit  = _SW(ss);
 768:         i++;
 769:         Exif[i].label = _SW("高さの解像度");
 770:         Exif[i].data  = _SW(to_string((int)imageEXIF.YResolution));
 771:         Exif[i].unit  = _SW(ss);
 772:         i++;
 773:     }
 774:     if (imageEXIF.BitsPerSample) {
 775:         Exif[i].label = _SW("画像のビットの深さ");
 776:         Exif[i].data  = _SW(to_string(imageEXIF.BitsPerSample));
 777:         i++;
 778:     }
 779:     if (! imageEXIF.Software.empty()) {
 780:         Exif[i].label = _SW("ソ\フトウェア");
 781:         Exif[i].data  = _SW(imageEXIF.Software);
 782:         i++;
 783:     }
 784: 
 785:     // 撮影日時関係
 786:     regex pattern(R"((\d{4}):(\d{2}):(\d{2})\s+(\d{2}:\d{2}:\d{2}))");
 787:  if (! imageEXIF.DateTime.empty()) {
 788:         Exif[i].label = _SW("撮影日時");
 789:         string ss = regex_replace(imageEXIF.DateTime, pattern, "$1-$2-$3T$4");
 790:         if (! imageEXIF.OffsetTime.empty()) {
 791:             ss +imageEXIF.OffsetTime;
 792:         }
 793:         Exif[i].data  = _SW(ss);
 794:         i++;
 795:     }
 796:     if (! imageEXIF.DateTimeOriginal.empty()) {
 797:         string ss = regex_replace(imageEXIF.DateTimeOriginal, pattern, "$1-$2-$3T$4");
 798:         Exif[i].label = _SW("撮影日時(オリジナル)");
 799:         if (! imageEXIF.OffsetTimeOriginal.empty()) {
 800:             ss +imageEXIF.OffsetTimeOriginal;
 801:         }
 802:         Exif[i].data  = _SW(ss);
 803:         i++;
 804:     }
 805:     if (! imageEXIF.DateTimeDigitized.empty()) {
 806:         string ss = regex_replace(imageEXIF.DateTimeDigitized, pattern, "$1-$2-$3T$4");
 807:         Exif[i].label = _SW("撮影日時(デジタイズ)");
 808:         if (! imageEXIF.OffsetTimeDigitized.empty()) {
 809:             ss +imageEXIF.OffsetTimeDigitized;
 810:         }
 811:         Exif[i].data  = _SW(ss);
 812:         i++;
 813:     }
 814:     if (! imageEXIF.SubSecTimeOriginal.empty()) {
 815:         Exif[i].label = _SW("撮影日時");
 816:         Exif[i].data  = _SW(imageEXIF.SubSecTimeOriginal);
 817:         Exif[i].unit  = _SW("ミリ秒");
 818:         i++;
 819:     }
 820: 
 821:     if (! imageEXIF.Copyright.empty()) {
 822:         Exif[i].label = _SW("著作権者");
 823:         Exif[i].data  = _SW(imageEXIF.Copyright);
 824:         i++;
 825:     }
 826:     Exif[i].label = _SW("露光制御");
 827:     switch (imageEXIF.ExposureProgram) {
 828:         case 1:
 829:             Exif[i].data  = _SW("マニュアル設定");
 830:             break;
 831:         case 2:
 832:             Exif[i].data  = _SW("通常のプログラムAE");
 833:             break;
 834:         case 3:
 835:             Exif[i].data  = _SW("絞り優先");
 836:             break;
 837:         case 4:
 838:             Exif[i].data  = _SW("シャッター速度優先");
 839:             break;
 840:         case 5:
 841:             Exif[i].data  = _SW("低速プログラム");
 842:             break;
 843:         case 6:
 844:             Exif[i].data  = _SW("高速プログラム");
 845:             break;
 846:         case 7:
 847:             Exif[i].data  = _SW("ポートレートモード");
 848:             break;
 849:         case 8:
 850:             Exif[i].data  = _SW("風景モード");
 851:             break;
 852:         default:
 853:             Exif[i].data  = _SW("不明");
 854:             break;
 855:     }
 856:     i++;
 857:     Exif[i].label = _SW("ISO感度");
 858:     Exif[i].data  = _SW(to_string(imageEXIF.ISOSpeedRatings));
 859:     i++;
 860:     Exif[i].label = _SW("シャッター速度");
 861:     f = 1.0 / imageEXIF.ShutterSpeedValue;
 862:     Exif[i].data  = _SW("1/" + to_string((int)f));
 863:     Exif[i].unit  = _SW("秒");
 864:     i++;
 865:     Exif[i].label = _SW("露出時間");
 866:     f = 1.0 / imageEXIF.ExposureTime;
 867:     Exif[i].data  = _SW("1/" + to_string((int)f));
 868:     Exif[i].unit  = _SW("秒");
 869:     i++;
 870:     Exif[i].label = _SW("絞り値");
 871:     Exif[i].data  = _SW(to_string(imageEXIF.ApertureValue));
 872:     i++;
 873:     Exif[i].label = _SW("F値");
 874:     snprintf(buff, SIZE_BUFF, "%g", imageEXIF.FNumber);
 875:     Exif[i].data  = _SW(buff);
 876:     i++;
 877:     Exif[i].label = _SW("レンズ焦点距離");
 878:     Exif[i].data  = _SW(to_string((int)imageEXIF.FocalLength));
 879:     Exif[i].unit  = _SW("ミリ");
 880:     i++;
 881:     if ((float)imageEXIF.BrightnessValue > 0) {
 882:         Exif[i].label = _SW("輝度値");
 883:         Exif[i].data  = _SW(to_string(imageEXIF.BrightnessValue));
 884:         i++;
 885:     }
 886:     if ((float)imageEXIF.ExposureBiasValue > 0) {
 887:         Exif[i].label = _SW("露出補正値");
 888:         Exif[i].data  = _SW(to_string(imageEXIF.ExposureBiasValue));
 889:         i++;
 890:     }
 891:     if ((float)imageEXIF.SubjectDistance > 0) {
 892:         Exif[i].label = _SW("被写体距離");
 893:         Exif[i].data  = _SW(to_string(imageEXIF.SubjectDistance));
 894:         i++;
 895:     }
 896:     Exif[i].label = _SW("フラッシュ");
 897:     switch (imageEXIF.Flash) {
 898:         case 0:
 899:             Exif[i].data  = _SW("非発光");
 900:             break;
 901:         case 1:
 902:             Exif[i].data  = _SW("発光");
 903:             break;
 904:         case 5:
 905:             Exif[i].data  = _SW("発光したが反射光検出できず");
 906:             break;
 907:         case 6:
 908:             Exif[i].data  = _SW("発光して反射光検出");
 909:             break;
 910:         default:
 911:             Exif[i].data  = _SW("不明");
 912:             break;
 913:     }
 914:     i++;
 915:     Exif[i].label = _SW("測光モード");
 916:     switch (imageEXIF.MeteringMode) {
 917:         case 0:
 918:             Exif[i].data  = _SW("平均測光");
 919:             break;
 920:         case 1:
 921:             Exif[i].data  = _SW("中央重点");
 922:             break;
 923:         case 2:
 924:             Exif[i].data  = _SW("スポット");
 925:             break;
 926:         case 4:
 927:             Exif[i].data  = _SW("多点スポット");
 928:             break;
 929:         case 5:
 930:             Exif[i].data  = _SW("マルチセグメント");
 931:             break;
 932:         case 6:
 933:             Exif[i].data  = _SW("部分測光");
 934:             break;
 935:         default:
 936:             Exif[i].data  = _SW("不明");
 937:             break;
 938:     }
 939:     i++;
 940:     Exif[i].label = _SW("光源");
 941:     switch (imageEXIF.LightSource) {
 942:         case 1:
 943:             Exif[i].data  = _SW("昼光");
 944:             break;
 945:         case 2:
 946:             Exif[i].data  = _SW("蛍光燈");
 947:             break;
 948:         case 3:
 949:             Exif[i].data  = _SW("白熱電球");
 950:             break;
 951:         case 4:
 952:             Exif[i].data  = _SW("フラッシュ");
 953:             break;
 954:         case 9:
 955:             Exif[i].data  = _SW("快晴");
 956:             break;
 957:         case 10:
 958:             Exif[i].data  = _SW("曇天");
 959:             break;
 960:         case 11:
 961:             Exif[i].data  = _SW("日陰");
 962:             break;
 963:         case 12:
 964:             Exif[i].data  = _SW("昼光色");
 965:             break;
 966:         case 13:
 967:             Exif[i].data  = _SW("昼白色");
 968:             break;
 969:         case 14:
 970:             Exif[i].data  = _SW("蛍光色");
 971:             break;
 972:         case 15:
 973:             Exif[i].data  = _SW("白色");
 974:             break;
 975:         case 17:
 976:             Exif[i].data  = _SW("標準ライトA");
 977:             break;
 978:         case 18:
 979:             Exif[i].data  = _SW("標準ライトB");
 980:             break;
 981:         case 19:
 982:             Exif[i].data  = _SW("標準ライトC");
 983:             break;
 984:         case 20:
 985:             Exif[i].data  = _SW("D55");
 986:             break;
 987:         case 21:
 988:             Exif[i].data  = _SW("D65");
 989:             break;
 990:         case 22:
 991:             Exif[i].data  = _SW("D75");
 992:             break;
 993:         case 23:
 994:             Exif[i].data  = _SW("D50");
 995:             break;
 996:         case 24:
 997:             Exif[i].data  = _SW("ISO白熱電球");
 998:             break;
 999:         default:
1000:             Exif[i].data  = _SW("不明");
1001:             break;
1002:     }
1003:     i++;
1004:     if (imageEXIF.Calibration.FocalLength !0) {
1005:         Exif[i].label = _SW("(ピクセル)");
1006:         Exif[i].data  = _SW(to_string(imageEXIF.Calibration.FocalLength));
1007:         i++;
1008:     }
1009:     if (imageEXIF.Calibration.OpticalCenterX !0) {
1010:         Exif[i].label = _SW("(ピクセル)");
1011:         Exif[i].data  = _SW(to_string(imageEXIF.Calibration.OpticalCenterX));
1012:         i++;
1013:     }
1014:     if (imageEXIF.Calibration.OpticalCenterY !0) {
1015:         Exif[i].label = _SW("(ピクセル)");
1016:         Exif[i].data  = _SW(to_string(imageEXIF.Calibration.OpticalCenterY));
1017:         i++;
1018:     }
1019:     // レンズ情報
1020:     if ((int)imageEXIF.LensInfo.FStopMin > 0) {
1021:         Exif[i].label = _SW("最小絞り値");
1022:         Exif[i].data  = _SW(to_string((int)imageEXIF.LensInfo.FStopMin));
1023:         i++;
1024:     }
1025:     if ((int)imageEXIF.LensInfo.FStopMax > 0) {
1026:         Exif[i].label = _SW("最大絞り値");
1027:         Exif[i].data  = _SW(to_string((int)imageEXIF.LensInfo.FStopMax));
1028:         i++;
1029:     }
1030:     if ((int)imageEXIF.LensInfo.FocalLengthMin > 0) {
1031:         Exif[i].label = _SW("広角側");
1032:         Exif[i].data  = _SW(to_string((int)imageEXIF.LensInfo.FocalLengthMin));
1033:         Exif[i].unit  = _SW("ミリ");
1034:         i++;
1035:     }
1036:     if ((int)imageEXIF.LensInfo.FocalLengthMax > 0) {
1037:         Exif[i].label = _SW("望遠側");
1038:         Exif[i].data  = _SW(to_string((int)imageEXIF.LensInfo.FocalLengthMax));
1039:         Exif[i].unit  = _SW("ミリ");
1040:         i++;
1041:     }
1042:     if ((int)imageEXIF.LensInfo.DigitalZoomRatio > 0) {
1043:         Exif[i].label = _SW("デジタルズーム倍率");
1044:         Exif[i].data  = _SW(to_string((int)imageEXIF.LensInfo.DigitalZoomRatio));
1045:         i++;
1046:     }
1047:     if ((int)imageEXIF.LensInfo.FocalLengthIn35mm > 0) {
1048:         Exif[i].label = _SW("焦点距離(35ミリ判相当");
1049:         Exif[i].data  = _SW(to_string((int)imageEXIF.LensInfo.FocalLengthIn35mm));
1050:         Exif[i].unit  = _SW("ミリ");
1051:         i++;
1052:     }
1053:     ss = "";
1054:     switch (imageEXIF.LensInfo.FocalPlaneResolutionUnit) {
1055:         case 2:
1056:             ss = "インチ";
1057:             break;
1058:         case 3:
1059:             ss = "センチ";
1060:             break;
1061:         default:
1062:             ss = "";
1063:             break;
1064:     }
1065:     Exif[i].label = _SW("焦点面の幅の解像度");
1066:     Exif[i].data  = _SW(to_string(imageEXIF.LensInfo.FocalPlaneXResolution));
1067:     Exif[i].unit  = _SW(ss);
1068:     i++;
1069:     Exif[i].label = _SW("焦点面の高さの解像度");
1070:     Exif[i].data  = _SW(to_string(imageEXIF.LensInfo.FocalPlaneYResolution));
1071:     Exif[i].unit  = _SW(ss);
1072:     i++;
1073: 
1074:     // レンズ関係の情報
1075:     if (! imageEXIF.LensInfo.Make.empty()) {
1076:         Exif[i].label = _SW("レンズのメーカー名");
1077:         Exif[i].data  = _SW(imageEXIF.LensInfo.Make);
1078:         i++;
1079:     }
1080:     if (! imageEXIF.LensInfo.Model.empty()) {
1081:         Exif[i].label = _SW("レンズのモデル名");
1082:         Exif[i].data  = _SW(imageEXIF.LensInfo.Model);
1083:         i++;
1084:     }
1085: 
1086:     // 位置情報
1087:     if (imageEXIF.GeoLocation.hasRelativeAltitude()) {
1088:         Exif[i].label = _SW("相対高度");
1089:         Exif[i].data  = _SW(to_string(imageEXIF.GeoLocation.RelativeAltitude));
1090:         Exif[i].unit  = _SW("メートル");
1091:         i++;
1092:     }
1093:     if (imageEXIF.GeoLocation.hasOrientation()) {
1094:         Exif[i].label = _SW("ロール角");
1095:         Exif[i].data  = _SW(to_string(imageEXIF.GeoLocation.RollDegree));
1096:         Exif[i].unit  = _SW("度");
1097:         i++;
1098:         Exif[i].label = _SW("ピッチ角");
1099:         Exif[i].data  = _SW(to_string(imageEXIF.GeoLocation.PitchDegree));
1100:         Exif[i].unit  = _SW("度");
1101:         i++;
1102:         Exif[i].label = _SW("ヨー角");
1103:         Exif[i].data  = _SW(to_string(imageEXIF.GeoLocation.YawDegree));
1104:         Exif[i].unit  = _SW("度");
1105:         i++;
1106:     }
1107:     if (imageEXIF.GeoLocation.hasSpeed()) {
1108:         Exif[i].label = _SW("飛行速度(X方向)");
1109:         Exif[i].data  = _SW(to_string(imageEXIF.GeoLocation.SpeedX));
1110:         Exif[i].unit  = _SW("メートル/秒");
1111:         i++;
1112:         Exif[i].label = _SW("飛行速度(Y方向)");
1113:         Exif[i].data  = _SW(to_string(imageEXIF.GeoLocation.SpeedY));
1114:         Exif[i].unit  = _SW("メートル/秒");
1115:         i++;
1116:         Exif[i].label = _SW("飛行速度(Z方向)");
1117:         Exif[i].data  = _SW(to_string(imageEXIF.GeoLocation.SpeedZ));
1118:         Exif[i].unit  = _SW("メートル/秒");
1119:         i++;
1120:     }
1121:     if (imageEXIF.GeoLocation.AccuracyXY > 0) {
1122:         Exif[i].label = _SW("GPS精度(XY方向)");
1123:         Exif[i].data  = _SW(to_string(imageEXIF.GeoLocation.AccuracyXY));
1124:         Exif[i].unit  = _SW("メートル");
1125:         i++;
1126:     }
1127:     if (imageEXIF.GeoLocation.AccuracyZ > 0) {
1128:         Exif[i].label = _SW("GPS精度(Z方向)");
1129:         Exif[i].data  = _SW(to_string(imageEXIF.GeoLocation.AccuracyZ));
1130:         Exif[i].unit  = _SW("メートル");
1131:         i++;
1132:     }
1133: 
1134:     if (imageEXIF.GeoLocation.GPSDOP >0) {
1135:         Exif[i].label = _SW("GPS精度低下率");
1136:         snprintf(buff, SIZE_BUFF, "%g", imageEXIF.GeoLocation.GPSDOP);
1137:         Exif[i].data  = _SW(buff);
1138:         i++;
1139:     }
1140:     if (imageEXIF.GeoLocation.GPSDifferential >0) {
1141:         Exif[i].label = _SW("GPS差分補正");
1142:         switch (imageEXIF.GeoLocation.GPSDifferential) {
1143:             case 0:
1144:                 ss = "差分補正なし";
1145:                 break;
1146:             case 1:
1147:                 ss = "差分補正適用";
1148:                 break;
1149:             default:
1150:                 ss = "不明";
1151:                 break;
1152:         }
1153:         Exif[i].data  = _SW(ss);
1154:         i++;
1155:     }
1156: 
1157:     // GPS日時関係
1158:     if (! imageEXIF.GeoLocation.GPSDateStamp.empty()) {
1159:         regex pattern(R"((\d{4}):(\d{2}):(\d{2}))");
1160:      string ss = regex_replace(imageEXIF.GeoLocation.GPSDateStamp, pattern, "$1-$2-$3T");
1161:         if (! imageEXIF.GeoLocation.GPSTimeStamp.empty()) {
1162:             istringstream iss((string)imageEXIF.GeoLocation.GPSTimeStamp);
1163:             int hour, min, sec;
1164:             iss >> hour >> min >> sec;
1165:             snprintf(buff, SIZE_BUFF, "T%02d:%02d:%02dZ", hour, min, sec);
1166:             ss +buff;
1167:         }
1168:         Exif[i].label = _SW("GPS日時");
1169:         Exif[i].data  = _SW(ss);
1170:         i++;
1171:     }
1172: 
1173:     return ret;
1174: }

Exif情報の取得は、サムネイル領域への表示とは別に行う。
tinyEXIF のクラス EXIFStreamFile を使って画像ファイルを読み込むことで、Exif情報を取得できる。これを、冒頭で定義したクラス配列 Exif へ逐次代入するのがユーザー関数 loadExif である。
逐次代入するのに合わせ、タグ名(データ項目名)を日本語にしている。exif-subifdで使われるtagデジタルスチルカメラ用画像ファイルフォーマット規格 Exif 2.3などを参考にした。

Exif情報は、クラス配列 Exif へ格納する。各々のExifタグに対し、データ項目(ラベル)、データ本体、単位の3つ組みデータとする。表示時にはデータ本体と単位を結合するが、CSVファイル出力時には、Excelで加工がしやすいよう、それぞれをカンマで区切って出力する。

撮影日時関係は、ISO 8601形式に変換するようにした。前述のUTCとの時差データがあれば、それを結合する。

共通手順、モジュールなど

参考サイト

(この項おわり)
header