From c8599c0631c6089248e87cb973bc9fa9bcf91faf Mon Sep 17 00:00:00 2001 From: Georg Hagen Date: Thu, 13 Feb 2025 23:25:42 +0100 Subject: [PATCH] Multiple fixes and extensions for exif builder --- openVulkanoCpp/Image/ExifBuilder.cpp | 147 ++++++++++++++++----------- openVulkanoCpp/Image/ExifBuilder.hpp | 46 ++++++--- 2 files changed, 117 insertions(+), 76 deletions(-) diff --git a/openVulkanoCpp/Image/ExifBuilder.cpp b/openVulkanoCpp/Image/ExifBuilder.cpp index 1ec4361..fab2aa3 100644 --- a/openVulkanoCpp/Image/ExifBuilder.cpp +++ b/openVulkanoCpp/Image/ExifBuilder.cpp @@ -5,6 +5,8 @@ */ #include "ExifBuilder.hpp" +#include "Extensions/FmtFormatter.hpp" +#include "Math/Math.hpp" #include #include #include @@ -35,6 +37,8 @@ namespace SOFTWARE_USED = 0x0131, DATE_TAKEN = 0x0132, EXPOSURE_TIME = 0x829a, + F_NUMBER = 0x829d, + FOCAL_LENGTH = 0x920a, GPS_INFO_OFFSET = 0x8825, }; @@ -292,7 +296,16 @@ namespace OpenVulkano::Image { } - void ExifBuilder::SetAltitude(float level) + void ExifBuilder::SetOrientation(float orientationRad) + { + orientationRad = Math::Utils::NormalizeAngleRad(orientationRad); + if (orientationRad > 0.78f && orientationRad < 2.35f) orientation = 8; + else if (orientationRad < 3.93f && orientationRad >= 2.35f) orientation = 3; + else if (orientationRad < 5.5f) orientation = 6; + else orientation = 1; + } + + void GPSInfo::SetAltitude(const float level) { altitudeIsAboveSeaLevel = level >= 0; altitude = std::abs(level); @@ -316,16 +329,19 @@ namespace OpenVulkano::Image AppendVector(result, EXIF_HEADER_AND_PADDING); AppendVector(result, TIFF_HEADER); - int numberOfMainTags = 1; // 1 is for GPS Info tag + int numberOfMainTags = 0; // 1 is for GPS Info tag numberOfMainTags += orientation != 0; numberOfMainTags += make != ""; numberOfMainTags += model != ""; - numberOfMainTags += xResolution.nominator || xResolution.denominator; - numberOfMainTags += yResolution.nominator || yResolution.denominator; + numberOfMainTags += (bool)xResolution; + numberOfMainTags += (bool)yResolution; numberOfMainTags += resolutionUnit != 0; - numberOfMainTags += exposureTime.nominator || exposureTime.denominator; + numberOfMainTags += (bool)exposureTime; numberOfMainTags += softwareUsed != ""; numberOfMainTags += dateTaken != ""; + numberOfMainTags += gpsInfo.has_value(); + numberOfMainTags += (bool)fNumber; + numberOfMainTags += (bool)focalLength; AppendU32(result, 8); // Append offset to the ifd AppendU16(result, numberOfMainTags); @@ -343,25 +359,35 @@ namespace OpenVulkano::Image offsets.push_back(AppendTagValueTypeAndString(result, IFDTag::MODEL, data, model)); } - if (orientation != 0) + if (orientation) { AppendTagValueTypeAndShort(result, IFDTag::ORIENTATION, orientation); } - if (xResolution.nominator || xResolution.denominator) + if (xResolution) { offsets.push_back(AppendTagValueTypeAndRational(result, IFDTag::X_RESOLUTION, data, xResolution)); } - if (yResolution.nominator || yResolution.denominator) + if (yResolution) { offsets.push_back(AppendTagValueTypeAndRational(result, IFDTag::Y_RESOLUTION, data, yResolution)); } - - if (exposureTime.nominator || exposureTime.denominator) + + if (exposureTime) { offsets.push_back(AppendTagValueTypeAndRational(result, IFDTag::EXPOSURE_TIME, data, exposureTime)); } + + if (fNumber) + { + offsets.push_back(AppendTagValueTypeAndRational(result, IFDTag::F_NUMBER, data, fNumber)); + } + + if (focalLength) + { + offsets.push_back(AppendTagValueTypeAndRational(result, IFDTag::FOCAL_LENGTH, data, focalLength)); + } if (resolutionUnit != 0) { @@ -377,11 +403,14 @@ namespace OpenVulkano::Image // Even though other software does provide this information without a problem offsets.push_back(AppendTagValueTypeAndString(result, IFDTag::DATE_TAKEN, data, dateTaken)); - // GPS Info offset - AppendTagAndValueType(result, (uint16_t) IFDTag::GPS_INFO_OFFSET, (uint16_t) IFDValueType::LONG_); - AppendU32(result, 1); // num components - gpsInfoOffset = AppendU32(result, 0); - + if (gpsInfo) + { + // GPS Info offset + AppendTagAndValueType(result, (uint16_t) IFDTag::GPS_INFO_OFFSET, (uint16_t) IFDValueType::LONG_); + AppendU32(result, 1); // num components + gpsInfoOffset = AppendU32(result, 0); + } + // next ifd offset AppendU32(result, 0); @@ -397,76 +426,70 @@ namespace OpenVulkano::Image } } - // Resolve GPS info offset + if (gpsInfo) { - int ifdAndSubdataSize = result.size(); - uint32_t *ptr = (uint32_t *) (result.data() + gpsInfoOffset); - *ptr = EndianSwap((uint32_t)(ifdAndSubdataSize - EXIF_HEADER_SIZE)); - } + // Resolve GPS info offset + { + int ifdAndSubdataSize = result.size(); + uint32_t *ptr = (uint32_t *) (result.data() + gpsInfoOffset); + *ptr = EndianSwap((uint32_t)(ifdAndSubdataSize - EXIF_HEADER_SIZE)); + } - // Writing GPS Info structure - int numberOfGPSInfoTags = 8; - AppendU16(result, numberOfGPSInfoTags); - offsets.resize(0); + // Writing GPS Info structure + constexpr int numberOfGPSInfoTags = 8; + AppendU16(result, numberOfGPSInfoTags); + offsets.resize(0); - // Latitude Ref - char latitudeRef = CoordRefToChar(latitude.ref); - AppendTagValueTypeAndByte(result, IFDGPSTag::LATITUDE_REF, IFDValueType::ASCII, latitudeRef); + // Latitude Ref + char latitudeRef = CoordRefToChar(gpsInfo->latitude.ref); + AppendTagValueTypeAndByte(result, IFDGPSTag::LATITUDE_REF, IFDValueType::ASCII, latitudeRef); - // Latitude - offsets.push_back(AppendTagValueTypeAndGPSRational(result, IFDGPSTag::LATITUDE, 3, 0)); // 0 * sizeof(RationalValue) + // Latitude + offsets.push_back(AppendTagValueTypeAndGPSRational(result, IFDGPSTag::LATITUDE, 3, 0)); // 0 * sizeof(RationalValue) - // Longitude Ref - char longitudeRef = CoordRefToChar(longitude.ref); - AppendTagValueTypeAndByte(result, IFDGPSTag::LONGITUDE_REF, IFDValueType::ASCII, longitudeRef); + // Longitude Ref + char longitudeRef = CoordRefToChar(gpsInfo->longitude.ref); + AppendTagValueTypeAndByte(result, IFDGPSTag::LONGITUDE_REF, IFDValueType::ASCII, longitudeRef); - // Longitude - offsets.push_back(AppendTagValueTypeAndGPSRational(result, IFDGPSTag::LONGITUDE, 3, 24)); // 3 * sizeof(RationalValue) + // Longitude + offsets.push_back(AppendTagValueTypeAndGPSRational(result, IFDGPSTag::LONGITUDE, 3, 24)); // 3 * sizeof(RationalValue) - // Altitude Ref - AppendTagValueTypeAndByte(result, IFDGPSTag::ALTITUDE_REF, IFDValueType::BYTE, altitudeIsAboveSeaLevel ? 0 : 1); + // Altitude Ref + AppendTagValueTypeAndByte(result, IFDGPSTag::ALTITUDE_REF, IFDValueType::BYTE, gpsInfo->altitudeIsAboveSeaLevel ? 0 : 1); - // Altitude - offsets.push_back(AppendTagValueTypeAndGPSRational(result, IFDGPSTag::ALTITUDE, 1, 48)); // 6 * sizeof(RationalValue) + // Altitude + offsets.push_back(AppendTagValueTypeAndGPSRational(result, IFDGPSTag::ALTITUDE, 1, 48)); // 6 * sizeof(RationalValue) - // Track Ref - AppendTagValueTypeAndByte(result, IFDGPSTag::TRACK_REF, IFDValueType::ASCII, (trackRef == GPSTrackRef::TRUE_NORTH) ? 'T' : 'M'); + // Track Ref + AppendTagValueTypeAndByte(result, IFDGPSTag::TRACK_REF, IFDValueType::ASCII, (gpsInfo->trackRef == GPSTrackRef::TRUE_NORTH) ? 'T' : 'M'); - // Track - offsets.push_back(AppendTagValueTypeAndGPSRational(result, IFDGPSTag::TRACK, 1, 56)); // 7 * sizeof(RationalValue) + // Track + offsets.push_back(AppendTagValueTypeAndGPSRational(result, IFDGPSTag::TRACK, 1, 56)); // 7 * sizeof(RationalValue) - // - - { int sizeOfResultSoFar = result.size(); const int valueToAdd = sizeOfResultSoFar - EXIF_HEADER_SIZE; for(const auto &offset : offsets) { AddValueToU32AndEndianSwap(result.data() + offset, valueToAdd); } + + AppendGPSCoords(result, gpsInfo->latitude); + AppendGPSCoords(result, gpsInfo->longitude); + + AppendU32(result, gpsInfo->altitude); + AppendU32(result, 1); // denominator for altitude + + constexpr int TRACK_PRECISION = 10000; + AppendU32(result, gpsInfo->track * TRACK_PRECISION); + AppendU32(result, TRACK_PRECISION); } - // - - AppendGPSCoords(result, latitude); - AppendGPSCoords(result, longitude); - - AppendU32(result, altitude); - AppendU32(result, 1); // denominator for altitude - - constexpr int TRACK_PRECISION = 10000; - AppendU32(result, track * TRACK_PRECISION); - AppendU32(result, TRACK_PRECISION); - return result; } std::string ExifBuilder::StringFromTime(std::time_t time) { - std::tm* timeInfo = std::localtime(&time); - std::ostringstream oss; - oss << std::put_time(timeInfo, "%Y:%m:%d %H:%M:%S"); - return oss.str(); + return fmt::format("{:%Y:%m:%d %H:%M:%S}", fmt::localtime(time)); } std::string ExifBuilder::GetCurrentTimestamp() @@ -475,4 +498,4 @@ namespace OpenVulkano::Image std::time_t currentTime = std::chrono::system_clock::to_time_t(now); return StringFromTime(currentTime); } -} \ No newline at end of file +} diff --git a/openVulkanoCpp/Image/ExifBuilder.hpp b/openVulkanoCpp/Image/ExifBuilder.hpp index 2f13771..951e7e8 100644 --- a/openVulkanoCpp/Image/ExifBuilder.hpp +++ b/openVulkanoCpp/Image/ExifBuilder.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include namespace OpenVulkano::Image @@ -17,6 +18,17 @@ namespace OpenVulkano::Image { uint32_t nominator; uint32_t denominator; + + RationalValue() : nominator(0), denominator(0) {} + explicit RationalValue(float val) : nominator(val * 10000), denominator(10000) {} + explicit RationalValue(double val) : nominator(val * 10000), denominator(10000) {} + explicit RationalValue(uint32_t nominator, uint32_t denominator = 1) : nominator(nominator), denominator(denominator) {} + + operator bool() const { return nominator || denominator; } + + RationalValue& operator =(float val) { nominator = val * 10000; denominator = 10000; return *this; } + RationalValue& operator =(uint32_t val) { nominator = val; denominator = 1; return *this; } + RationalValue& operator =(const RationalValue& val) = default; }; enum class LatitudeRef @@ -45,19 +57,8 @@ namespace OpenVulkano::Image GPSCoords(int32_t degrees, int32_t minutes, int32_t seconds, LongitudeRef ref); }; - class ExifBuilder + struct GPSInfo { - public: - int orientation = 0; - std::string make; - std::string model; - RationalValue xResolution = { 0, 0 }; - RationalValue yResolution = { 0, 0 }; - int resolutionUnit = 0; - RationalValue exposureTime = { 0, 0 }; - std::string dateTaken; // format: yyyy:mm:dd hh:mm:ss - std::string softwareUsed = "OpenVulkano"; - GPSCoords latitude = { 0, 0, 0, LatitudeRef::NORTH }; GPSCoords longitude = { 0, 0, 0, LongitudeRef::EAST }; @@ -67,12 +68,29 @@ namespace OpenVulkano::Image GPSTrackRef trackRef = GPSTrackRef::TRUE_NORTH; float track = 0; // range is [0.0; 360.0) - void SetAltitude(float level); + }; + + class ExifBuilder + { + public: + int orientation = 0; + std::string make; + std::string model; + RationalValue xResolution, yResolution; + int resolutionUnit = 0; + RationalValue exposureTime, fNumber, focalLength; + std::string dateTaken; // format: yyyy:mm:dd hh:mm:ss + std::string softwareUsed = "OpenVulkano"; + + std::optional gpsInfo; + + void SetResolution(uint32_t dpi = 72) { xResolution = yResolution = dpi; resolutionUnit = 2;} + void SetOrientation(float orientationRad); void SetTime(std::time_t timestamp); // Typical usage is -> jpeg_write_marker(cinfo, JPEG_APP0 + 1, exif_data.data(), exif_data.size()); [[nodiscard]] std::vector Build(); [[nodiscard]] static std::string StringFromTime(std::time_t time); [[nodiscard]] static std::string GetCurrentTimestamp(); }; -} \ No newline at end of file +}