目次
サンプル・プログラム
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.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入力処理を改良 |
2.0.0 | 2024/04/27 | Edgeブラウザ対応,64ビット対応 |
バージョン | 更新日 | 内容 |
---|---|---|
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.5 | 2022/09/03 | デバッグコード埋め込み |
1.4 | 2021/05/01 | makeMapLeaflet() 引数追加 |
バージョン | 更新日 | 内容 |
---|---|---|
1.2.0 | 2024/05/06 | getModulePath() 追加 |
1.12 | 2021/01/31 | readWebContents() 引数post追加 |
1.11 | 2020/10/17 | htmlspecialchars() 追加 |
1.1 | 2020/10/17 | GetVersion2()追加,readWebContents() 引数ua追加 |
1.01 | 2020/10/03 | setClipboardData() bug-fix |
バージョン | 更新日 | 内容 |
---|---|---|
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 APPNAME "photomapwin" //アプリケーション名
47: #define APPNAMEJP "撮影場所をマッピング" //アプリケーション名(日本語)
48: #define APPVERSION "2.3.0" //バージョン
49: #define APPYEAR "2020-2024" //作成年
50: #define REFERENCE "https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-13-01.shtm" // 参考サイト
51:
52: //char*バッファサイズ
53: #define SIZE_BUFF 512
54:
55: //ListViewItemの最大文字長:変更不可
56: #define MAX_LISTVIEWITEM 258
57:
58: //ヘルプ・ファイル
59: #define HELPFILE ".\\etc\\help.chm"
60:
61: //デフォルト保存ファイル名
62: #define SAVEFILE "photomapwin.csv"
63:
64: //マップID
65: #define MAP_ID "map_id"
66: //地図の大きさ
67: #define MAP_WIDTH 650 //地図の幅(ピクセル)
68: #define MAP_HEIGHT 345 //地図の高さ(ピクセル)
69: //経度・緯度(初期値)
70: #define DEF_LONGITUDE 139.766667
71: double Longitude = DEF_LONGITUDE;
72: #define DEF_LATITUDE 35.681111
73: double Latitude = DEF_LATITUDE;
74: //地図拡大率(初期値)
75: #define DEF_ZOOM 13
76: int Zoom = DEF_ZOOM;
77: //地図の種類(初期値)
78: #define DEF_MAPTYPE "GSISTD"
79: string Maptype = DEF_MAPTYPE;
解説:データ構造
photomapwin.cpp
140: // Exif格納クラス
141: #define SIZE_EXIFS 100 // 最大格納数
142: class _Exif {
143: public:
144: wstring label = L""; // データ項目
145: wstring data = L""; // データ本体
146: wstring unit = L""; // 単位
147: } Exif[SIZE_EXIFS];
解説:フルパスからファイル名を取り出す
photomapwin.cpp
356: /**
357: * ファイル名(フルパス)からファイル名(拡張子を除く)を取り出す
358: * @param wstring path ファイル名(フルパス)
359: * @return wstring ファイル名(拡張子を除く)
360: */
361: wstring wgetBasename(wstring path) {
362: wstring basename = L"";
363:
364: //最後のディレクトリ識別子を探す
365: size_t pos = path.rfind(_SW("\\"));
366: if (pos == string::npos) {
367: return basename;
368: }
369: //ファイル名+拡張子
370: wstring fname = path.substr(pos + 1);
371:
372: //拡張子を除く
373: pos = fname.rfind(_SW("."));
374: if (pos == string::npos) {
375: basename = fname;
376: } else {
377: basename = fname.substr(0, pos);
378: }
379: return basename;
380: }
Boost C++ライブラリにも同様関数があるが、うまくコンパイルできなかったので、自力でコーディングした。シフトJIS文字列では、ディレクトリ識別子 ¥(\0x5C)を含む全角文字があるため、ファイル名はワイド文字列に変換して処理する。
解説:画像ファイルを読み込む
photomapwin.cpp
565: /**
566: * 画像ファイルの表示
567: * @param HWND hWnd 表示するウィンドウ・ハンドル
568: * @param const char *fname 画像ファイル名
569: * @return bool TRUE:表示成功/FALSE:失敗
570: */
571: bool onPaint(HWND hWnd, const char *fname) {
572: HDC hdc;
573: PAINTSTRUCT ps;
574: static HDC hdcBMP;
575: static int ix,iy;
576: int sx, sy;
577: RECT rect;
578: bool ret = TRUE;
579:
580: //画像ファイル読み込み
581: hdc = GetDC(hWnd);
582: if(loadImageOnMemory((TCHAR*)fname, &hdcBMP, hdc, &ix, &iy) == FALSE) {
583: ErrorMessage = "画像ファイル " + (string)fname + " の読み込みに失敗";
584: return FALSE;
585: }
586: InvalidateRect(hWnd, 0, TRUE);
587: hdc = BeginPaint(hWnd, &ps);
588: GetClientRect(hWnd, &rect);
589:
590: //領域クリア
591: HPEN hNewPen = (HPEN)CreatePen(PS_INSIDEFRAME, 4, RGB(255, 255, 255));
592: HPEN hOldPen = (HPEN)SelectObject(hdc,hNewPen);
593: HBRUSH hNewBrush = (HBRUSH)CreateSolidBrush(RGB(255, 255, 255));
594: HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, hNewBrush);
595: Rectangle(hdc, 0, 0, rect.right, rect.bottom);
596: DeleteObject(SelectObject(hdc, hOldBrush));
597: DeleteObject(SelectObject(hdc, hOldPen));
598:
599: //縮小表示
600: reSizeImage(&sx, &sy, ix, iy, (rect.right - rect.left), (rect.bottom - rect.top));
601: SetStretchBltMode(hdc, HALFTONE);
602: StretchBlt(hdc, 0, 0, sx, sy, hdcBMP, 0, 0, ix, iy, SRCCOPY);
603: EndPaint(hWnd, &ps);
604: ReleaseDC(hWnd, hdc);
605:
606: return ret;
607: }
photomapwin.cpp
448: /**
449: * 画像ファイルをGDI+を使って読み込む
450: * Orientation情報があれば,画像を回転させる
451: * @param TCHAR* szFile 画像ファイル名
452: * @param HDC* hdcBMP オフスクリーン用デバイスコンテキストを格納
453: * @param HDC hdc デバイスコンテキスト
454: * @param int* ix, iy 読み込んだ画像の横・縦ピクセル数
455: * @return なし
456: */
457: bool loadImageOnMemory(TCHAR* szFile, HDC* hdcBMP, HDC hdc, int* ix, int* iy) {
458: Image* imageP;
459: WCHAR wTitle[MAX_PATH];
460: BITMAPINFOHEADER bmih;
461: HBITMAP hBitmap;
462: BYTE *pBits;
463:
464: //読み込むファイル名
465: MultiByteToWideChar(932, 0, szFile, -1, wTitle, sizeof(wTitle) / sizeof(TCHAR));
466: imageP = Bitmap::FromFile(wTitle);
467: if (imageP == 0) {
468: *ix = 0;
469: *iy = 0;
470: *hdcBMP = 0;
471: return FALSE;
472: }
473:
474: // ExifのOrientationを取得
475: UINT size = imageP->GetPropertyItemSize(PropertyTagOrientation);
476: PropertyItem* prop = (PropertyItem*)malloc(size);
477: if (prop) {
478: imageP->GetPropertyItem(PropertyTagOrientation, size, prop);
479: int orientation = *(short*)prop->value;
480: free(prop);
481:
482: // Orientationに基づいて回転
483: switch (orientation) {
484: case 3: // 180度回転
485: imageP->RotateFlip(Rotate180FlipNone);
486: break;
487: case 6: // 90度時計回り
488: imageP->RotateFlip(Rotate90FlipNone);
489: break;
490: case 8: // 90度反時計回り
491: imageP->RotateFlip(Rotate270FlipNone);
492: break;
493: case 2: // 水平方向に反転
494: imageP->RotateFlip(RotateNoneFlipX);
495: break;
496: case 4: // 垂直方向に反転
497: imageP->RotateFlip(RotateNoneFlipY);
498: break;
499: case 5: // 90度反時計回り+垂直反転
500: imageP->RotateFlip(Rotate270FlipX);
501: break;
502: case 7: // 90度時計回り+垂直反転
503: imageP->RotateFlip(Rotate90FlipX);
504: break;
505: default: // Orientationが1の場合はそのまま
506: break;
507: }
508: }
509:
510: // 画像サイズを取得
511: bmih.biSize = sizeof(bmih);
512: bmih.biWidth = *ix = imageP->GetWidth();
513: bmih.biHeight = *iy = imageP->GetHeight();
514: bmih.biPlanes = 1;
515: bmih.biBitCount = 32;
516: bmih.biCompression = BI_RGB;
517: bmih.biSizeImage = 0;
518: bmih.biXPelsPerMeter = 0;
519: bmih.biYPelsPerMeter = 0;
520: bmih.biClrUsed = 0;
521: bmih.biClrImportant = 0;
522: hBitmap = CreateDIBSection(NULL, (BITMAPINFO*)&bmih, 0, (void**)&pBits, NULL, 0);
523: if (pBits == NULL) {
524: ErrorMessage = "メモリ不足";
525: DeleteObject(hBitmap);
526: delete imageP;
527: *ix = 0;
528: *iy = 0;
529: return FALSE;
530: }
531: *hdcBMP = CreateCompatibleDC(hdc);
532: // HOldBitmap = (HBITMAP)SelectObject(*hdcBMP, hBitmap);
533: SelectObject(*hdcBMP, hBitmap);
534: Graphics MyGraphics(*hdcBMP);
535: MyGraphics.DrawImage(imageP, 0, 0, imageP->GetWidth(), imageP->GetHeight());
536: delete imageP;
537:
538: return TRUE;
539: }
photomapwin.cpp
541: /**
542: * 縦横比を保った画像サイズ計算
543: * @param int* sx, sy 変換後画像サイズを格納
544: * @param int px, py 元画像の縦横サイズ
545: * @param int dx, dy 変換後領域の縦横サイズ
546: * @return なし
547: */
548: void reSizeImage(int* sx, int* sy, int px, int py, int dx, int dy) {
549: double pxy = double(px) / double(py); //元画像の横縦比
550: double dxy = double(dx) / double(dy); //変換後の横縦比
551: int dx2, dy2;
552:
553: //横を基準に縦を縮小
554: if(pxy > dxy) {
555: dx2 = dx;
556: dy2 = (int)double(dx / pxy);
557: } else {
558: dy2 = dy;
559: dx2 = (int)double(dy * pxy);
560: }
561: *sx = dx2;
562: *sy = dy2;
563: }
まず、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
610: /**
611: * 画像ファイルを開いてExif情報を取得する(実作業)
612: * @param const char *fname 保存ファイル名
613: * @return bool TRUE:取得成功/FALSE:失敗
614: */
615: bool loadExif(const char *fname) {
616: double f;
617: string ss;
618: char buff[SIZE_BUFF + 1];
619: bool ret = TRUE;
620:
621: //画像ファイルのオープン
622: EXIFStreamFile stream(fname);
623: if (! stream.IsValid()) {
624: ErrorMessage = "画像ファイル " + (string)fname + " の読み込みに失敗";
625: return FALSE;
626: }
627: //Exif情報の取得
628: TinyEXIF::EXIFInfo imageEXIF(stream);
629:
630: //Exif情報を配列Exifへ代入
631: //初期化
632: for (int i = 0; i < SIZE_EXIFS; i++) {
633: Exif[i].label = L"";
634: Exif[i].data = L"";
635: Exif[i].unit = L"";
636: }
637: //代入
638: int i = 0;
639:
640: //位置情報
641: if (imageEXIF.GeoLocation.hasLatLon()) {
642: Exif[i].label = _SW("緯度");
643: Exif[i].data = _SW(to_string(imageEXIF.GeoLocation.Latitude));
644: Exif[i].unit = _SW("度");
645: Latitude = imageEXIF.GeoLocation.Latitude;
646: i++;
647: Exif[i].label = _SW("経度");
648: Exif[i].data = _SW(to_string(imageEXIF.GeoLocation.Longitude));
649: Exif[i].unit = _SW("度");
650: Longitude = imageEXIF.GeoLocation.Longitude;
651: i++;
652: } else {
653: ErrorMessage = "画像ファイル " + (string)fname + " に位置情報が存在しない";
654: ret = FALSE;
655: }
656: if (imageEXIF.GeoLocation.hasAltitude()) {
657: snprintf(buff, SIZE_BUFF, "%.1f", imageEXIF.GeoLocation.Altitude);
658: switch (imageEXIF.GeoLocation.AltitudeRef) {
659: case 0:
660: Exif[i].unit = _SW("メートル(海抜)");
661: break;
662: case 1:
663: Exif[i].unit = _SW("メートル(海面下)");
664: break;
665: default:
666: Exif[i].unit = _SW("メートル");
667: break;
668: }
669: Exif[i].label = _SW("高度");
670: Exif[i].data = _SW((string)buff);
671: i++;
672: }
673: if (! imageEXIF.GeoLocation.GPSMapDatum.empty()) {
674: Exif[i].label = _SW("測地系");
675: Exif[i].data = _SW(imageEXIF.GeoLocation.GPSMapDatum);
676: i++;
677: }
678: //画像情報
679: if (imageEXIF.ImageWidth || imageEXIF.ImageHeight) {
680: Exif[i].label = _SW("画像の幅");
681: Exif[i].data = _SW(to_string(imageEXIF.ImageWidth));
682: Exif[i].unit = _SW("ピクセル");
683: i++;
684: Exif[i].label = _SW("画像の高さ");
685: Exif[i].data = _SW(to_string(imageEXIF.ImageHeight));
686: Exif[i].unit = _SW("ピクセル");
687: i++;
688: }
689: if (imageEXIF.RelatedImageWidth || imageEXIF.RelatedImageHeight) {
690: Exif[i].label = _SW("画像の幅");
691: Exif[i].data = _SW(to_string(imageEXIF.RelatedImageWidth));
692: Exif[i].unit = _SW("ピクセル");
693: i++;
694: Exif[i].label = _SW("画像の高さ");
695: Exif[i].data = _SW(to_string(imageEXIF.RelatedImageHeight));
696: Exif[i].unit = _SW("ピクセル");
697: i++;
698: }
699: if (! imageEXIF.ImageDescription.empty()) {
700: Exif[i].label = _SW("画像タイトル");
701: Exif[i].data = _SW(imageEXIF.ImageDescription);
702: i++;
703: }
704: if (! imageEXIF.Make.empty() || ! imageEXIF.Model.empty()) {
705: Exif[i].label = _SW("カメラのメーカー名");
706: Exif[i].data = _SW(imageEXIF.Make);
707: i++;
708: Exif[i].label = _SW("カメラのモデル名");
709: Exif[i].data = _SW(imageEXIF.Model);
710: i++;
711: }
712: if (! imageEXIF.SerialNumber.empty()) {
713: Exif[i].label = _SW("シリアル番号");
714: Exif[i].data = _SW(imageEXIF.SerialNumber);
715: i++;
716: }
717: if (imageEXIF.Orientation) {
718: Exif[i].label = _SW("画像方向");
719: switch (imageEXIF.Orientation) {
720: case 1:
721: Exif[i].data = _SW("左上");
722: break;
723: case 3:
724: Exif[i].data = _SW("右下");
725: break;
726: case 6:
727: Exif[i].data = _SW("右上");
728: break;
729: case 8:
730: Exif[i].data = _SW("左下");
731: break;
732: default:
733: Exif[i].data = _SW("不明");
734: break;
735: }
736: i++;
737: }
738: if (imageEXIF.XResolution || imageEXIF.YResolution || imageEXIF.ResolutionUnit) {
739: ss = "";
740: switch (imageEXIF.ResolutionUnit) {
741: case 2:
742: ss = "インチ";
743: break;
744: case 3:
745: ss = "センチ";
746: break;
747: default:
748: ss = "";
749: break;
750: }
751: Exif[i].label = _SW("幅の解像度");
752: Exif[i].data = _SW(to_string((int)imageEXIF.XResolution));
753: Exif[i].unit = _SW(ss);
754: i++;
755: Exif[i].label = _SW("高さの解像度");
756: Exif[i].data = _SW(to_string((int)imageEXIF.YResolution));
757: Exif[i].unit = _SW(ss);
758: i++;
759: }
760: if (imageEXIF.BitsPerSample) {
761: Exif[i].label = _SW("画像のビットの深さ");
762: Exif[i].data = _SW(to_string(imageEXIF.BitsPerSample));
763: i++;
764: }
765: if (! imageEXIF.Software.empty()) {
766: Exif[i].label = _SW("ソ\フトウェア");
767: Exif[i].data = _SW(imageEXIF.Software);
768: i++;
769: }
770:
771: // 撮影日時関係
772: regex pattern(R"((\d{4}):(\d{2}):(\d{2})\s+(\d{2}:\d{2}:\d{2}))");
773: if (! imageEXIF.DateTime.empty()) {
774: Exif[i].label = _SW("撮影日時");
775: string ss = regex_replace(imageEXIF.DateTime, pattern, "$1-$2-$3T$4");
776: if (! imageEXIF.OffsetTime.empty()) {
777: ss += imageEXIF.OffsetTime;
778: }
779: Exif[i].data = _SW(ss);
780: i++;
781: }
782: if (! imageEXIF.DateTimeOriginal.empty()) {
783: string ss = regex_replace(imageEXIF.DateTimeOriginal, pattern, "$1-$2-$3T$4");
784: Exif[i].label = _SW("撮影日時(オリジナル)");
785: if (! imageEXIF.OffsetTimeOriginal.empty()) {
786: ss += imageEXIF.OffsetTimeOriginal;
787: }
788: Exif[i].data = _SW(ss);
789: i++;
790: }
791: if (! imageEXIF.DateTimeDigitized.empty()) {
792: string ss = regex_replace(imageEXIF.DateTimeDigitized, pattern, "$1-$2-$3T$4");
793: Exif[i].label = _SW("撮影日時(デジタイズ)");
794: if (! imageEXIF.OffsetTimeDigitized.empty()) {
795: ss += imageEXIF.OffsetTimeDigitized;
796: }
797: Exif[i].data = _SW(ss);
798: i++;
799: }
800: if (! imageEXIF.SubSecTimeOriginal.empty()) {
801: Exif[i].label = _SW("撮影日時");
802: Exif[i].data = _SW(imageEXIF.SubSecTimeOriginal);
803: Exif[i].unit = _SW("ミリ秒");
804: i++;
805: }
806:
807: if (! imageEXIF.Copyright.empty()) {
808: Exif[i].label = _SW("著作権者");
809: Exif[i].data = _SW(imageEXIF.Copyright);
810: i++;
811: }
812: Exif[i].label = _SW("露光制御");
813: switch (imageEXIF.ExposureProgram) {
814: case 1:
815: Exif[i].data = _SW("マニュアル設定");
816: break;
817: case 2:
818: Exif[i].data = _SW("通常のプログラムAE");
819: break;
820: case 3:
821: Exif[i].data = _SW("絞り優先");
822: break;
823: case 4:
824: Exif[i].data = _SW("シャッター速度優先");
825: break;
826: case 5:
827: Exif[i].data = _SW("低速プログラム");
828: break;
829: case 6:
830: Exif[i].data = _SW("高速プログラム");
831: break;
832: case 7:
833: Exif[i].data = _SW("ポートレートモード");
834: break;
835: case 8:
836: Exif[i].data = _SW("風景モード");
837: break;
838: default:
839: Exif[i].data = _SW("不明");
840: break;
841: }
842: i++;
843: Exif[i].label = _SW("ISO感度");
844: Exif[i].data = _SW(to_string(imageEXIF.ISOSpeedRatings));
845: i++;
846: Exif[i].label = _SW("シャッター速度");
847: f = 1.0 / imageEXIF.ShutterSpeedValue;
848: Exif[i].data = _SW("1/" + to_string((int)f));
849: Exif[i].unit = _SW("秒");
850: i++;
851: Exif[i].label = _SW("露出時間");
852: f = 1.0 / imageEXIF.ExposureTime;
853: Exif[i].data = _SW("1/" + to_string((int)f));
854: Exif[i].unit = _SW("秒");
855: i++;
856: Exif[i].label = _SW("絞り値");
857: Exif[i].data = _SW(to_string(imageEXIF.ApertureValue));
858: i++;
859: Exif[i].label = _SW("F値");
860: snprintf(buff, SIZE_BUFF, "%g", imageEXIF.FNumber);
861: Exif[i].data = _SW(buff);
862: i++;
863: Exif[i].label = _SW("レンズ焦点距離");
864: Exif[i].data = _SW(to_string((int)imageEXIF.FocalLength));
865: Exif[i].unit = _SW("ミリ");
866: i++;
867: if ((float)imageEXIF.BrightnessValue > 0) {
868: Exif[i].label = _SW("輝度値");
869: Exif[i].data = _SW(to_string(imageEXIF.BrightnessValue));
870: i++;
871: }
872: if ((float)imageEXIF.ExposureBiasValue > 0) {
873: Exif[i].label = _SW("露出補正値");
874: Exif[i].data = _SW(to_string(imageEXIF.ExposureBiasValue));
875: i++;
876: }
877: if ((float)imageEXIF.SubjectDistance > 0) {
878: Exif[i].label = _SW("被写体距離");
879: Exif[i].data = _SW(to_string(imageEXIF.SubjectDistance));
880: i++;
881: }
882: Exif[i].label = _SW("フラッシュ");
883: switch (imageEXIF.Flash) {
884: case 0:
885: Exif[i].data = _SW("非発光");
886: break;
887: case 1:
888: Exif[i].data = _SW("発光");
889: break;
890: case 5:
891: Exif[i].data = _SW("発光したが反射光検出できず");
892: break;
893: case 6:
894: Exif[i].data = _SW("発光して反射光検出");
895: break;
896: default:
897: Exif[i].data = _SW("不明");
898: break;
899: }
900: i++;
901: Exif[i].label = _SW("測光モード");
902: switch (imageEXIF.MeteringMode) {
903: case 0:
904: Exif[i].data = _SW("平均測光");
905: break;
906: case 1:
907: Exif[i].data = _SW("中央重点");
908: break;
909: case 2:
910: Exif[i].data = _SW("スポット");
911: break;
912: case 4:
913: Exif[i].data = _SW("多点スポット");
914: break;
915: case 5:
916: Exif[i].data = _SW("マルチセグメント");
917: break;
918: case 6:
919: Exif[i].data = _SW("部分測光");
920: break;
921: default:
922: Exif[i].data = _SW("不明");
923: break;
924: }
925: i++;
926: Exif[i].label = _SW("光源");
927: switch (imageEXIF.LightSource) {
928: case 1:
929: Exif[i].data = _SW("昼光");
930: break;
931: case 2:
932: Exif[i].data = _SW("蛍光燈");
933: break;
934: case 3:
935: Exif[i].data = _SW("白熱電球");
936: break;
937: case 4:
938: Exif[i].data = _SW("フラッシュ");
939: break;
940: case 9:
941: Exif[i].data = _SW("快晴");
942: break;
943: case 10:
944: Exif[i].data = _SW("曇天");
945: break;
946: case 11:
947: Exif[i].data = _SW("日陰");
948: break;
949: case 12:
950: Exif[i].data = _SW("昼光色");
951: break;
952: case 13:
953: Exif[i].data = _SW("昼白色");
954: break;
955: case 14:
956: Exif[i].data = _SW("蛍光色");
957: break;
958: case 15:
959: Exif[i].data = _SW("白色");
960: break;
961: case 17:
962: Exif[i].data = _SW("標準ライトA");
963: break;
964: case 18:
965: Exif[i].data = _SW("標準ライトB");
966: break;
967: case 19:
968: Exif[i].data = _SW("標準ライトC");
969: break;
970: case 20:
971: Exif[i].data = _SW("D55");
972: break;
973: case 21:
974: Exif[i].data = _SW("D65");
975: break;
976: case 22:
977: Exif[i].data = _SW("D75");
978: break;
979: case 23:
980: Exif[i].data = _SW("D50");
981: break;
982: case 24:
983: Exif[i].data = _SW("ISO白熱電球");
984: break;
985: default:
986: Exif[i].data = _SW("不明");
987: break;
988: }
989: i++;
990: if (imageEXIF.Calibration.FocalLength != 0) {
991: Exif[i].label = _SW("(ピクセル)");
992: Exif[i].data = _SW(to_string(imageEXIF.Calibration.FocalLength));
993: i++;
994: }
995: if (imageEXIF.Calibration.OpticalCenterX != 0) {
996: Exif[i].label = _SW("(ピクセル)");
997: Exif[i].data = _SW(to_string(imageEXIF.Calibration.OpticalCenterX));
998: i++;
999: }
1000: if (imageEXIF.Calibration.OpticalCenterY != 0) {
1001: Exif[i].label = _SW("(ピクセル)");
1002: Exif[i].data = _SW(to_string(imageEXIF.Calibration.OpticalCenterY));
1003: i++;
1004: }
1005: //レンズ情報
1006: if ((int)imageEXIF.LensInfo.FStopMin > 0) {
1007: Exif[i].label = _SW("最小絞り値");
1008: Exif[i].data = _SW(to_string((int)imageEXIF.LensInfo.FStopMin));
1009: i++;
1010: }
1011: if ((int)imageEXIF.LensInfo.FStopMax > 0) {
1012: Exif[i].label = _SW("最大絞り値");
1013: Exif[i].data = _SW(to_string((int)imageEXIF.LensInfo.FStopMax));
1014: i++;
1015: }
1016: if ((int)imageEXIF.LensInfo.FocalLengthMin > 0) {
1017: Exif[i].label = _SW("広角側");
1018: Exif[i].data = _SW(to_string((int)imageEXIF.LensInfo.FocalLengthMin));
1019: Exif[i].unit = _SW("ミリ");
1020: i++;
1021: }
1022: if ((int)imageEXIF.LensInfo.FocalLengthMax > 0) {
1023: Exif[i].label = _SW("望遠側");
1024: Exif[i].data = _SW(to_string((int)imageEXIF.LensInfo.FocalLengthMax));
1025: Exif[i].unit = _SW("ミリ");
1026: i++;
1027: }
1028: if ((int)imageEXIF.LensInfo.DigitalZoomRatio > 0) {
1029: Exif[i].label = _SW("デジタルズーム倍率");
1030: Exif[i].data = _SW(to_string((int)imageEXIF.LensInfo.DigitalZoomRatio));
1031: i++;
1032: }
1033: if ((int)imageEXIF.LensInfo.FocalLengthIn35mm > 0) {
1034: Exif[i].label = _SW("焦点距離(35ミリ判相当");
1035: Exif[i].data = _SW(to_string((int)imageEXIF.LensInfo.FocalLengthIn35mm));
1036: Exif[i].unit = _SW("ミリ");
1037: i++;
1038: }
1039: ss = "";
1040: switch (imageEXIF.LensInfo.FocalPlaneResolutionUnit) {
1041: case 2:
1042: ss = "インチ";
1043: break;
1044: case 3:
1045: ss = "センチ";
1046: break;
1047: default:
1048: ss = "";
1049: break;
1050: }
1051: Exif[i].label = _SW("焦点面の幅の解像度");
1052: Exif[i].data = _SW(to_string(imageEXIF.LensInfo.FocalPlaneXResolution));
1053: Exif[i].unit = _SW(ss);
1054: i++;
1055: Exif[i].label = _SW("焦点面の高さの解像度");
1056: Exif[i].data = _SW(to_string(imageEXIF.LensInfo.FocalPlaneYResolution));
1057: Exif[i].unit = _SW(ss);
1058: i++;
1059:
1060: // レンズ関係の情報
1061: if (! imageEXIF.LensInfo.Make.empty()) {
1062: Exif[i].label = _SW("レンズのメーカー名");
1063: Exif[i].data = _SW(imageEXIF.LensInfo.Make);
1064: i++;
1065: }
1066: if (! imageEXIF.LensInfo.Model.empty()) {
1067: Exif[i].label = _SW("レンズのモデル名");
1068: Exif[i].data = _SW(imageEXIF.LensInfo.Model);
1069: i++;
1070: }
1071:
1072: //位置情報
1073: if (imageEXIF.GeoLocation.hasRelativeAltitude()) {
1074: Exif[i].label = _SW("相対高度");
1075: Exif[i].data = _SW(to_string(imageEXIF.GeoLocation.RelativeAltitude));
1076: Exif[i].unit = _SW("メートル");
1077: i++;
1078: }
1079: if (imageEXIF.GeoLocation.hasOrientation()) {
1080: Exif[i].label = _SW("ロール角");
1081: Exif[i].data = _SW(to_string(imageEXIF.GeoLocation.RollDegree));
1082: Exif[i].unit = _SW("度");
1083: i++;
1084: Exif[i].label = _SW("ピッチ角");
1085: Exif[i].data = _SW(to_string(imageEXIF.GeoLocation.PitchDegree));
1086: Exif[i].unit = _SW("度");
1087: i++;
1088: Exif[i].label = _SW("ヨー角");
1089: Exif[i].data = _SW(to_string(imageEXIF.GeoLocation.YawDegree));
1090: Exif[i].unit = _SW("度");
1091: i++;
1092: }
1093: if (imageEXIF.GeoLocation.hasSpeed()) {
1094: Exif[i].label = _SW("飛行速度(X方向)");
1095: Exif[i].data = _SW(to_string(imageEXIF.GeoLocation.SpeedX));
1096: Exif[i].unit = _SW("メートル/秒");
1097: i++;
1098: Exif[i].label = _SW("飛行速度(Y方向)");
1099: Exif[i].data = _SW(to_string(imageEXIF.GeoLocation.SpeedY));
1100: Exif[i].unit = _SW("メートル/秒");
1101: i++;
1102: Exif[i].label = _SW("飛行速度(Z方向)");
1103: Exif[i].data = _SW(to_string(imageEXIF.GeoLocation.SpeedZ));
1104: Exif[i].unit = _SW("メートル/秒");
1105: i++;
1106: }
1107: if (imageEXIF.GeoLocation.AccuracyXY > 0) {
1108: Exif[i].label = _SW("GPS精度(XY方向)");
1109: Exif[i].data = _SW(to_string(imageEXIF.GeoLocation.AccuracyXY));
1110: Exif[i].unit = _SW("メートル");
1111: i++;
1112: }
1113: if (imageEXIF.GeoLocation.AccuracyZ > 0) {
1114: Exif[i].label = _SW("GPS精度(Z方向)");
1115: Exif[i].data = _SW(to_string(imageEXIF.GeoLocation.AccuracyZ));
1116: Exif[i].unit = _SW("メートル");
1117: i++;
1118: }
1119:
1120: if (imageEXIF.GeoLocation.GPSDOP >= 0) {
1121: Exif[i].label = _SW("GPS精度低下率");
1122: snprintf(buff, SIZE_BUFF, "%g", imageEXIF.GeoLocation.GPSDOP);
1123: Exif[i].data = _SW(buff);
1124: i++;
1125: }
1126: if (imageEXIF.GeoLocation.GPSDifferential >= 0) {
1127: Exif[i].label = _SW("GPS差分補正");
1128: switch (imageEXIF.GeoLocation.GPSDifferential) {
1129: case 0:
1130: ss = "差分補正なし";
1131: break;
1132: case 1:
1133: ss = "差分補正適用";
1134: break;
1135: default:
1136: ss = "不明";
1137: break;
1138: }
1139: Exif[i].data = _SW(ss);
1140: i++;
1141: }
1142:
1143: // GPS日時関係
1144: if (! imageEXIF.GeoLocation.GPSDateStamp.empty()) {
1145: regex pattern(R"((\d{4}):(\d{2}):(\d{2}))");
1146: string ss = regex_replace(imageEXIF.GeoLocation.GPSDateStamp, pattern, "$1-$2-$3T");
1147: if (! imageEXIF.GeoLocation.GPSTimeStamp.empty()) {
1148: istringstream iss((string)imageEXIF.GeoLocation.GPSTimeStamp);
1149: int hour, min, sec;
1150: iss >> hour >> min >> sec;
1151: snprintf(buff, SIZE_BUFF, "T%02d:%02d:%02dZ", hour, min, sec);
1152: ss += buff;
1153: }
1154: Exif[i].label = _SW("GPS日時");
1155: Exif[i].data = _SW(ss);
1156: i++;
1157: }
1158:
1159: return ret;
1160: }
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 ドラフト版
(2024年10月26日)Orientation情報があれば表示画像を回転させる,使用ライブラリ更新
(2024年10月13日)UTCとの時差,その他表記方法見直し,使用ライブラリ更新
(2024年8月17日)使用ライブラリ更新
(2024年5月3日)API入力処理を改良
(2024年4月27日)Edgeブラウザ対応,64ビット対応
(2024年3月23日)使用ライブラリ更新