
目次
サンプル・プログラム
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.cpp | APIキーの管理(ソース) |
sour/apikey.hpp | APIキーの管理(ヘッダ) |
sour/TinyEXIF.h | TinyEXIFソース・プログラム |
sour/TinyEXIF.cpp | TinyEXIFソース・プログラム |
sour/tinyxml2.h | tinyxml2ソース・プログラム |
sour/tinyxml2.cpp | tinyxml2ソース・プログラム |
sour/WebView2.h | WebView2に関わるヘッダ |
sour/event.h | WebView2用インターフェース(ヘッダ) |
sour/event.cpp | WebView2用インターフェース(ソース) |
sour/pahooWebView2.cpp | WebView2に関わる関数(ソース) |
sour/pahooWebView2.hpp | WebView2に関わる関数(ヘッダ) |
バージョン | 更新日 | 内容 |
---|---|---|
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入力処理を改良 |
バージョン | 更新日 | 内容 |
---|---|---|
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()メソッド追加 |
バージョン | 更新日 | 内容 |
---|---|---|
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() 追加 |
バージョン | 更新日 | 内容 |
---|---|---|
1.0.0 | 2024/04/27 | 初版 |
バージョン | 更新日 | 内容 |
---|---|---|
2.0.0 | 2024/04/29 | createSetAPIkey, processSetAPIkey に統合 |
1.0 | 2020/09/30 | 初版 |
使用ライブラリ
Exif情報を解釈するのに、Seacave氏による TinyEXIF と、このモジュールが呼び出す inyxml2 を、https://github.com/cdcseacave/TinyEXIF と https://github.com/leethomason/tinyxml2 から、それぞれダウンロードする。後述するとおり、TinyEXIF には手を加える。
また、地図表示にWebブラウザ・コントロールを利用するため "WebView2Loader.dll" を利用する。jchv / webview2-in-mingw からダウンロードできる。
リソースの準備
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];
解説:フルパスからファイル名を取り出す
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: }
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: }

まず、GDI+ を使って画像データをメモリに読み込むのがユーザー関数 loadImageOnMemory である。
読み込んだ画像の縦横のピクセル数を取り出し、ユーザー関数 reSizeImage を使って、サムネイル領域のサイズに縦横比が合うように縮小する。メモリ上の画像データを縮小してサムネイル領域に表示するのは StretchBlt 関数である。
縮小したら、
Exif規格とTinyExifの改良
改定履歴を読むと、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 = 0; i < SIZE_EXIFS; i++) {
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: }
tinyEXIF のクラス EXIFStreamFile を使って画像ファイルを読み込むことで、Exif情報を取得できる。これを、冒頭で定義したクラス配列 Exif へ逐次代入するのがユーザー関数 loadExif である。
逐次代入するのに合わせ、タグ名(データ項目名)を日本語にしている。exif-subifdで使われるtag、デジタルスチルカメラ用画像ファイルフォーマット規格 Exif 2.3などを参考にした。

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

撮影日時関係は、ISO 8601形式に変換するようにした。前述のUTCとの時差データがあれば、それを結合する。
共通手順、モジュールなど
- C++ 開発環境の準備:ぱふぅ家のホームページ
- C++ 開発環境の準備 -MSYS2編-
- WiX によるWindowsインストーラー作成:ぱふぅ家のホームページ
- C++ でダイアログボックスを使う
- C++ でイベント駆動型アプリを作る
- 解説:ニュース一覧作成――C++ で Googleニュース検索
- 解説:検索結果をCSVファイルに保存――C++ で Googleニュース検索
- 解説:クリップボード――C++ で Googleニュース検索
- 解説:APIキーの管理――C++ で直近の地震情報を取得する
- 解説:WebView2――C++ で直近の地震情報を取得する
- 解説:マップ表示用HTML生成――C++ で直近の地震情報を取得する
参考サイト
- PHPで撮影場所をマッピング:ぱふぅ家のホームページ
- Exif 2.32 ドラフト版
(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日)使用ライブラリ更新