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" 
21using namespace tImage
22using namespace tMath
23 
24 
25const 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}; 
85tStaticAssert(tNumElements(tMetaTagNames) == int(tMetaTag::NumTags)); 
86 
87 
88const 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}; 
192tStaticAssert(tNumElements(tMetaTagDescs) == int(tMetaTag::NumTags)); 
193 
194 
195const char* tImage::tGetMetaTagName(tMetaTag tag
196
197 return tMetaTagNames[int(tag)]; 
198
199 
200 
201const char* tImage::tGetMetaTagDesc(tMetaTag tag
202
203 return tMetaTagDescs[int(tag)]; 
204
205 
206 
207bool 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 
224void 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 
267void 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 
392void 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 
616void 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 
644tString 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 
866void 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 
908void 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