| 1 | // tMetaData.cpp  |
| 2 | //  |
| 3 | // A class to store image meta-data. Some image formats allow comments and other metadata to be stored inside the  |
| 4 | // image. For example, jpg files may contain EXIF or XMP meta-data. This class is basically a map of key/value strings  |
| 5 | // that may be a member of some tImageXXX types, It currently knows how to parse EXIF and XMP meta-data.  |
| 6 | //  |
| 7 | // Copyright (c) 2022, 2023 Tristan Grimmer.  |
| 8 | // Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby  |
| 9 | // granted, provided that the above copyright notice and this permission notice appear in all copies.  |
| 10 | //  |
| 11 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL  |
| 12 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,  |
| 13 | // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN  |
| 14 | // AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR  |
| 15 | // PERFORMANCE OF THIS SOFTWARE.  |
| 16 |   |
| 17 | #include "Image/tMetaData.h"  |
| 18 | #include "System/tPrint.h"  |
| 19 | #include "Math/tVector3.h"  |
| 20 | #include "TinyEXIF/TinyEXIF.h"  |
| 21 | using namespace tImage;  |
| 22 | using namespace tMath;  |
| 23 |   |
| 24 |   |
| 25 | const char* tMetaTagNames[] =  |
| 26 | {  |
| 27 | // Camera Hardware Tag Names  |
| 28 | "Make" ,  |
| 29 | "Model" ,  |
| 30 | "Serial Number" ,  |
| 31 | "Make Model Serial" ,  |
| 32 |   |
| 33 | // Geo Location Tag Names  |
| 34 | "Latitude DD" ,  |
| 35 | "Latitude" ,  |
| 36 | "Longitude DD" ,  |
| 37 | "Longitude" ,  |
| 38 | "Altitude" ,  |
| 39 | "Altitude Ref" ,  |
| 40 | "Altitude Rel" ,  |
| 41 | "Roll" ,  |
| 42 | "Pitch" ,  |
| 43 | "Yaw" ,  |
| 44 | "VelX" ,  |
| 45 | "VelY" ,  |
| 46 | "VelZ" ,  |
| 47 | "Speed" ,  |
| 48 | "GPS Survey" ,  |
| 49 | "GPS Time Stamp" ,  |
| 50 |   |
| 51 | // Camera Settings Tag Names  |
| 52 | "Shutter Speed" ,  |
| 53 | "Exposure Time" ,  |
| 54 | "Exposure Bias" ,  |
| 55 | "F-Stop" ,  |
| 56 | "Exposure Program" ,  |
| 57 | "ISO" ,  |
| 58 | "Aperture" ,  |
| 59 | "Brightness" ,  |
| 60 | "Metering Mode" ,  |
| 61 | "Flash Present" ,  |
| 62 | "Flash Used" ,  |
| 63 | "Flash Strobe" ,  |
| 64 | "Flash Mode" ,  |
| 65 | "Flash Red-Eye" ,  |
| 66 | "Focal Length" ,  |
| 67 | "Orientation" ,  |
| 68 | "Length Unit" ,  |
| 69 | "X-Pixels Per Unit" ,  |
| 70 | "Y-Pixels Per Unit" ,  |
| 71 | "Bits Per Sample" ,  |
| 72 | "Image Width" ,  |
| 73 | "Image Height" ,  |
| 74 | "Image Width Orig" ,  |
| 75 | "Image Height Orig" ,  |
| 76 | "Date/Time Change" ,  |
| 77 | "Date/Time Orig" ,  |
| 78 | "Date/Time Digitized" ,  |
| 79 |   |
| 80 | // Authoring Note Tag Names  |
| 81 | "Software" ,  |
| 82 | "Description" ,  |
| 83 | "Copyright"   |
| 84 | };  |
| 85 | tStaticAssert(tNumElements(tMetaTagNames) == int(tMetaTag::NumTags));  |
| 86 |   |
| 87 |   |
| 88 | const char* tMetaTagDescs[] =  |
| 89 | {  |
| 90 | // Camera Hardware Tag Descriptions  |
| 91 | "Camera make/manufacturer." ,  |
| 92 | "Camera model." ,  |
| 93 | "Camera serial number." ,  |
| 94 | "Camera unique identifier containing make, model, and serial number.\n"   |
| 95 | "Takes form \"Make | Model | Serial\" when all 3 present." ,  |
| 96 |   |
| 97 | // Geo Location Tag Descriptions  |
| 98 | "Latitude in decimal degrees." ,  |
| 99 | "Latitude in degrees, minutes, seconds followed by N (north) or S (south)." ,  |
| 100 | "Longitude in decimal degrees." ,  |
| 101 | "Longitude in degrees, minutes, seconds followed by W (west) or E (east)." ,  |
| 102 | "Altitude in meters relative to sea-level." ,  |
| 103 | "Relative altitude ground reference. Applies to Altitude Rel value.\n"   |
| 104 | "\"Above Ground\": Reference data unavailable. Assume above ground.\n"   |
| 105 | "\"Above Sea Level\": Ground is above sea level.\n"   |
| 106 | "\"Below Sea Level\": Ground is below sea level." ,  |
| 107 | "Relative altitude in meters. Often how high above ground." ,  |
| 108 | "Flight roll in degrees." ,  |
| 109 | "Flight pitch in degrees." ,  |
| 110 | "Flight yaw in degrees." ,  |
| 111 | "X-Component (forwards/backwards) of velocity in m/s. May be negative. DJI maker-note." ,  |
| 112 | "Y-Component (left/right) of velocity in m/s. May be negative. DJI maker-note." ,  |
| 113 | "Z-Component (up/down) of velocity in m/s. May be negative. DJI maker-note." ,  |
| 114 | "Length of velocity vector in m/s. Speed is always >= 0. DJI maker-note." ,  |
| 115 | "Geodetic survey data." ,  |
| 116 | "UTC Date and time of GPS data in format YYYY-MM-DD hh:mm:ss\n"   |
| 117 | "It's possible one of YYYY-MM-DD or hh:mm:ss is not available." ,  |
| 118 |   |
| 119 | // Camera Settings Tag Descriptions  |
| 120 | "Shutter speed in units 1/s. Reciprocal of exposure time. If not set, computed." ,  |
| 121 | "Exposure time in seconds. Reciprocal of Shutter Speed. If not set, computed." ,  |
| 122 | "Exposure bias in APEX units." ,  |
| 123 | "Ratio of the lens focal length to the diameter of the entrance pupil. Unitless." ,  |
| 124 | "Exposure program. Will be one of following values:\n"   |
| 125 | "\"Not Defined\"\n"   |
| 126 | "\"Manual\"\n"   |
| 127 | "\"Normal Program\"\n"   |
| 128 | "\"Aperture Priority\"\n"   |
| 129 | "\"Shutter Priority\"\n"   |
| 130 | "\"Creative Program\"\n"   |
| 131 | "\"Action Program\"\n"   |
| 132 | "\"Portrait Mode\"\n"   |
| 133 | "\"Landscape Mode\"" ,  |
| 134 | "Equivalent ISO film speed rating." ,  |
| 135 | "Aperture in APEX units." ,  |
| 136 | "Average scene luminance of whole image in APEX units." ,  |
| 137 | "Metering mode. Will be one of following values:\n"   |
| 138 | "\"Unknown\"\n"   |
| 139 | "\"Average\"\n"   |
| 140 | "\"Center Weighted Average\"\n"   |
| 141 | "\"Spot\"\n"   |
| 142 | "\"Multi-spot\"\n"   |
| 143 | "\"Pattern\"\n"   |
| 144 | "\"Partial\"" ,  |
| 145 | "Flash hardware present. Possible values \"Yes\" or \"No\"\n" ,  |
| 146 | "Flash used. Possible values \"Yes\" or \"No\"\n" ,  |
| 147 | "Flash strobe detection. Possible values:\n"   |
| 148 | "\"No Detection\"\n"   |
| 149 | "\"Reserved\"\n"   |
| 150 | "\"Strobe Return Light Not Detected\"\n"   |
| 151 | "\"Strobe Return Light Detected\"" ,  |
| 152 | "Flash camera mode. Possible values:\n"   |
| 153 | "\"Unknown\"\n"   |
| 154 | "\"Compulsory Flash Firing\"\n"   |
| 155 | "\"Compulsory Flash Suppression\"\n"   |
| 156 | "\"Auto\"" ,  |
| 157 | "Flash red-eye reduction. Possible values:\n"   |
| 158 | "\"No Red-Eye Reduction or Unknown\"\n"   |
| 159 | "\"Red-Eye Reduction\"" ,  |
| 160 | "Focal length in mm. Always > 0." ,  |
| 161 | "Information on camera orientation when photo taken. The following\n"   |
| 162 | "transformations may be present in the image data:\n"   |
| 163 | "\"Unspecified\": Not orientation info provided.\n"   |
| 164 | "\"No Transforms\": Image is not mirrored or rotated.\n"   |
| 165 | "\"Flip-Y\": Image is mirrored about vertical axis (right <-> left).\n"   |
| 166 | "\"Flip-XY\": Image flipped about both axes. Same as 180° rotation.\n"   |
| 167 | "\"Flip-X\": Image is mirrored about horizontal axis (top <-> bottom).\n"   |
| 168 | "\"Rot-CW90 Flip-Y\": Image is rotated 90° clockwise and then flipped horizontally.\n"   |
| 169 | "\"Rot-ACW90\": Image is rotated 90° anti-clockwise.\n"   |
| 170 | "\"Rot-ACW90 Flip-Y\": Image is rotated 90° clockwise and then flipped horizontally.\n"   |
| 171 | "\"Rot-CW90\": Image is rotated 90° anti-clockwise." ,  |
| 172 | "The length unit used for the Pixels-per-unit values:\n"   |
| 173 | "\"Not Specified\"\n"   |
| 174 | "\"Inch\"\n"   |
| 175 | "\"cm\"" ,  |
| 176 | "Horizontal pixels per length unit." ,  |
| 177 | "Veritical pixels per length unit." ,  |
| 178 | "Bits per colour component. Not bits per pixel." ,  |
| 179 | "Image width in pixels." ,  |
| 180 | "Image height in pixels." ,  |
| 181 | "Original image width (before edits) in pixels." ,  |
| 182 | "Original image height(before edits) in pixels." ,  |
| 183 | "Date and time the image was changed in format YYYY-MM-DD hh:mm:ss" ,  |
| 184 | "Date and time of original image in format YYYY-MM-DD hh:mm:ss." ,  |
| 185 | "Date and time the image was digitized in format YYYY-MM-DD hh:mm:ss." ,  |
| 186 |   |
| 187 | // Authoring Note Tag Descriptions  |
| 188 | "Software used to edit image." ,  |
| 189 | "Image description." ,  |
| 190 | "Copyright notice."   |
| 191 | };  |
| 192 | tStaticAssert(tNumElements(tMetaTagDescs) == int(tMetaTag::NumTags));  |
| 193 |   |
| 194 |   |
| 195 | const char* tImage::tGetMetaTagName(tMetaTag tag)  |
| 196 | {  |
| 197 | return tMetaTagNames[int(tag)];  |
| 198 | }  |
| 199 |   |
| 200 |   |
| 201 | const char* tImage::tGetMetaTagDesc(tMetaTag tag)  |
| 202 | {  |
| 203 | return tMetaTagDescs[int(tag)];  |
| 204 | }  |
| 205 |   |
| 206 |   |
| 207 | bool tMetaData::Set(const uint8* rawJpgImageData, int numBytes)  |
| 208 | {  |
| 209 | Clear();  |
| 210 | TinyEXIF::EXIFInfo exifInfo;  |
| 211 | int errorCode = exifInfo.parseFrom(data: rawJpgImageData, length: numBytes);  |
| 212 | if (errorCode)  |
| 213 | return false;  |
| 214 |   |
| 215 | SetTags_CamHardware(exifInfo);  |
| 216 | SetTags_GeoLocation(exifInfo);  |
| 217 | SetTags_CamSettings(exifInfo);  |
| 218 | SetTags_AuthorNotes(exifInfo);  |
| 219 |   |
| 220 | return IsValid();  |
| 221 | }  |
| 222 |   |
| 223 |   |
| 224 | void tMetaData::SetTags_CamHardware(const TinyEXIF::EXIFInfo& exifInfo)  |
| 225 | {  |
| 226 | // Make. tString can handle nullptr.  |
| 227 | tString make = exifInfo.Make.c_str();  |
| 228 | if (make.IsValid())  |
| 229 | {  |
| 230 | Data[ int(tMetaTag::Make) ].Set(make);  |
| 231 | NumTagsValid++;  |
| 232 | }  |
| 233 |   |
| 234 | // Model  |
| 235 | tString model = exifInfo.Model.c_str();  |
| 236 | if (model.IsValid())  |
| 237 | {  |
| 238 | Data[ int(tMetaTag::Model) ].Set(model);  |
| 239 | NumTagsValid++;  |
| 240 | }  |
| 241 |   |
| 242 | // SerialNumber  |
| 243 | tString serial = exifInfo.SerialNumber.c_str();  |
| 244 | if (serial.IsValid())  |
| 245 | {  |
| 246 | Data[ int(tMetaTag::SerialNumber) ].Set(serial);  |
| 247 | NumTagsValid++;  |
| 248 | }  |
| 249 |   |
| 250 | // MakeModelSerial  |
| 251 | // Handles any combination of valid and invalid make, model, and serial strings.  |
| 252 | tString makeModelSerial = make + " | " ;  |
| 253 | if (model.IsValid())  |
| 254 | makeModelSerial += model + " | " ;  |
| 255 | if (serial.IsValid())  |
| 256 | makeModelSerial += serial + " | " ;  |
| 257 | makeModelSerial.ExtractRight(suffix: " | " );  |
| 258 |   |
| 259 | if (makeModelSerial.IsValid())  |
| 260 | {  |
| 261 | Data[ int(tMetaTag::MakeModelSerial) ].Set(makeModelSerial);  |
| 262 | NumTagsValid++;  |
| 263 | }  |
| 264 | }  |
| 265 |   |
| 266 |   |
| 267 | void tMetaData::SetTags_GeoLocation(const TinyEXIF::EXIFInfo& exifInfo)  |
| 268 | {  |
| 269 | // If we have LatLong we should have it in DD and DMS formats.  |
| 270 | if (exifInfo.GeoLocation.hasLatLon())  |
| 271 | {  |
| 272 | // LatitudeDD  |
| 273 | double lat = exifInfo.GeoLocation.Latitude;  |
| 274 | Data[ int(tMetaTag::LatitudeDD) ].Set(float(lat));  |
| 275 | NumTagsValid++;  |
| 276 |   |
| 277 | // LatitudeDMS  |
| 278 | // The exifInfo should not have fraction values for the degree and minutes if they did everythng right.  |
| 279 | int degLat = int ( tMath::tRound(v: exifInfo.GeoLocation.LatComponents.degrees) );  |
| 280 | int minLat = int ( tMath::tRound(v: exifInfo.GeoLocation.LatComponents.minutes) );  |
| 281 | int secLat = int ( tMath::tRound(v: exifInfo.GeoLocation.LatComponents.seconds) );  |
| 282 | char dirLat = exifInfo.GeoLocation.LatComponents.direction;  |
| 283 | tString dmsLat;  |
| 284 | tsPrintf(dest&: dmsLat, format: "%d°%d'%d\"%c" , degLat, minLat, secLat, dirLat);  |
| 285 | Data[ int(tMetaTag::LatitudeDMS) ].Set(dmsLat);  |
| 286 | NumTagsValid++;  |
| 287 |   |
| 288 | // LongitudeDD  |
| 289 | double lon = exifInfo.GeoLocation.Longitude;  |
| 290 | Data[ int(tMetaTag::LongitudeDD) ].Set(float(lon));  |
| 291 | NumTagsValid++;  |
| 292 |   |
| 293 | // LongitudeDMS  |
| 294 | int degLon = int ( tMath::tRound(v: exifInfo.GeoLocation.LonComponents.degrees) );  |
| 295 | int minLon = int ( tMath::tRound(v: exifInfo.GeoLocation.LonComponents.minutes) );  |
| 296 | int secLon = int ( tMath::tRound(v: exifInfo.GeoLocation.LonComponents.seconds) );  |
| 297 | char dirLon = exifInfo.GeoLocation.LonComponents.direction;  |
| 298 | tString dmsLon;  |
| 299 | tsPrintf(dest&: dmsLon, format: "%d°%d'%d\"%c" , degLon, minLon, secLon, dirLon);  |
| 300 | Data[ int(tMetaTag::LongitudeDMS) ].Set(dmsLon);  |
| 301 | NumTagsValid++;  |
| 302 | }  |
| 303 |   |
| 304 | if (exifInfo.GeoLocation.hasAltitude())  |
| 305 | {  |
| 306 | double alt = exifInfo.GeoLocation.Altitude;  |
| 307 | Data[ int(tMetaTag::Altitude) ].Set(float(alt));  |
| 308 | NumTagsValid++;  |
| 309 | }  |
| 310 |   |
| 311 | if (exifInfo.GeoLocation.hasRelativeAltitude())  |
| 312 | {  |
| 313 | int8 ref = exifInfo.GeoLocation.AltitudeRef;  |
| 314 | tString refStr("Above Ground" );  |
| 315 | switch (ref)  |
| 316 | {  |
| 317 | case 0: refStr = "Above Sea Level" ; break;  |
| 318 | case -1: refStr = "Below Sea Level" ; break;  |
| 319 | }  |
| 320 | Data[ int(tMetaTag::AltitudeRelRef) ].Set(refStr);  |
| 321 | NumTagsValid++;  |
| 322 |   |
| 323 | double altRel = exifInfo.GeoLocation.RelativeAltitude;  |
| 324 | Data[ int(tMetaTag::AltitudeRel) ].Set(float(altRel));  |
| 325 | NumTagsValid++;  |
| 326 | }  |
| 327 |   |
| 328 | if (exifInfo.GeoLocation.hasOrientation())  |
| 329 | {  |
| 330 | double roll = exifInfo.GeoLocation.RollDegree;  |
| 331 | Data[ int(tMetaTag::Roll) ].Set(float(roll));  |
| 332 | NumTagsValid++;  |
| 333 |   |
| 334 | double pitch = exifInfo.GeoLocation.PitchDegree;  |
| 335 | Data[ int(tMetaTag::Pitch) ].Set(float(pitch));  |
| 336 | NumTagsValid++;  |
| 337 |   |
| 338 | double yaw = exifInfo.GeoLocation.YawDegree;  |
| 339 | Data[ int(tMetaTag::Yaw) ].Set(float(yaw));  |
| 340 | NumTagsValid++;  |
| 341 | }  |
| 342 |   |
| 343 | if (exifInfo.GeoLocation.hasSpeed())  |
| 344 | {  |
| 345 | tVector3 vel  |
| 346 | (  |
| 347 | float(exifInfo.GeoLocation.SpeedX),  |
| 348 | float(exifInfo.GeoLocation.SpeedY),  |
| 349 | float(exifInfo.GeoLocation.SpeedZ)  |
| 350 | );  |
| 351 | Data[ int(tMetaTag::VelX) ].Set(vel.x);  |
| 352 | Data[ int(tMetaTag::VelY) ].Set(vel.y);  |
| 353 | Data[ int(tMetaTag::VelZ) ].Set(vel.z);  |
| 354 | NumTagsValid += 3;  |
| 355 |   |
| 356 | Data[ int(tMetaTag::Speed) ].Set( vel.Length() );  |
| 357 | NumTagsValid++;  |
| 358 | }  |
| 359 |   |
| 360 | std::string survey = exifInfo.GeoLocation.GPSMapDatum;  |
| 361 | if (!survey.empty())  |
| 362 | {  |
| 363 | Data[ int(tMetaTag::GPSSurvey) ].Set(survey.c_str());  |
| 364 | NumTagsValid++;  |
| 365 | }  |
| 366 |   |
| 367 | // tString can handle nullptr.  |
| 368 | tString utcDate(exifInfo.GeoLocation.GPSDateStamp.c_str());  |
| 369 | tString utcTime(exifInfo.GeoLocation.GPSTimeStamp.c_str());  |
| 370 | if (utcDate.IsValid())  |
| 371 | utcDate.Replace(search: ':', replace: '-');  |
| 372 | if (utcTime.IsValid())  |
| 373 | {  |
| 374 | utcTime.ExtractRight(divider: '.');  |
| 375 | utcTime.Replace(search: ' ', replace: ':');  |
| 376 | }  |
| 377 | tString dateTime;  |
| 378 | if (utcDate.IsValid() && utcTime.IsValid())  |
| 379 | dateTime = utcDate + " " + utcTime;  |
| 380 | else if (utcDate.IsValid())  |
| 381 | dateTime = utcDate;  |
| 382 | else if (utcTime.IsValid())  |
| 383 | dateTime = utcTime;  |
| 384 | if (dateTime.IsValid())  |
| 385 | {  |
| 386 | Data[ int(tMetaTag::GPSTimeStamp) ].Set(dateTime);  |
| 387 | NumTagsValid++;  |
| 388 | }  |
| 389 | }  |
| 390 |   |
| 391 |   |
| 392 | void tMetaData::SetTags_CamSettings(const TinyEXIF::EXIFInfo& exifInfo)  |
| 393 | {  |
| 394 | // These are annoying because they are not independent.  |
| 395 | double shutterSpeed = exifInfo.ShutterSpeedValue;  |
| 396 | double exposureTime = exifInfo.ExposureTime;  |
| 397 |   |
| 398 | // Compute one from the other if necessary.  |
| 399 | if ((shutterSpeed <= 0.0) && (exposureTime > 0.0))  |
| 400 | shutterSpeed = 1.0 / exposureTime;  |
| 401 | else if ((exposureTime <= 0.0) && (shutterSpeed > 0.0))  |
| 402 | exposureTime = 1.0 / shutterSpeed;  |
| 403 |   |
| 404 | if (shutterSpeed > 0.0)  |
| 405 | {  |
| 406 | Data[ int(tMetaTag::ShutterSpeed) ].Set(float(shutterSpeed));  |
| 407 | NumTagsValid++;  |
| 408 | }  |
| 409 |   |
| 410 | if (exposureTime > 0.0)  |
| 411 | {  |
| 412 | Data[ int(tMetaTag::ExposureTime) ].Set(float(exposureTime));  |
| 413 | NumTagsValid++;  |
| 414 | }  |
| 415 |   |
| 416 | double exposureBias = exifInfo.ExposureBiasValue;  |
| 417 | if (exposureBias > 0.0)  |
| 418 | {  |
| 419 | Data[ int(tMetaTag::ExposureBias) ].Set(float(exposureBias));  |
| 420 | NumTagsValid++;  |
| 421 | }  |
| 422 |   |
| 423 | double fstop = exifInfo.FNumber;  |
| 424 | if (fstop > 0.0)  |
| 425 | {  |
| 426 | Data[ int(tMetaTag::FStop) ].Set(float(fstop));  |
| 427 | NumTagsValid++;  |
| 428 | }  |
| 429 |   |
| 430 | // Only set exposure program if it's defined.  |
| 431 | uint32 prog = exifInfo.ExposureProgram;  |
| 432 | if (prog)  |
| 433 | {  |
| 434 | Data[ int(tMetaTag::ExposureProgram) ].Set(prog);  |
| 435 | NumTagsValid++;  |
| 436 | }  |
| 437 |   |
| 438 | uint32 iso = exifInfo.ISOSpeedRatings;  |
| 439 | if (iso > 0)  |
| 440 | {  |
| 441 | Data[ int(tMetaTag::ISO) ].Set(iso);  |
| 442 | NumTagsValid++;  |
| 443 | }  |
| 444 |   |
| 445 | double aperture = exifInfo.ApertureValue;  |
| 446 | if (aperture > 0.0)  |
| 447 | {  |
| 448 | Data[ int(tMetaTag::Aperture) ].Set(float(aperture));  |
| 449 | NumTagsValid++;  |
| 450 | }  |
| 451 |   |
| 452 | double brightness = exifInfo.BrightnessValue;  |
| 453 | if (brightness)  |
| 454 | {  |
| 455 | Data[ int(tMetaTag::Brightness) ].Set(float(brightness));  |
| 456 | NumTagsValid++;  |
| 457 | }  |
| 458 |   |
| 459 | // Only set metering mode if it's known.  |
| 460 | uint32 meterMode = exifInfo.MeteringMode;  |
| 461 | if (meterMode)  |
| 462 | {  |
| 463 | Data[ int(tMetaTag::MeteringMode) ].Set(meterMode);  |
| 464 | NumTagsValid++;  |
| 465 | }  |
| 466 |   |
| 467 | uint32 flash = exifInfo.Flash;  |
| 468 |   |
| 469 | // Flash bit 5. This bit is true if flash NOT present.  |
| 470 | uint32 flashHardware = ((flash & 0x00000020) >> 5) ? 0 : 1;  |
| 471 | Data[ int(tMetaTag::FlashHardware) ].Set(flashHardware);  |
| 472 | NumTagsValid++;  |
| 473 |   |
| 474 | if (flashHardware)  |
| 475 | {  |
| 476 | // Flash bit 0.  |
| 477 | uint32 flashUsed = (flash & 0x00000001);  |
| 478 | if (flashUsed)  |
| 479 | {  |
| 480 | Data[ int(tMetaTag::FlashUsed) ].Set(flashUsed);  |
| 481 | NumTagsValid++;  |
| 482 | }  |
| 483 |   |
| 484 | // Flash bits 1 and 2. Only set if dectector present.  |
| 485 | uint32 flashStrobe = (flash & 0x00000006) >> 1;  |
| 486 | if (flashStrobe)  |
| 487 | {  |
| 488 | Data[ int(tMetaTag::FlashStrobe) ].Set(flashStrobe);  |
| 489 | NumTagsValid++;  |
| 490 | }  |
| 491 |   |
| 492 | // Flash bits 3 and 4. Only set if mode not unknown.  |
| 493 | uint32 flashMode = (flash & 0x00000018) >> 3;  |
| 494 | if (flashMode)  |
| 495 | {  |
| 496 | Data[ int(tMetaTag::FlashMode) ].Set(flashMode);  |
| 497 | NumTagsValid++;  |
| 498 | }  |
| 499 |   |
| 500 | // Flash bit 6.  |
| 501 | uint32 flashRedEye = (flash & 0x00000040) >> 6;  |
| 502 | if (flashRedEye)  |
| 503 | {  |
| 504 | Data[ int(tMetaTag::FlashRedEye) ].Set(flashRedEye);  |
| 505 | NumTagsValid++;  |
| 506 | }  |
| 507 | }  |
| 508 |   |
| 509 | double focalLength = exifInfo.FocalLength;  |
| 510 | if (focalLength > 0.0)  |
| 511 | {  |
| 512 | Data[ int(tMetaTag::FocalLength) ].Set(float(focalLength));  |
| 513 | NumTagsValid++;  |
| 514 | }  |
| 515 |   |
| 516 | // Only set orientation if it's specified.  |
| 517 | uint32 orientation = exifInfo.Orientation;  |
| 518 | if (orientation)  |
| 519 | {  |
| 520 | Data[ int(tMetaTag::Orientation) ].Set(orientation);  |
| 521 | NumTagsValid++;  |
| 522 | }  |
| 523 |   |
| 524 | // Only set length unit if it's specified.  |
| 525 | uint32 lengthUnit = exifInfo.ResolutionUnit;  |
| 526 | if (lengthUnit)  |
| 527 | {  |
| 528 | Data[ int(tMetaTag::LengthUnit) ].Set(lengthUnit);  |
| 529 | NumTagsValid++;  |
| 530 | }  |
| 531 |   |
| 532 | double pixelsPerUnitX = exifInfo.XResolution;  |
| 533 | if (pixelsPerUnitX > 0.0)  |
| 534 | {  |
| 535 | Data[ int(tMetaTag::XPixelsPerUnit) ].Set(float(pixelsPerUnitX));  |
| 536 | NumTagsValid++;  |
| 537 | }  |
| 538 |   |
| 539 | double pixelsPerUnitY = exifInfo.YResolution;  |
| 540 | if (pixelsPerUnitY > 0.0)  |
| 541 | {  |
| 542 | Data[ int(tMetaTag::YPixelsPerUnit) ].Set(float(pixelsPerUnitY));  |
| 543 | NumTagsValid++;  |
| 544 | }  |
| 545 |   |
| 546 | uint32 bitsPerComponent = exifInfo.BitsPerSample;  |
| 547 | if (bitsPerComponent)  |
| 548 | {  |
| 549 | Data[ int(tMetaTag::BitsPerSample) ].Set(bitsPerComponent);  |
| 550 | NumTagsValid++;  |
| 551 | }  |
| 552 |   |
| 553 | uint32 imageWidth = exifInfo.ImageWidth;  |
| 554 | if (imageWidth > 0)  |
| 555 | {  |
| 556 | Data[ int(tMetaTag::ImageWidth) ].Set(imageWidth);  |
| 557 | NumTagsValid++;  |
| 558 | }  |
| 559 |   |
| 560 | uint32 imageHeight = exifInfo.ImageHeight;  |
| 561 | if (imageHeight > 0)  |
| 562 | {  |
| 563 | Data[ int(tMetaTag::ImageHeight) ].Set(imageHeight);  |
| 564 | NumTagsValid++;  |
| 565 | }  |
| 566 |   |
| 567 | uint32 imageWidthOrig = exifInfo.RelatedImageWidth;  |
| 568 | if (imageWidthOrig > 0)  |
| 569 | {  |
| 570 | Data[ int(tMetaTag::ImageWidthOrig) ].Set(imageWidthOrig);  |
| 571 | NumTagsValid++;  |
| 572 | }  |
| 573 |   |
| 574 | uint32 imageHeightOrig = exifInfo.RelatedImageHeight;  |
| 575 | if (imageHeightOrig > 0)  |
| 576 | {  |
| 577 | Data[ int(tMetaTag::ImageHeightOrig) ].Set(imageHeightOrig);  |
| 578 | NumTagsValid++;  |
| 579 | }  |
| 580 |   |
| 581 | tString dateTimeChange(exifInfo.DateTime.c_str());  |
| 582 | if (dateTimeChange.IsValid())  |
| 583 | {  |
| 584 | tString yyyymmdd = dateTimeChange.ExtractLeft(divider: ' ');  |
| 585 | yyyymmdd.Replace(search: ':', replace: '-');  |
| 586 | dateTimeChange = yyyymmdd + " " + dateTimeChange;  |
| 587 |   |
| 588 | Data[ int(tMetaTag::DateTimeChange) ].Set(dateTimeChange);  |
| 589 | NumTagsValid++;  |
| 590 | }  |
| 591 |   |
| 592 | tString dateTimeOrig(exifInfo.DateTimeOriginal.c_str());  |
| 593 | if (dateTimeOrig.IsValid())  |
| 594 | {  |
| 595 | tString yyyymmdd = dateTimeOrig.ExtractLeft(divider: ' ');  |
| 596 | yyyymmdd.Replace(search: ':', replace: '-');  |
| 597 | dateTimeOrig = yyyymmdd + " " + dateTimeOrig;  |
| 598 |   |
| 599 | Data[ int(tMetaTag::DateTimeOrig) ].Set(dateTimeOrig);  |
| 600 | NumTagsValid++;  |
| 601 | }  |
| 602 |   |
| 603 | tString dateTimeDig(exifInfo.DateTimeDigitized.c_str());  |
| 604 | if (dateTimeDig.IsValid())  |
| 605 | {  |
| 606 | tString yyyymmdd = dateTimeDig.ExtractLeft(divider: ' ');  |
| 607 | yyyymmdd.Replace(search: ':', replace: '-');  |
| 608 | dateTimeDig = yyyymmdd + " " + dateTimeDig;  |
| 609 |   |
| 610 | Data[ int(tMetaTag::DateTimeDigit) ].Set(dateTimeDig);  |
| 611 | NumTagsValid++;  |
| 612 | }  |
| 613 | }  |
| 614 |   |
| 615 |   |
| 616 | void tMetaData::SetTags_AuthorNotes(const TinyEXIF::EXIFInfo& exifInfo)  |
| 617 | {  |
| 618 | // Software  |
| 619 | tString software = exifInfo.Software.c_str();  |
| 620 | if (software.IsValid())  |
| 621 | {  |
| 622 | Data[ int(tMetaTag::Software) ].Set(software);  |
| 623 | NumTagsValid++;  |
| 624 | }  |
| 625 |   |
| 626 | // Description  |
| 627 | tString description = exifInfo.ImageDescription.c_str();  |
| 628 | if (description.IsValid())  |
| 629 | {  |
| 630 | Data[ int(tMetaTag::Description) ].Set(description);  |
| 631 | NumTagsValid++;  |
| 632 | }  |
| 633 |   |
| 634 | // Copyright  |
| 635 | tString copyright = exifInfo.Copyright.c_str();  |
| 636 | if (copyright.IsValid())  |
| 637 | {  |
| 638 | Data[ int(tMetaTag::Copyright) ].Set(copyright);  |
| 639 | NumTagsValid++;  |
| 640 | }  |
| 641 | }  |
| 642 |   |
| 643 |   |
| 644 | tString tMetaData::GetPrettyValue(tMetaTag tag) const  |
| 645 | {  |
| 646 | tString value;  |
| 647 | if (!IsValid())  |
| 648 | return value;  |
| 649 |   |
| 650 | const tMetaDatum& datum = Data[int(tag)];  |
| 651 | if (!datum.IsSet())  |
| 652 | return value;  |
| 653 |   |
| 654 | switch (tag)  |
| 655 | {  |
| 656 | case tMetaTag::Make:  |
| 657 | case tMetaTag::Model:  |
| 658 | case tMetaTag::SerialNumber:  |
| 659 | case tMetaTag::MakeModelSerial:  |
| 660 | value = datum.String;  |
| 661 | break;  |
| 662 |   |
| 663 | case tMetaTag::LatitudeDD:  |
| 664 | tsPrintf(dest&: value, format: "%f°" , datum.Float);  |
| 665 | break;  |
| 666 |   |
| 667 | case tMetaTag::LatitudeDMS:  |
| 668 | value = datum.String;  |
| 669 | break;  |
| 670 |   |
| 671 | case tMetaTag::LongitudeDD:  |
| 672 | tsPrintf(dest&: value, format: "%f°" , datum.Float);  |
| 673 | break;  |
| 674 |   |
| 675 | case tMetaTag::LongitudeDMS:  |
| 676 | value = datum.String;  |
| 677 | break;  |
| 678 |   |
| 679 | case tMetaTag::Altitude:  |
| 680 | tsPrintf(dest&: value, format: "%f m" , datum.Float);  |
| 681 | break;  |
| 682 |   |
| 683 | case tMetaTag::AltitudeRelRef:  |
| 684 | value = datum.String;  |
| 685 | break;  |
| 686 |   |
| 687 | case tMetaTag::AltitudeRel:  |
| 688 | {  |
| 689 | tsPrintf(dest&: value, format: "%f m" , datum.Float);  |
| 690 | const tMetaDatum& refDatum = Data[int(tMetaTag::AltitudeRelRef)];  |
| 691 | if (refDatum.IsSet())  |
| 692 | value = value + " " + refDatum.String.Lower();  |
| 693 | break;  |
| 694 | }  |
| 695 |   |
| 696 | case tMetaTag::Roll:  |
| 697 | case tMetaTag::Pitch:  |
| 698 | case tMetaTag::Yaw:  |
| 699 | tsPrintf(dest&: value, format: "%f°" , datum.Float);  |
| 700 | break;  |
| 701 |   |
| 702 | case tMetaTag::VelX:  |
| 703 | case tMetaTag::VelY:  |
| 704 | case tMetaTag::VelZ:  |
| 705 | case tMetaTag::Speed:  |
| 706 | tsPrintf(dest&: value, format: "%f m/s" , datum.Float);  |
| 707 | break;  |
| 708 |   |
| 709 | case tMetaTag::GPSSurvey:  |
| 710 | case tMetaTag::GPSTimeStamp:  |
| 711 | value = datum.String;  |
| 712 | break;  |
| 713 |   |
| 714 | case tMetaTag::ShutterSpeed:  |
| 715 | tsPrintf(dest&: value, format: "%f 1/s" , datum.Float);  |
| 716 | break;  |
| 717 |   |
| 718 | case tMetaTag::ExposureTime:  |
| 719 | tsPrintf(dest&: value, format: "%f s" , datum.Float);  |
| 720 | break;  |
| 721 |   |
| 722 | case tMetaTag::ExposureBias:  |
| 723 | tsPrintf(dest&: value, format: "%f APEX" , datum.Float);  |
| 724 | break;  |
| 725 |   |
| 726 | case tMetaTag::FStop:  |
| 727 | tsPrintf(dest&: value, format: "%.1f" , datum.Float);  |
| 728 | break;  |
| 729 |   |
| 730 | case tMetaTag::ExposureProgram:  |
| 731 | value = "Not Defined" ;  |
| 732 | switch (datum.Uint32)  |
| 733 | {  |
| 734 | case 1: value = "Manual" ; break;  |
| 735 | case 2: value = "Normal Program" ; break;  |
| 736 | case 3: value = "Aperture Priority" ; break;  |
| 737 | case 4: value = "Shutter Priority" ; break;  |
| 738 | case 5: value = "Creative Program" ; break;  |
| 739 | case 6: value = "Action Program" ; break;  |
| 740 | case 7: value = "Portrait Mode" ; break;  |
| 741 | case 8: value = "Landscape Mode" ; break;  |
| 742 | }  |
| 743 | break;  |
| 744 |   |
| 745 | case tMetaTag::ISO:  |
| 746 | tsPrintf(dest&: value, format: "%u" , datum.Uint32);  |
| 747 | break;  |
| 748 |   |
| 749 | case tMetaTag::Aperture:  |
| 750 | case tMetaTag::Brightness:  |
| 751 | tsPrintf(dest&: value, format: "%f APEX" , datum.Float);  |
| 752 | break;  |
| 753 |   |
| 754 | case tMetaTag::MeteringMode:  |
| 755 | value = "Unknown" ;  |
| 756 | switch (datum.Uint32)  |
| 757 | {  |
| 758 | case 1: value = "Average" ; break;  |
| 759 | case 2: value = "Center Weighted Average" ; break;  |
| 760 | case 3: value = "Spot" ; break;  |
| 761 | case 4: value = "Multi-spot" ; break;  |
| 762 | case 5: value = "Pattern" ; break;  |
| 763 | case 6: value = "Partial" ; break;  |
| 764 | }  |
| 765 | break;  |
| 766 |   |
| 767 | case tMetaTag::FlashHardware:  |
| 768 | value = datum.Uint32 ? "Hardware Present" : "Hardware Not Present" ;  |
| 769 | break;  |
| 770 |   |
| 771 | case tMetaTag::FlashUsed:  |
| 772 | value = datum.Uint32 ? "Yes" : "No" ;  |
| 773 | break;  |
| 774 |   |
| 775 | case tMetaTag::FlashStrobe:  |
| 776 | value = "No Detector" ;  |
| 777 | switch (datum.Uint32)  |
| 778 | {  |
| 779 | case 1: value = "Reserved" ; break;  |
| 780 | case 2: value = "Not Detected" ; break;  |
| 781 | case 3: value = "Detected" ; break;  |
| 782 | }  |
| 783 | break;  |
| 784 |   |
| 785 | case tMetaTag::FlashMode:  |
| 786 | value = "Unknown" ;  |
| 787 | switch (datum.Uint32)  |
| 788 | {  |
| 789 | case 1: value = "Compulsory Firing" ; break;  |
| 790 | case 2: value = "Compulsory Suppression" ; break;  |
| 791 | case 3: value = "Auto" ; break;  |
| 792 | }  |
| 793 | break;  |
| 794 |   |
| 795 | case tMetaTag::FlashRedEye:  |
| 796 | value = datum.Uint32 ? "Reduction" : "No Reduction" ;  |
| 797 | break;  |
| 798 |   |
| 799 | case tMetaTag::FocalLength:  |
| 800 | tsPrintf(dest&: value, format: "%d mm" , tClampMin(val: int(datum.Float), min: 1));  |
| 801 | break;  |
| 802 |   |
| 803 | case tMetaTag::Orientation:  |
| 804 | {  |
| 805 | value = "Unspecified" ;  |
| 806 | switch (datum.Uint32)  |
| 807 | {  |
| 808 | case 1: value = "Normal" ; break;  |
| 809 | case 2: value = "Flip-Y" ; break;  |
| 810 | case 3: value = "Flip-XY" ; break;  |
| 811 | case 4: value = "Flip-X" ; break;  |
| 812 | case 5: value = "Rot-CW90 Flip-Y" ; break;  |
| 813 | case 6: value = "Rot-ACW90" ; break;  |
| 814 | case 7: value = "Rot-ACW90 Flip-Y" ; break;  |
| 815 | case 8: value = "Rot-CW90" ; break;  |
| 816 | }  |
| 817 | break;  |
| 818 | }  |
| 819 |   |
| 820 | case tMetaTag::LengthUnit:  |
| 821 | {  |
| 822 | value = "units" ;  |
| 823 | switch (datum.Uint32)  |
| 824 | {  |
| 825 | case 2: value = "inch" ; break;  |
| 826 | case 3: value = "cm" ; break;  |
| 827 | }  |
| 828 | break;  |
| 829 | }  |
| 830 |   |
| 831 | case tMetaTag::XPixelsPerUnit:  |
| 832 | case tMetaTag::YPixelsPerUnit:  |
| 833 | {  |
| 834 | tString unit = GetPrettyValue(tag: tMetaTag::LengthUnit);  |
| 835 | if (unit.IsValid())  |
| 836 | tsPrintf(dest&: value, format: "%d pixels/%s" , int(datum.Float), unit.Chr());  |
| 837 | else  |
| 838 | tsPrintf(dest&: value, format: "%d pixels" , int(datum.Float));  |
| 839 | break;  |
| 840 | }  |
| 841 |   |
| 842 | case tMetaTag::BitsPerSample:  |
| 843 | tsPrintf(dest&: value, format: "%d bits/component" , datum.Uint32);  |
| 844 | break;  |
| 845 |   |
| 846 | case tMetaTag::ImageWidth:  |
| 847 | case tMetaTag::ImageHeight:  |
| 848 | case tMetaTag::ImageWidthOrig:  |
| 849 | case tMetaTag::ImageHeightOrig:  |
| 850 | tsPrintf(dest&: value, format: "%d pixels" , datum.Uint32);  |
| 851 | break;  |
| 852 |   |
| 853 | case tMetaTag::DateTimeChange:  |
| 854 | case tMetaTag::DateTimeOrig:  |
| 855 | case tMetaTag::DateTimeDigit:  |
| 856 | case tMetaTag::Software:  |
| 857 | case tMetaTag::Description:  |
| 858 | case tMetaTag::Copyright:  |
| 859 | value = datum.String;  |
| 860 | break;  |
| 861 | }  |
| 862 | return value;  |
| 863 | }  |
| 864 |   |
| 865 |   |
| 866 | void tMetaData::Save(tChunkWriter& chunk) const  |
| 867 | {  |
| 868 | chunk.Begin(chunkID: tChunkID::Image_MetaData);  |
| 869 | {  |
| 870 | chunk.Begin(chunkID: tChunkID::Image_MetaDataVersion);  |
| 871 | {  |
| 872 | chunk.Write(obj: ChunkVersion);  |
| 873 | }  |
| 874 | chunk.End();  |
| 875 |   |
| 876 | for (int d = 0; d < int(tMetaTag::NumTags); d++)  |
| 877 | {  |
| 878 | if (!Data[d].IsValid())  |
| 879 | continue;  |
| 880 |   |
| 881 | const tMetaDatum& datum = Data[d];  |
| 882 | chunk.Begin(chunkID: tChunkID::Image_MetaDatum);  |
| 883 | {  |
| 884 | chunk.Write(obj: d);  |
| 885 | chunk.Write(obj: datum.Type);  |
| 886 | switch (datum.Type)  |
| 887 | {  |
| 888 | case tMetaDatum::DatumType::Uint32:  |
| 889 | chunk.Write(obj: datum.Uint32);  |
| 890 | break;  |
| 891 |   |
| 892 | case tMetaDatum::DatumType::Float:  |
| 893 | chunk.Write(obj: datum.Float);  |
| 894 | break;  |
| 895 |   |
| 896 | case tMetaDatum::DatumType::String:  |
| 897 | chunk.Write(s: datum.String);  |
| 898 | break;  |
| 899 | }  |
| 900 | }  |
| 901 | chunk.End();  |
| 902 | }  |
| 903 | }  |
| 904 | chunk.End();  |
| 905 | }  |
| 906 |   |
| 907 |   |
| 908 | void tMetaData::Load(const tChunk& chunk)  |
| 909 | {  |
| 910 | Clear();  |
| 911 | if (chunk.ID() != tChunkID::Image_MetaData)  |
| 912 | return;  |
| 913 |   |
| 914 | for (tChunk ch = chunk.First(); ch.IsValid(); ch = ch.Next())  |
| 915 | {  |
| 916 | switch (ch.ID())  |
| 917 | {  |
| 918 | case tChunkID::Image_MetaDataVersion:  |
| 919 | {  |
| 920 | int version;  |
| 921 | ch.GetItem(item&: version);  |
| 922 | break;  |
| 923 | }  |
| 924 |   |
| 925 | case tChunkID::Image_MetaDatum:  |
| 926 | {  |
| 927 | int id = 0;  |
| 928 | ch.GetItem(item&: id);  |
| 929 | tMetaDatum& datum = Data[id];  |
| 930 |   |
| 931 | ch.GetItem(item&: *((int*)&datum.Type));  |
| 932 | switch (datum.Type)  |
| 933 | {  |
| 934 | case tMetaDatum::DatumType::Uint32:  |
| 935 | ch.GetItem(item&: datum.Uint32);  |
| 936 | break;  |
| 937 |   |
| 938 | case tMetaDatum::DatumType::Float:  |
| 939 | ch.GetItem(item&: datum.Float);  |
| 940 | break;  |
| 941 |   |
| 942 | case tMetaDatum::DatumType::String:  |
| 943 | ch.GetItem(item&: datum.String);  |
| 944 | break;  |
| 945 | }  |
| 946 | NumTagsValid++;  |
| 947 | break;  |
| 948 | }  |
| 949 | }  |
| 950 | }  |
| 951 | }  |
| 952 | |