From c28e1adc4dd78eb7b2e5239462d969b8272359ac Mon Sep 17 00:00:00 2001 From: Vladyslav Baranovskyi Date: Tue, 17 Sep 2024 14:41:31 +0300 Subject: [PATCH 1/9] Basic implementation (encoding course information is in TODO right now) --- openVulkanoCpp/Image/ExifBuilder.cpp | 402 +++++++++++++++++++++++++++ openVulkanoCpp/Image/ExifBuilder.hpp | 61 ++++ 2 files changed, 463 insertions(+) create mode 100644 openVulkanoCpp/Image/ExifBuilder.cpp create mode 100644 openVulkanoCpp/Image/ExifBuilder.hpp diff --git a/openVulkanoCpp/Image/ExifBuilder.cpp b/openVulkanoCpp/Image/ExifBuilder.cpp new file mode 100644 index 0000000..8a24616 --- /dev/null +++ b/openVulkanoCpp/Image/ExifBuilder.cpp @@ -0,0 +1,402 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#include "ExifBuilder.hpp" + +#define ARRAY_COUNT(Array) (sizeof(Array) / sizeof(*Array)) + +namespace +{ + char EXIF_HEADER_AND_PADDING[] = {'E', 'x', 'i', 'f', 0, 0}; + int const EXIF_HEADER_SIZE = ARRAY_COUNT(EXIF_HEADER_AND_PADDING); + + char TIFF_HEADER[] = {0x4d, 0x4d, 0, 0x2a}; + int const TIFF_HEADER_SIZE = ARRAY_COUNT(TIFF_HEADER); + + enum class IFDTag : uint16_t + { + END = 0, + MAKE = 0x010f, + MODEL = 0x0110, + ORIENTATION = 0x0112, + XRESOLUTION = 0x011a, + YRESOLUTION = 0x011b, + RESOLUTION_UNIT = 0x0128, + SOFTWARE_USED = 0x0131, + DATE_TAKEN = 0x0132, + EXPOSURE_TIME = 0x829a, + GPS_INFO_OFFSET = 0x8825, + }; + + enum class IFDValueType + { + BYTE = 1, + ASCII = 2, + SHORT = 3, + LONG_ = 4, + RATIONAL = 5, + }; + + uint32_t endianSwap(uint32_t Value) + { + uint32_t Result; + + char *Ptr = (char *)&Value; + char *Out = (char *)&Result; + Out[0] = Ptr[3]; + Out[1] = Ptr[2]; + Out[2] = Ptr[1]; + Out[3] = Ptr[0]; + + return Result; + } + + uint16_t endianSwap(uint16_t Value) + { + uint16_t Result; + + char *Ptr = (char *)&Value; + char *Out = (char *)&Result; + Out[0] = Ptr[1]; + Out[1] = Ptr[0]; + + return Result; + } + + int appendU8(std::vector& array, uint8_t value) + { + int result = array.size(); + array.push_back(value); + return result; + } + + int appendU16(std::vector& array, uint16_t value, bool endianSwap = false) + { + int result = array.size(); + + if(endianSwap) + value = ::endianSwap(value); + + char *src = (char *)&value; + array.push_back(src[0]); + array.push_back(src[1]); + + return result; + } + + int appendU32(std::vector& array, uint32_t value, bool endianSwap = false) + { + int result = array.size(); + + if(endianSwap) + value = ::endianSwap(value); + + char *src = (char *)&value; + array.push_back(src[0]); + array.push_back(src[1]); + array.push_back(src[2]); + array.push_back(src[3]); + + return result; + } + + int appendVector(std::vector& array, std::vector values) + { + int result = array.size(); + + for(auto value : values) + { + array.push_back(value); + } + + return result; + } + + int appendVector(std::vector& array, char* values, int count) + { + int result = array.size(); + + for(int i = 0; i < count; ++i) + { + array.push_back(values[i]); + } + + return result; + } + + int appendGPSCoords(std::vector& array, const OpenVulkano::Image::GPSCoords& coords, bool endianSwap = false) + { + int result = array.size(); + + appendU32(array, coords.degrees, endianSwap); + appendU32(array, 1, endianSwap); + + appendU32(array, coords.minutes, endianSwap); + appendU32(array, 1, endianSwap); + + appendU32(array, coords.seconds, endianSwap); + appendU32(array, 1, endianSwap); + + return result; + } +} + +namespace OpenVulkano::Image +{ + std::vector ExifBuilder::build() + { + std::vector result; + std::vector data; // the data that has ascii values + + appendVector(result, EXIF_HEADER_AND_PADDING, EXIF_HEADER_SIZE); + appendVector(result, TIFF_HEADER, TIFF_HEADER_SIZE); + + int numberOfMainTags = 1; // 1 is for GPS Info tag + numberOfMainTags += orientation != 0; + numberOfMainTags += make != ""; + numberOfMainTags += model != ""; + numberOfMainTags += xresolution.nominator || xresolution.denominator; + numberOfMainTags += yresolution.nominator || yresolution.denominator; + numberOfMainTags += resolutionUnit != 0; + numberOfMainTags += exposureTime.nominator || exposureTime.denominator; + numberOfMainTags += softwareUsed != ""; + numberOfMainTags += dateTaken != ""; + + appendU32(result, 8, true); // append offset to the ifd + appendU16(result, numberOfMainTags, true); + + // offsets in result array where the offset to the data should be stored + int makeOffset = 0; + int modelOffset = 0; + int dateTakenOffset = 0; + int softwareUsedOffset = 0; + int gpsInfoOffset = 0; + + // Make + if(make != "") + { + appendU16(result, (uint16_t)IFDTag::MAKE, true); + appendU16(result, (uint16_t)IFDValueType::ASCII, true); + appendU32(result, make.size() + 1, true); + makeOffset = appendU32(result, data.size() + 1, true); + int offsetInData = appendVector(data, (char *)make.c_str(), make.size() + 1); + uint32_t* ptr = (uint32_t *)(result.data() + makeOffset); + *ptr = offsetInData; + } + + // Model + if(model != "") + { + appendU16(result, (uint16_t)IFDTag::MODEL, true); + appendU16(result, (uint16_t)IFDValueType::ASCII, true); + appendU32(result, model.size() + 1, true); + modelOffset = appendU32(result, data.size() + 1, true); + int offsetInData = appendVector(data, (char *)model.c_str(), model.size() + 1); + uint32_t* ptr = (uint32_t *)(result.data() + modelOffset); + *ptr = offsetInData; + } + + // Orientation + if(orientation != 0) + { + appendU16(result, (uint16_t)IFDTag::ORIENTATION, true); + appendU16(result, (uint16_t)IFDValueType::SHORT, true); + appendU32(result, 1, true); + appendU16(result, (uint16_t)orientation, true); + appendU16(result, 0); // padding + } + + // XResolution + if(xresolution.nominator || xresolution.denominator) + { + appendU16(result, (uint16_t)IFDTag::XRESOLUTION, true); + appendU16(result, (uint16_t)IFDValueType::RATIONAL, true); + appendU32(result, xresolution.nominator, true); + appendU32(result, xresolution.denominator, true); + } + + // YResolution + if(yresolution.nominator || yresolution.denominator) + { + appendU16(result, (uint16_t)IFDTag::YRESOLUTION, true); + appendU16(result, (uint16_t)IFDValueType::RATIONAL, true); + appendU32(result, yresolution.nominator, true); + appendU32(result, yresolution.denominator, true); + } + + // Exposure Time + if(exposureTime.nominator || exposureTime.denominator) + { + appendU16(result, (uint16_t)IFDTag::EXPOSURE_TIME, true); + appendU16(result, (uint16_t)IFDValueType::RATIONAL, true); + appendU32(result, exposureTime.nominator, true); + appendU32(result, exposureTime.denominator, true); + } + + // ResolutionUnit + if(resolutionUnit != 0) + { + appendU16(result, (uint16_t)IFDTag::RESOLUTION_UNIT, true); + appendU16(result, (uint16_t)IFDValueType::SHORT, true); + appendU32(result, 1, true); // number of components + appendU16(result, resolutionUnit, true); + appendU16(result, 0); // padding + } + + // Software Used + if(softwareUsed != "") + { + appendU16(result, (uint16_t)IFDTag::SOFTWARE_USED, true); + appendU16(result, (uint16_t)IFDValueType::ASCII, true); + appendU32(result, softwareUsed.size() + 1, true); + softwareUsedOffset = appendU32(result, data.size() + 1, true); + int offsetInData = appendVector(data, (char *)softwareUsed.c_str(), softwareUsed.size() + 1); + uint32_t* ptr = (uint32_t *)(result.data() + softwareUsedOffset); + *ptr = offsetInData; + } + + // Date Taken + // NOTE(vb): For some reason windows file properties doesn't print date taken field! + // Even though other software does provide this information without a problem + if(dateTaken != "") + { + appendU16(result, (uint16_t)IFDTag::DATE_TAKEN, true); + appendU16(result, (uint16_t)IFDValueType::ASCII, true); + appendU32(result, dateTaken.size() + 1, true); + dateTakenOffset = appendU32(result, data.size() + 1, true); + int offsetInData = appendVector(data, (char *)dateTaken.c_str(), dateTaken.size() + 1); + uint32_t* ptr = (uint32_t *)(result.data() + dateTakenOffset); + *ptr = offsetInData; + } + + // GPS Info offset + appendU16(result, (uint16_t) IFDTag::GPS_INFO_OFFSET, true); + appendU16(result, (uint16_t) IFDValueType::LONG_, true); + appendU32(result, 1, true); // num components + gpsInfoOffset = appendU32(result, 0, true); // to be filled + + // next ifd offset + appendU32(result, 0); + + int resultSize = result.size(); + appendVector(result, data); + int ifdAndSubdataSize = result.size(); + + if(modelOffset) + { + uint32_t *ptr = (uint32_t *) (result.data() + modelOffset); + *ptr += resultSize - EXIF_HEADER_SIZE; + *ptr = endianSwap(*ptr); + } + + if(makeOffset) + { + uint32_t *ptr = (uint32_t *) (result.data() + makeOffset); + *ptr += resultSize - EXIF_HEADER_SIZE; + *ptr = endianSwap(*ptr); + } + + if(dateTakenOffset) + { + uint32_t *ptr = (uint32_t *) (result.data() + dateTakenOffset); + *ptr += resultSize - EXIF_HEADER_SIZE; + *ptr = endianSwap(*ptr); + } + + if(softwareUsedOffset) + { + uint32_t *ptr = (uint32_t *) (result.data() + softwareUsedOffset); + *ptr += resultSize - EXIF_HEADER_SIZE; + *ptr = endianSwap(*ptr); + } + + { + uint32_t *ptr = (uint32_t *) (result.data() + gpsInfoOffset); + *ptr = endianSwap((uint32_t)(ifdAndSubdataSize - EXIF_HEADER_SIZE)); + } + + // Writing GPS Info structure + int numberOfGPSInfoTags = 6; + appendU16(result, numberOfGPSInfoTags, true); + + // Latitude Ref + appendU16(result, 1, true); + appendU16(result, (uint16_t) IFDValueType::ASCII, true); + appendU32(result, 2, true); // 2 for N/S + \0 + appendU8(result, latitudeRef == LatitudeRef::NORTH ? 'N' : 'S'); + appendU8(result, 0); + appendU8(result, 0); // padding + appendU8(result, 0); // padding + + // Latitude + appendU16(result, 2, true); + appendU16(result, (uint16_t) IFDValueType::RATIONAL, true); + appendU32(result, 3, true); // number of components + int latitudeOffset = appendU32(result, 0); // 0 * sizeof(RationalValue) + + // Longitude Ref + appendU16(result, 3, true); + appendU16(result, (uint16_t) IFDValueType::ASCII, true); + appendU32(result, 2, true); // 2 for E/W + \0 + appendU8(result, longitudeRef == LongitudeRef::EAST ? 'E' : 'W'); + appendU8(result, 0); + appendU8(result, 0); // padding + appendU8(result, 0); // padding + + // Longitude + appendU16(result, 4, true); + appendU16(result, (uint16_t) IFDValueType::RATIONAL, true); + appendU32(result, 3, true); // number of components + int longitudeOffset = appendU32(result, 24); // 3 * sizeof(RationalValue) + + // Altitude Ref + appendU16(result, 5, true); + appendU16(result, (uint16_t) IFDValueType::BYTE, true); + appendU32(result, 1, true); // number of components + appendU8(result, altitudeIsAboveSeaLevel ? 0 : 1); + appendU8(result, 0); // padding + appendU8(result, 0); // padding + appendU8(result, 0); // padding + + // Altitude + appendU16(result, 6, true); + appendU16(result, (uint16_t) IFDValueType::RATIONAL, true); + appendU32(result, 1, true); // number of components + int altitudeOffset = appendU32(result, 48); // 6 * sizeof(RationalValue) + + int sizeOfResultSoFar = result.size(); + + // Latitude + { + uint32_t *ptr = (uint32_t *) (result.data() + latitudeOffset); + *ptr += sizeOfResultSoFar - EXIF_HEADER_SIZE; + *ptr = endianSwap(*ptr); + } + + // Longitude + { + uint32_t *ptr = (uint32_t *) (result.data() + longitudeOffset); + *ptr += sizeOfResultSoFar - EXIF_HEADER_SIZE; + *ptr = endianSwap(*ptr); + } + + // Altitude + { + uint32_t *ptr = (uint32_t *) (result.data() + altitudeOffset); + *ptr += sizeOfResultSoFar - EXIF_HEADER_SIZE; + *ptr = endianSwap(*ptr); + } + + // + + appendGPSCoords(result, latitude, true); + appendGPSCoords(result, longitude, true); + appendU32(result, altitude, true); + appendU32(result, 1, true); // denominator for altitude + + return result; + } +} \ No newline at end of file diff --git a/openVulkanoCpp/Image/ExifBuilder.hpp b/openVulkanoCpp/Image/ExifBuilder.hpp new file mode 100644 index 0000000..3b30024 --- /dev/null +++ b/openVulkanoCpp/Image/ExifBuilder.hpp @@ -0,0 +1,61 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#pragma once +#include +#include +#include + +namespace OpenVulkano::Image +{ + struct RationalValue + { + uint32_t nominator; + uint32_t denominator; + }; + + enum class LatitudeRef + { + NORTH, + SOUTH, + }; + enum class LongitudeRef + { + EAST, + WEST, + }; + + struct GPSCoords + { + int32_t degrees, minutes, seconds; + }; + + class ExifBuilder + { + 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; + + LatitudeRef latitudeRef = LatitudeRef::NORTH; + GPSCoords latitude = {0, 0, 0}; + + LongitudeRef longitudeRef = LongitudeRef::EAST; + GPSCoords longitude = {0, 0, 0}; + + bool altitudeIsAboveSeaLevel = true; + uint32_t altitude = 0; + + // Typical usage is -> jpeg_write_marker(cinfo, JPEG_APP0 + 1, exif_data.data(), exif_data.size()); + std::vector build(); + }; +} \ No newline at end of file From 0fd5b1aec85faf98af5a0ee887f20c330d02b42c Mon Sep 17 00:00:00 2001 From: Vladyslav Baranovskyi Date: Tue, 17 Sep 2024 19:28:27 +0300 Subject: [PATCH 2/9] Writing x/y resolution and exposureTime properly, storing Track direction --- openVulkanoCpp/Image/ExifBuilder.cpp | 90 +++++++++++++++++++++++++--- openVulkanoCpp/Image/ExifBuilder.hpp | 8 +++ 2 files changed, 89 insertions(+), 9 deletions(-) diff --git a/openVulkanoCpp/Image/ExifBuilder.cpp b/openVulkanoCpp/Image/ExifBuilder.cpp index 8a24616..d8e911b 100644 --- a/openVulkanoCpp/Image/ExifBuilder.cpp +++ b/openVulkanoCpp/Image/ExifBuilder.cpp @@ -127,6 +127,16 @@ namespace return result; } + int appendRational(std::vector& array, const OpenVulkano::Image::RationalValue& rational, bool endianSwap = false) + { + int result = array.size(); + + appendU32(array, rational.nominator, endianSwap); + appendU32(array, rational.denominator, endianSwap); + + return result; + } + int appendGPSCoords(std::vector& array, const OpenVulkano::Image::GPSCoords& coords, bool endianSwap = false) { int result = array.size(); @@ -210,30 +220,42 @@ namespace OpenVulkano::Image } // XResolution + int xresolutionOffset = 0; if(xresolution.nominator || xresolution.denominator) { appendU16(result, (uint16_t)IFDTag::XRESOLUTION, true); appendU16(result, (uint16_t)IFDValueType::RATIONAL, true); - appendU32(result, xresolution.nominator, true); - appendU32(result, xresolution.denominator, true); + appendU32(result, 1, true); // number of components + xresolutionOffset = appendU32(result, data.size(), true); + int offsetInData = appendRational(data, xresolution, true); + uint32_t* ptr = (uint32_t *)(result.data() + xresolutionOffset); + *ptr = offsetInData; } // YResolution + int yresolutionOffset = 0; if(yresolution.nominator || yresolution.denominator) { appendU16(result, (uint16_t)IFDTag::YRESOLUTION, true); appendU16(result, (uint16_t)IFDValueType::RATIONAL, true); - appendU32(result, yresolution.nominator, true); - appendU32(result, yresolution.denominator, true); + appendU32(result, 1, true); // number of components + yresolutionOffset = appendU32(result, data.size(), true); + int offsetInData = appendRational(data, yresolution, true); + uint32_t* ptr = (uint32_t *)(result.data() + yresolutionOffset); + *ptr = offsetInData; } // Exposure Time + int exposureTimeOffset = 0; if(exposureTime.nominator || exposureTime.denominator) { appendU16(result, (uint16_t)IFDTag::EXPOSURE_TIME, true); appendU16(result, (uint16_t)IFDValueType::RATIONAL, true); - appendU32(result, exposureTime.nominator, true); - appendU32(result, exposureTime.denominator, true); + appendU32(result, 1, true); // number of components + exposureTimeOffset = appendU32(result, data.size(), true); + int offsetInData = appendRational(data, exposureTime, true); + uint32_t* ptr = (uint32_t *)(result.data() + exposureTimeOffset); + *ptr = offsetInData; } // ResolutionUnit @@ -285,20 +307,41 @@ namespace OpenVulkano::Image appendVector(result, data); int ifdAndSubdataSize = result.size(); - if(modelOffset) + if(model != "") { uint32_t *ptr = (uint32_t *) (result.data() + modelOffset); *ptr += resultSize - EXIF_HEADER_SIZE; *ptr = endianSwap(*ptr); } - if(makeOffset) + if(make != "") { uint32_t *ptr = (uint32_t *) (result.data() + makeOffset); *ptr += resultSize - EXIF_HEADER_SIZE; *ptr = endianSwap(*ptr); } + if(xresolutionOffset) + { + uint32_t *ptr = (uint32_t *) (result.data() + xresolutionOffset); + *ptr += resultSize - EXIF_HEADER_SIZE; + *ptr = endianSwap(*ptr); + } + + if(yresolutionOffset) + { + uint32_t *ptr = (uint32_t *) (result.data() + yresolutionOffset); + *ptr += resultSize - EXIF_HEADER_SIZE; + *ptr = endianSwap(*ptr); + } + + if(exposureTimeOffset) + { + uint32_t *ptr = (uint32_t *) (result.data() + exposureTimeOffset); + *ptr += resultSize - EXIF_HEADER_SIZE; + *ptr = endianSwap(*ptr); + } + if(dateTakenOffset) { uint32_t *ptr = (uint32_t *) (result.data() + dateTakenOffset); @@ -319,7 +362,7 @@ namespace OpenVulkano::Image } // Writing GPS Info structure - int numberOfGPSInfoTags = 6; + int numberOfGPSInfoTags = 8; appendU16(result, numberOfGPSInfoTags, true); // Latitude Ref @@ -367,6 +410,23 @@ namespace OpenVulkano::Image appendU32(result, 1, true); // number of components int altitudeOffset = appendU32(result, 48); // 6 * sizeof(RationalValue) + // Track Ref + appendU16(result, 14, true); + appendU16(result, (uint16_t) IFDValueType::ASCII, true); + appendU32(result, 2, true); // 2 for T/M + \0 + appendU8(result, trackRef == GPSTrackRef::TRUE_ ? 'T' : 'M'); + appendU8(result, 0); + appendU8(result, 0); // padding + appendU8(result, 0); // padding + + // Track + appendU16(result, 15, true); + appendU16(result, (uint16_t) IFDValueType::RATIONAL, true); + appendU32(result, 1, true); // number of components + int trackOffset = appendU32(result, 56); // 7 * sizeof(RationalValue) + + // + int sizeOfResultSoFar = result.size(); // Latitude @@ -390,13 +450,25 @@ namespace OpenVulkano::Image *ptr = endianSwap(*ptr); } + // Track + { + uint32_t *ptr = (uint32_t *) (result.data() + trackOffset); + *ptr += sizeOfResultSoFar - EXIF_HEADER_SIZE; + *ptr = endianSwap(*ptr); + } + // appendGPSCoords(result, latitude, true); appendGPSCoords(result, longitude, true); + appendU32(result, altitude, true); appendU32(result, 1, true); // denominator for altitude + int const TRACK_PRECISION = 10000; + appendU32(result, track * TRACK_PRECISION, true); + appendU32(result, TRACK_PRECISION, true); + return result; } } \ No newline at end of file diff --git a/openVulkanoCpp/Image/ExifBuilder.hpp b/openVulkanoCpp/Image/ExifBuilder.hpp index 3b30024..b7062a4 100644 --- a/openVulkanoCpp/Image/ExifBuilder.hpp +++ b/openVulkanoCpp/Image/ExifBuilder.hpp @@ -27,6 +27,11 @@ namespace OpenVulkano::Image EAST, WEST, }; + enum class GPSTrackRef + { + TRUE_, + MAGNETIC + }; struct GPSCoords { @@ -55,6 +60,9 @@ namespace OpenVulkano::Image bool altitudeIsAboveSeaLevel = true; uint32_t altitude = 0; + GPSTrackRef trackRef = GPSTrackRef::TRUE_; + float track = 0; // range is [0.0; 360.0) + // Typical usage is -> jpeg_write_marker(cinfo, JPEG_APP0 + 1, exif_data.data(), exif_data.size()); std::vector build(); }; From ed87e1dfdb74186921760f3ad196a461d981c267 Mon Sep 17 00:00:00 2001 From: Vladyslav Baranovskyi Date: Mon, 23 Sep 2024 22:09:36 +0300 Subject: [PATCH 3/9] Summary: - Using std::array instead of c arrays - Methods and functions are now in PascalCase - Using std::reverse in EndianSwap - Using little_endian bool variable - TRUE_ is now TRUE_NORTH - Added constructors to GPSCoords class - GetCurrentTimestamp() method - Added setter for altitude variable - Minor renamings --- openVulkanoCpp/Image/ExifBuilder.cpp | 571 +++++++++++++++------------ openVulkanoCpp/Image/ExifBuilder.hpp | 24 +- 2 files changed, 326 insertions(+), 269 deletions(-) diff --git a/openVulkanoCpp/Image/ExifBuilder.cpp b/openVulkanoCpp/Image/ExifBuilder.cpp index d8e911b..3fe5436 100644 --- a/openVulkanoCpp/Image/ExifBuilder.cpp +++ b/openVulkanoCpp/Image/ExifBuilder.cpp @@ -5,16 +5,24 @@ */ #include "ExifBuilder.hpp" - -#define ARRAY_COUNT(Array) (sizeof(Array) / sizeof(*Array)) +#include +#include +#include +#include +#include +#include +#include +#include namespace { - char EXIF_HEADER_AND_PADDING[] = {'E', 'x', 'i', 'f', 0, 0}; - int const EXIF_HEADER_SIZE = ARRAY_COUNT(EXIF_HEADER_AND_PADDING); + const int EXIF_HEADER_SIZE = 6; + std::array EXIF_HEADER_AND_PADDING = { 'E', 'x', 'i', 'f', 0, 0 }; - char TIFF_HEADER[] = {0x4d, 0x4d, 0, 0x2a}; - int const TIFF_HEADER_SIZE = ARRAY_COUNT(TIFF_HEADER); + const int TIFF_HEADER_SIZE = 4; + std::array TIFF_HEADER = { 0x4d, 0x4d, 0, 0x2a }; + + constexpr bool LITTLE_ENDIAN = std::endian::native == std::endian::little; enum class IFDTag : uint16_t { @@ -22,8 +30,8 @@ namespace MAKE = 0x010f, MODEL = 0x0110, ORIENTATION = 0x0112, - XRESOLUTION = 0x011a, - YRESOLUTION = 0x011b, + X_RESOLUTION = 0x011a, + Y_RESOLUTION = 0x011b, RESOLUTION_UNIT = 0x0128, SOFTWARE_USED = 0x0131, DATE_TAKEN = 0x0132, @@ -40,143 +48,212 @@ namespace RATIONAL = 5, }; - uint32_t endianSwap(uint32_t Value) + uint32_t EndianSwap(uint32_t value) { - uint32_t Result; + uint32_t result; - char *Ptr = (char *)&Value; - char *Out = (char *)&Result; - Out[0] = Ptr[3]; - Out[1] = Ptr[2]; - Out[2] = Ptr[1]; - Out[3] = Ptr[0]; + char *ptr = (char *) &value; + std::reverse(ptr, ptr + 4); + result = value; - return Result; - } - - uint16_t endianSwap(uint16_t Value) - { - uint16_t Result; - - char *Ptr = (char *)&Value; - char *Out = (char *)&Result; - Out[0] = Ptr[1]; - Out[1] = Ptr[0]; - - return Result; - } - - int appendU8(std::vector& array, uint8_t value) - { - int result = array.size(); - array.push_back(value); return result; } - int appendU16(std::vector& array, uint16_t value, bool endianSwap = false) + uint16_t EndianSwap(uint16_t value) { - int result = array.size(); + uint16_t result; - if(endianSwap) - value = ::endianSwap(value); + char *ptr = (char *) &value; + std::reverse(ptr, ptr + 2); + result = value; - char *src = (char *)&value; + return result; + } + + int AppendU8(std::vector& array, uint8_t value) + { + int offset = array.size(); + array.push_back(value); + return offset; + } + + int AppendU16(std::vector& array, uint16_t value) + { + int offset = array.size(); + + if constexpr (LITTLE_ENDIAN) + { + value = ::EndianSwap(value); + } + + char *src = (char *) &value; array.push_back(src[0]); array.push_back(src[1]); - return result; + return offset; } - int appendU32(std::vector& array, uint32_t value, bool endianSwap = false) + // no endian swap + int AppendU32NES(std::vector& array, uint32_t value) { - int result = array.size(); + int offset = array.size(); - if(endianSwap) - value = ::endianSwap(value); - - char *src = (char *)&value; + char *src = (char *) &value; array.push_back(src[0]); array.push_back(src[1]); array.push_back(src[2]); array.push_back(src[3]); - return result; + return offset; } - int appendVector(std::vector& array, std::vector values) + int AppendU32(std::vector& array, uint32_t value) { - int result = array.size(); + int offset = array.size(); - for(auto value : values) + if constexpr (LITTLE_ENDIAN) + { + value = ::EndianSwap(value); + } + + char *src = (char *) &value; + array.push_back(src[0]); + array.push_back(src[1]); + array.push_back(src[2]); + array.push_back(src[3]); + + return offset; + } + + template + int AppendVector(std::vector& array, const std::array& values) + { + int offset = array.size(); + + for (auto value: values) { array.push_back(value); } - return result; + return offset; } - int appendVector(std::vector& array, char* values, int count) + int AppendVector(std::vector& array, std::vector values) { - int result = array.size(); + int offset = array.size(); - for(int i = 0; i < count; ++i) + for (auto value: values) + { + array.push_back(value); + } + + return offset; + } + + int AppendVector(std::vector& array, char* values, int count) + { + int offset = array.size(); + + for (int i = 0; i < count; ++i) { array.push_back(values[i]); } - return result; + return offset; } - int appendRational(std::vector& array, const OpenVulkano::Image::RationalValue& rational, bool endianSwap = false) + int AppendRational(std::vector& array, const OpenVulkano::Image::RationalValue& rational) { - int result = array.size(); + int offset = array.size(); - appendU32(array, rational.nominator, endianSwap); - appendU32(array, rational.denominator, endianSwap); + AppendU32(array, rational.nominator); + AppendU32(array, rational.denominator); - return result; + return offset; } - int appendGPSCoords(std::vector& array, const OpenVulkano::Image::GPSCoords& coords, bool endianSwap = false) + int AppendGPSCoords(std::vector& array, const OpenVulkano::Image::GPSCoords& coords) { - int result = array.size(); + int offset = array.size(); - appendU32(array, coords.degrees, endianSwap); - appendU32(array, 1, endianSwap); + AppendU32(array, coords.degrees); + AppendU32(array, 1); - appendU32(array, coords.minutes, endianSwap); - appendU32(array, 1, endianSwap); + AppendU32(array, coords.minutes); + AppendU32(array, 1); - appendU32(array, coords.seconds, endianSwap); - appendU32(array, 1, endianSwap); + AppendU32(array, coords.seconds); + AppendU32(array, 1); - return result; + return offset; + } + + void AppendTagAndValueType(std::vector& array, uint16_t tag, uint16_t valueType) + { + AppendU16(array, tag); + AppendU16(array, valueType); + } + + void AddValueToU32AndEndianSwap(uint8_t *data, int valueToAdd) + { + uint32_t *ptr = (uint32_t *) data; + *ptr += valueToAdd; + *ptr = EndianSwap(*ptr); } } namespace OpenVulkano::Image { - std::vector ExifBuilder::build() + GPSCoords::GPSCoords(int32_t valueForAll) + { + this->degrees = valueForAll; + this->minutes = valueForAll; + this->seconds = valueForAll; + } + + GPSCoords::GPSCoords(int32_t degrees, int32_t minutes, int32_t seconds) + { + this->degrees = degrees; + this->minutes = minutes; + this->seconds = seconds; + } + + void ExifBuilder::SetAltitude(float level) + { + altitudeIsAboveSeaLevel = level >= 0; + if (level < 0) + { + level = -level; + } + altitude = level; + } + + std::vector ExifBuilder::Build() { std::vector result; - std::vector data; // the data that has ascii values + std::vector data; // the data that has ascii and rational values - appendVector(result, EXIF_HEADER_AND_PADDING, EXIF_HEADER_SIZE); - appendVector(result, TIFF_HEADER, TIFF_HEADER_SIZE); + if (dateTaken == "") + { + dateTaken = GetCurrentTimestamp(); + } + + AppendVector(result, EXIF_HEADER_AND_PADDING); + AppendVector(result, TIFF_HEADER); int numberOfMainTags = 1; // 1 is for GPS Info tag numberOfMainTags += orientation != 0; numberOfMainTags += make != ""; numberOfMainTags += model != ""; - numberOfMainTags += xresolution.nominator || xresolution.denominator; - numberOfMainTags += yresolution.nominator || yresolution.denominator; + numberOfMainTags += xResolution.nominator || xResolution.denominator; + numberOfMainTags += yResolution.nominator || yResolution.denominator; numberOfMainTags += resolutionUnit != 0; numberOfMainTags += exposureTime.nominator || exposureTime.denominator; numberOfMainTags += softwareUsed != ""; numberOfMainTags += dateTaken != ""; - appendU32(result, 8, true); // append offset to the ifd - appendU16(result, numberOfMainTags, true); + AppendU32(result, 8); // Append offset to the ifd + AppendU16(result, numberOfMainTags); // offsets in result array where the offset to the data should be stored int makeOffset = 0; @@ -186,96 +263,88 @@ namespace OpenVulkano::Image int gpsInfoOffset = 0; // Make - if(make != "") + if (make != "") { - appendU16(result, (uint16_t)IFDTag::MAKE, true); - appendU16(result, (uint16_t)IFDValueType::ASCII, true); - appendU32(result, make.size() + 1, true); - makeOffset = appendU32(result, data.size() + 1, true); - int offsetInData = appendVector(data, (char *)make.c_str(), make.size() + 1); + AppendTagAndValueType(result, (uint16_t) IFDTag::MAKE, (uint16_t) IFDValueType::ASCII); + AppendU32(result, make.size() + 1); + makeOffset = AppendU32(result, data.size() + 1); + int offsetInData = AppendVector(data, (char *)make.c_str(), make.size() + 1); uint32_t* ptr = (uint32_t *)(result.data() + makeOffset); *ptr = offsetInData; } // Model - if(model != "") + if (model != "") { - appendU16(result, (uint16_t)IFDTag::MODEL, true); - appendU16(result, (uint16_t)IFDValueType::ASCII, true); - appendU32(result, model.size() + 1, true); - modelOffset = appendU32(result, data.size() + 1, true); - int offsetInData = appendVector(data, (char *)model.c_str(), model.size() + 1); + AppendTagAndValueType(result, (uint16_t) IFDTag::MODEL, (uint16_t) IFDValueType::ASCII); + AppendU32(result, model.size() + 1); + modelOffset = AppendU32(result, data.size() + 1); + int offsetInData = AppendVector(data, (char *)model.c_str(), model.size() + 1); uint32_t* ptr = (uint32_t *)(result.data() + modelOffset); *ptr = offsetInData; } // Orientation - if(orientation != 0) + if (orientation != 0) { - appendU16(result, (uint16_t)IFDTag::ORIENTATION, true); - appendU16(result, (uint16_t)IFDValueType::SHORT, true); - appendU32(result, 1, true); - appendU16(result, (uint16_t)orientation, true); - appendU16(result, 0); // padding + AppendTagAndValueType(result, (uint16_t) IFDTag::ORIENTATION, (uint16_t) IFDValueType::SHORT); + AppendU32(result, 1); + AppendU16(result, (uint16_t)orientation); + AppendU16(result, 0); // padding } - // XResolution - int xresolutionOffset = 0; - if(xresolution.nominator || xresolution.denominator) + // xResolution + int xResolutionOffset = 0; + if (xResolution.nominator || xResolution.denominator) { - appendU16(result, (uint16_t)IFDTag::XRESOLUTION, true); - appendU16(result, (uint16_t)IFDValueType::RATIONAL, true); - appendU32(result, 1, true); // number of components - xresolutionOffset = appendU32(result, data.size(), true); - int offsetInData = appendRational(data, xresolution, true); - uint32_t* ptr = (uint32_t *)(result.data() + xresolutionOffset); + AppendTagAndValueType(result, (uint16_t) IFDTag::X_RESOLUTION, (uint16_t) IFDValueType::RATIONAL); + AppendU32(result, 1); // number of components + xResolutionOffset = AppendU32(result, data.size()); + int offsetInData = AppendRational(data, xResolution); + uint32_t* ptr = (uint32_t *)(result.data() + xResolutionOffset); *ptr = offsetInData; } - // YResolution - int yresolutionOffset = 0; - if(yresolution.nominator || yresolution.denominator) + // yResolution + int yResolutionOffset = 0; + if (yResolution.nominator || yResolution.denominator) { - appendU16(result, (uint16_t)IFDTag::YRESOLUTION, true); - appendU16(result, (uint16_t)IFDValueType::RATIONAL, true); - appendU32(result, 1, true); // number of components - yresolutionOffset = appendU32(result, data.size(), true); - int offsetInData = appendRational(data, yresolution, true); - uint32_t* ptr = (uint32_t *)(result.data() + yresolutionOffset); + AppendTagAndValueType(result, (uint16_t) IFDTag::Y_RESOLUTION, (uint16_t) IFDValueType::RATIONAL); + AppendU32(result, 1); // number of components + yResolutionOffset = AppendU32(result, data.size()); + int offsetInData = AppendRational(data, yResolution); + uint32_t* ptr = (uint32_t *)(result.data() + yResolutionOffset); *ptr = offsetInData; } // Exposure Time int exposureTimeOffset = 0; - if(exposureTime.nominator || exposureTime.denominator) + if (exposureTime.nominator || exposureTime.denominator) { - appendU16(result, (uint16_t)IFDTag::EXPOSURE_TIME, true); - appendU16(result, (uint16_t)IFDValueType::RATIONAL, true); - appendU32(result, 1, true); // number of components - exposureTimeOffset = appendU32(result, data.size(), true); - int offsetInData = appendRational(data, exposureTime, true); + AppendTagAndValueType(result, (uint16_t) IFDTag::EXPOSURE_TIME, (uint16_t) IFDValueType::RATIONAL); + AppendU32(result, 1); // number of components + exposureTimeOffset = AppendU32(result, data.size()); + int offsetInData = AppendRational(data, exposureTime); uint32_t* ptr = (uint32_t *)(result.data() + exposureTimeOffset); *ptr = offsetInData; } // ResolutionUnit - if(resolutionUnit != 0) + if (resolutionUnit != 0) { - appendU16(result, (uint16_t)IFDTag::RESOLUTION_UNIT, true); - appendU16(result, (uint16_t)IFDValueType::SHORT, true); - appendU32(result, 1, true); // number of components - appendU16(result, resolutionUnit, true); - appendU16(result, 0); // padding + AppendTagAndValueType(result, (uint16_t) IFDTag::RESOLUTION_UNIT, (uint16_t) IFDValueType::SHORT); + AppendU32(result, 1); // number of components + AppendU16(result, resolutionUnit); + AppendU16(result, 0); // padding } // Software Used - if(softwareUsed != "") + if (softwareUsed != "") { - appendU16(result, (uint16_t)IFDTag::SOFTWARE_USED, true); - appendU16(result, (uint16_t)IFDValueType::ASCII, true); - appendU32(result, softwareUsed.size() + 1, true); - softwareUsedOffset = appendU32(result, data.size() + 1, true); - int offsetInData = appendVector(data, (char *)softwareUsed.c_str(), softwareUsed.size() + 1); + AppendTagAndValueType(result, (uint16_t) IFDTag::SOFTWARE_USED, (uint16_t) IFDValueType::ASCII); + AppendU32(result, softwareUsed.size() + 1); + softwareUsedOffset = AppendU32(result, data.size() + 1); + int offsetInData = AppendVector(data, (char *)softwareUsed.c_str(), softwareUsed.size() + 1); uint32_t* ptr = (uint32_t *)(result.data() + softwareUsedOffset); *ptr = offsetInData; } @@ -283,192 +352,174 @@ namespace OpenVulkano::Image // Date Taken // NOTE(vb): For some reason windows file properties doesn't print date taken field! // Even though other software does provide this information without a problem - if(dateTaken != "") { - appendU16(result, (uint16_t)IFDTag::DATE_TAKEN, true); - appendU16(result, (uint16_t)IFDValueType::ASCII, true); - appendU32(result, dateTaken.size() + 1, true); - dateTakenOffset = appendU32(result, data.size() + 1, true); - int offsetInData = appendVector(data, (char *)dateTaken.c_str(), dateTaken.size() + 1); + AppendTagAndValueType(result, (uint16_t) IFDTag::DATE_TAKEN, (uint16_t) IFDValueType::ASCII); + AppendU32(result, dateTaken.size() + 1); + dateTakenOffset = AppendU32(result, data.size() + 1); + int offsetInData = AppendVector(data, (char *)dateTaken.c_str(), dateTaken.size() + 1); uint32_t* ptr = (uint32_t *)(result.data() + dateTakenOffset); *ptr = offsetInData; } // GPS Info offset - appendU16(result, (uint16_t) IFDTag::GPS_INFO_OFFSET, true); - appendU16(result, (uint16_t) IFDValueType::LONG_, true); - appendU32(result, 1, true); // num components - gpsInfoOffset = appendU32(result, 0, true); // to be filled + AppendTagAndValueType(result, (uint16_t) IFDTag::GPS_INFO_OFFSET, (uint16_t) IFDValueType::LONG_); + AppendU32(result, 1); // num components + gpsInfoOffset = AppendU32(result, 0); // to be filled // next ifd offset - appendU32(result, 0); + AppendU32(result, 0); int resultSize = result.size(); - appendVector(result, data); + AppendVector(result, data); int ifdAndSubdataSize = result.size(); - if(model != "") { - uint32_t *ptr = (uint32_t *) (result.data() + modelOffset); - *ptr += resultSize - EXIF_HEADER_SIZE; - *ptr = endianSwap(*ptr); - } + const int valueToAdd = resultSize - EXIF_HEADER_SIZE; + if (model != "") + { + AddValueToU32AndEndianSwap(result.data() + modelOffset, valueToAdd); + } - if(make != "") - { - uint32_t *ptr = (uint32_t *) (result.data() + makeOffset); - *ptr += resultSize - EXIF_HEADER_SIZE; - *ptr = endianSwap(*ptr); - } + if (make != "") + { + AddValueToU32AndEndianSwap(result.data() + makeOffset, valueToAdd); + } - if(xresolutionOffset) - { - uint32_t *ptr = (uint32_t *) (result.data() + xresolutionOffset); - *ptr += resultSize - EXIF_HEADER_SIZE; - *ptr = endianSwap(*ptr); - } + if (xResolutionOffset) + { + AddValueToU32AndEndianSwap(result.data() + xResolutionOffset, valueToAdd); + } - if(yresolutionOffset) - { - uint32_t *ptr = (uint32_t *) (result.data() + yresolutionOffset); - *ptr += resultSize - EXIF_HEADER_SIZE; - *ptr = endianSwap(*ptr); - } + if (yResolutionOffset) + { + AddValueToU32AndEndianSwap(result.data() + yResolutionOffset, valueToAdd); + } - if(exposureTimeOffset) - { - uint32_t *ptr = (uint32_t *) (result.data() + exposureTimeOffset); - *ptr += resultSize - EXIF_HEADER_SIZE; - *ptr = endianSwap(*ptr); - } + if (exposureTimeOffset) + { + AddValueToU32AndEndianSwap(result.data() + exposureTimeOffset, valueToAdd); + } - if(dateTakenOffset) - { - uint32_t *ptr = (uint32_t *) (result.data() + dateTakenOffset); - *ptr += resultSize - EXIF_HEADER_SIZE; - *ptr = endianSwap(*ptr); - } + if (dateTakenOffset) + { + AddValueToU32AndEndianSwap(result.data() + dateTakenOffset, valueToAdd); + } - if(softwareUsedOffset) - { - uint32_t *ptr = (uint32_t *) (result.data() + softwareUsedOffset); - *ptr += resultSize - EXIF_HEADER_SIZE; - *ptr = endianSwap(*ptr); + if (softwareUsedOffset) + { + AddValueToU32AndEndianSwap(result.data() + softwareUsedOffset, valueToAdd); + } } { uint32_t *ptr = (uint32_t *) (result.data() + gpsInfoOffset); - *ptr = endianSwap((uint32_t)(ifdAndSubdataSize - EXIF_HEADER_SIZE)); + *ptr = EndianSwap((uint32_t)(ifdAndSubdataSize - EXIF_HEADER_SIZE)); } // Writing GPS Info structure int numberOfGPSInfoTags = 8; - appendU16(result, numberOfGPSInfoTags, true); + AppendU16(result, numberOfGPSInfoTags); // Latitude Ref - appendU16(result, 1, true); - appendU16(result, (uint16_t) IFDValueType::ASCII, true); - appendU32(result, 2, true); // 2 for N/S + \0 - appendU8(result, latitudeRef == LatitudeRef::NORTH ? 'N' : 'S'); - appendU8(result, 0); - appendU8(result, 0); // padding - appendU8(result, 0); // padding + AppendTagAndValueType(result, 1, (uint16_t) IFDValueType::ASCII); + AppendU32(result, 2); // 2 for N/S + \0 + AppendU8(result, latitudeRef == LatitudeRef::NORTH ? 'N' : 'S'); + AppendU8(result, 0); + AppendU8(result, 0); // padding + AppendU8(result, 0); // padding // Latitude - appendU16(result, 2, true); - appendU16(result, (uint16_t) IFDValueType::RATIONAL, true); - appendU32(result, 3, true); // number of components - int latitudeOffset = appendU32(result, 0); // 0 * sizeof(RationalValue) + AppendTagAndValueType(result, 2, (uint16_t) IFDValueType::RATIONAL); + AppendU32(result, 3); // number of components + int latitudeOffset = AppendU32NES(result, 0); // 0 * sizeof(RationalValue) // Longitude Ref - appendU16(result, 3, true); - appendU16(result, (uint16_t) IFDValueType::ASCII, true); - appendU32(result, 2, true); // 2 for E/W + \0 - appendU8(result, longitudeRef == LongitudeRef::EAST ? 'E' : 'W'); - appendU8(result, 0); - appendU8(result, 0); // padding - appendU8(result, 0); // padding + AppendTagAndValueType(result, 3, (uint16_t) IFDValueType::ASCII); + AppendU32(result, 2); // 2 for E/W + \0 + AppendU8(result, longitudeRef == LongitudeRef::EAST ? 'E' : 'W'); + AppendU8(result, 0); + AppendU8(result, 0); // padding + AppendU8(result, 0); // padding // Longitude - appendU16(result, 4, true); - appendU16(result, (uint16_t) IFDValueType::RATIONAL, true); - appendU32(result, 3, true); // number of components - int longitudeOffset = appendU32(result, 24); // 3 * sizeof(RationalValue) + AppendTagAndValueType(result, 4, (uint16_t) IFDValueType::RATIONAL); + AppendU32(result, 3); // number of components + int longitudeOffset = AppendU32NES(result, 24); // 3 * sizeof(RationalValue) // Altitude Ref - appendU16(result, 5, true); - appendU16(result, (uint16_t) IFDValueType::BYTE, true); - appendU32(result, 1, true); // number of components - appendU8(result, altitudeIsAboveSeaLevel ? 0 : 1); - appendU8(result, 0); // padding - appendU8(result, 0); // padding - appendU8(result, 0); // padding + AppendTagAndValueType(result, 5, (uint16_t) IFDValueType::BYTE); + AppendU32(result, 1); // number of components + AppendU8(result, altitudeIsAboveSeaLevel ? 0 : 1); + AppendU8(result, 0); // padding + AppendU8(result, 0); // padding + AppendU8(result, 0); // padding // Altitude - appendU16(result, 6, true); - appendU16(result, (uint16_t) IFDValueType::RATIONAL, true); - appendU32(result, 1, true); // number of components - int altitudeOffset = appendU32(result, 48); // 6 * sizeof(RationalValue) + AppendTagAndValueType(result, 6, (uint16_t) IFDValueType::RATIONAL); + AppendU32(result, 1); // number of components + int altitudeOffset = AppendU32NES(result, 48); // 6 * sizeof(RationalValue) // Track Ref - appendU16(result, 14, true); - appendU16(result, (uint16_t) IFDValueType::ASCII, true); - appendU32(result, 2, true); // 2 for T/M + \0 - appendU8(result, trackRef == GPSTrackRef::TRUE_ ? 'T' : 'M'); - appendU8(result, 0); - appendU8(result, 0); // padding - appendU8(result, 0); // padding + AppendTagAndValueType(result, 14, (uint16_t) IFDValueType::ASCII); + AppendU32(result, 2); // 2 for T/M + \0 + AppendU8(result, trackRef == GPSTrackRef::TRUE_NORTH ? 'T' : 'M'); + AppendU8(result, 0); + AppendU8(result, 0); // padding + AppendU8(result, 0); // padding // Track - appendU16(result, 15, true); - appendU16(result, (uint16_t) IFDValueType::RATIONAL, true); - appendU32(result, 1, true); // number of components - int trackOffset = appendU32(result, 56); // 7 * sizeof(RationalValue) + AppendTagAndValueType(result, 15, (uint16_t) IFDValueType::RATIONAL); + AppendU32(result, 1); // number of components + int trackOffset = AppendU32NES(result, 56); // 7 * sizeof(RationalValue) // - int sizeOfResultSoFar = result.size(); - - // Latitude { - uint32_t *ptr = (uint32_t *) (result.data() + latitudeOffset); - *ptr += sizeOfResultSoFar - EXIF_HEADER_SIZE; - *ptr = endianSwap(*ptr); - } + int sizeOfResultSoFar = result.size(); + const int valueToAdd = sizeOfResultSoFar - EXIF_HEADER_SIZE; + // Latitude + { + AddValueToU32AndEndianSwap(result.data() + latitudeOffset, valueToAdd); + } - // Longitude - { - uint32_t *ptr = (uint32_t *) (result.data() + longitudeOffset); - *ptr += sizeOfResultSoFar - EXIF_HEADER_SIZE; - *ptr = endianSwap(*ptr); - } + // Longitude + { + AddValueToU32AndEndianSwap(result.data() + longitudeOffset, valueToAdd); + } - // Altitude - { - uint32_t *ptr = (uint32_t *) (result.data() + altitudeOffset); - *ptr += sizeOfResultSoFar - EXIF_HEADER_SIZE; - *ptr = endianSwap(*ptr); - } + // Altitude + { + AddValueToU32AndEndianSwap(result.data() + altitudeOffset, valueToAdd); + } - // Track - { - uint32_t *ptr = (uint32_t *) (result.data() + trackOffset); - *ptr += sizeOfResultSoFar - EXIF_HEADER_SIZE; - *ptr = endianSwap(*ptr); + // Track + { + AddValueToU32AndEndianSwap(result.data() + trackOffset, valueToAdd); + } } // - appendGPSCoords(result, latitude, true); - appendGPSCoords(result, longitude, true); + AppendGPSCoords(result, latitude); + AppendGPSCoords(result, longitude); - appendU32(result, altitude, true); - appendU32(result, 1, true); // denominator for altitude + AppendU32(result, altitude); + AppendU32(result, 1); // denominator for altitude int const TRACK_PRECISION = 10000; - appendU32(result, track * TRACK_PRECISION, true); - appendU32(result, TRACK_PRECISION, true); + AppendU32(result, track * TRACK_PRECISION); + AppendU32(result, TRACK_PRECISION); return result; } + + std::string ExifBuilder::GetCurrentTimestamp() + { + auto now = std::chrono::system_clock::now(); + std::time_t currentTime = std::chrono::system_clock::to_time_t(now); + std::tm *timeInfo = std::localtime(¤tTime); + std::ostringstream oss; + oss << std::put_time(timeInfo, "%Y:%m:%d %H:%M:%S"); + return oss.str(); + } } \ No newline at end of file diff --git a/openVulkanoCpp/Image/ExifBuilder.hpp b/openVulkanoCpp/Image/ExifBuilder.hpp index b7062a4..8548dac 100644 --- a/openVulkanoCpp/Image/ExifBuilder.hpp +++ b/openVulkanoCpp/Image/ExifBuilder.hpp @@ -29,13 +29,16 @@ namespace OpenVulkano::Image }; enum class GPSTrackRef { - TRUE_, + TRUE_NORTH, MAGNETIC }; struct GPSCoords { int32_t degrees, minutes, seconds; + + GPSCoords(int32_t valueForAll = 0); + GPSCoords(int32_t degrees, int32_t minutes, int32_t seconds); }; class ExifBuilder @@ -44,26 +47,29 @@ namespace OpenVulkano::Image int orientation = 0; std::string make; std::string model; - RationalValue xresolution = {0, 0}; - RationalValue yresolution = {0, 0}; + RationalValue xResolution = { 0, 0 }; + RationalValue yResolution = { 0, 0 }; int resolutionUnit = 0; - RationalValue exposureTime = {0, 0}; + RationalValue exposureTime = { 0, 0 }; std::string dateTaken; // format: yyyy:mm:dd hh:mm:ss - std::string softwareUsed; + std::string softwareUsed = "OpenVulkano"; LatitudeRef latitudeRef = LatitudeRef::NORTH; - GPSCoords latitude = {0, 0, 0}; + GPSCoords latitude = { 0, 0, 0 }; LongitudeRef longitudeRef = LongitudeRef::EAST; - GPSCoords longitude = {0, 0, 0}; + GPSCoords longitude = { 0, 0, 0 }; bool altitudeIsAboveSeaLevel = true; uint32_t altitude = 0; - GPSTrackRef trackRef = GPSTrackRef::TRUE_; + GPSTrackRef trackRef = GPSTrackRef::TRUE_NORTH; float track = 0; // range is [0.0; 360.0) + + void SetAltitude(float level); // Typical usage is -> jpeg_write_marker(cinfo, JPEG_APP0 + 1, exif_data.data(), exif_data.size()); - std::vector build(); + std::vector Build(); + static std::string GetCurrentTimestamp(); }; } \ No newline at end of file From a166350abd361f8895ee3dad4c7e0b4410806b47 Mon Sep 17 00:00:00 2001 From: Vladyslav Baranovskyi Date: Tue, 24 Sep 2024 16:37:37 +0300 Subject: [PATCH 4/9] Minor changes --- openVulkanoCpp/Image/ExifBuilder.cpp | 67 +++++++++++----------------- 1 file changed, 27 insertions(+), 40 deletions(-) diff --git a/openVulkanoCpp/Image/ExifBuilder.cpp b/openVulkanoCpp/Image/ExifBuilder.cpp index 3fe5436..1f8eebc 100644 --- a/openVulkanoCpp/Image/ExifBuilder.cpp +++ b/openVulkanoCpp/Image/ExifBuilder.cpp @@ -16,11 +16,11 @@ namespace { - const int EXIF_HEADER_SIZE = 6; - std::array EXIF_HEADER_AND_PADDING = { 'E', 'x', 'i', 'f', 0, 0 }; + constexpr int EXIF_HEADER_SIZE = 6; + constexpr std::array EXIF_HEADER_AND_PADDING = { 'E', 'x', 'i', 'f', 0, 0 }; - const int TIFF_HEADER_SIZE = 4; - std::array TIFF_HEADER = { 0x4d, 0x4d, 0, 0x2a }; + constexpr int TIFF_HEADER_SIZE = 4; + constexpr std::array TIFF_HEADER = { 0x4d, 0x4d, 0, 0x2a }; constexpr bool LITTLE_ENDIAN = std::endian::native == std::endian::little; @@ -70,59 +70,46 @@ namespace return result; } - int AppendU8(std::vector& array, uint8_t value) + template + int Append(std::vector& array, T value) { int offset = array.size(); - array.push_back(value); + const int bytes = sizeof(T); + char *c = (char *) &value; + for (int i = 0; i < bytes; i++) + { + array.push_back(c[i]); + } return offset; } + int AppendU8(std::vector& array, uint8_t value) + { + return Append(array, value); + } + int AppendU16(std::vector& array, uint16_t value) { - int offset = array.size(); - if constexpr (LITTLE_ENDIAN) { value = ::EndianSwap(value); } - - char *src = (char *) &value; - array.push_back(src[0]); - array.push_back(src[1]); - - return offset; + return Append(array, value); } // no endian swap int AppendU32NES(std::vector& array, uint32_t value) { - int offset = array.size(); - - char *src = (char *) &value; - array.push_back(src[0]); - array.push_back(src[1]); - array.push_back(src[2]); - array.push_back(src[3]); - - return offset; + return Append(array, value); } int AppendU32(std::vector& array, uint32_t value) { - int offset = array.size(); - if constexpr (LITTLE_ENDIAN) { value = ::EndianSwap(value); } - - char *src = (char *) &value; - array.push_back(src[0]); - array.push_back(src[1]); - array.push_back(src[2]); - array.push_back(src[3]); - - return offset; + return Append(array, value); } template @@ -138,7 +125,7 @@ namespace return offset; } - int AppendVector(std::vector& array, std::vector values) + int AppendVector(std::vector& array, const std::vector& values) { int offset = array.size(); @@ -233,7 +220,7 @@ namespace OpenVulkano::Image std::vector result; std::vector data; // the data that has ascii and rational values - if (dateTaken == "") + if (dateTaken.empty()) { dateTaken = GetCurrentTimestamp(); } @@ -263,7 +250,7 @@ namespace OpenVulkano::Image int gpsInfoOffset = 0; // Make - if (make != "") + if (!make.empty()) { AppendTagAndValueType(result, (uint16_t) IFDTag::MAKE, (uint16_t) IFDValueType::ASCII); AppendU32(result, make.size() + 1); @@ -274,7 +261,7 @@ namespace OpenVulkano::Image } // Model - if (model != "") + if (!model.empty()) { AppendTagAndValueType(result, (uint16_t) IFDTag::MODEL, (uint16_t) IFDValueType::ASCII); AppendU32(result, model.size() + 1); @@ -339,7 +326,7 @@ namespace OpenVulkano::Image } // Software Used - if (softwareUsed != "") + if (!softwareUsed.empty()) { AppendTagAndValueType(result, (uint16_t) IFDTag::SOFTWARE_USED, (uint16_t) IFDValueType::ASCII); AppendU32(result, softwareUsed.size() + 1); @@ -375,12 +362,12 @@ namespace OpenVulkano::Image { const int valueToAdd = resultSize - EXIF_HEADER_SIZE; - if (model != "") + if (!model.empty()) { AddValueToU32AndEndianSwap(result.data() + modelOffset, valueToAdd); } - if (make != "") + if (!make.empty()) { AddValueToU32AndEndianSwap(result.data() + makeOffset, valueToAdd); } From a0cf20b9a83cd75a1d7b8487ca61646804a32dcf Mon Sep 17 00:00:00 2001 From: Vladyslav Baranovskyi Date: Wed, 25 Sep 2024 12:20:33 +0300 Subject: [PATCH 5/9] Proper GPSCoords construction from a single float value, moved ref variables to GPSCoords --- openVulkanoCpp/Image/ExifBuilder.cpp | 43 +++++++++++++++++++++++----- openVulkanoCpp/Image/ExifBuilder.hpp | 7 ++--- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/openVulkanoCpp/Image/ExifBuilder.cpp b/openVulkanoCpp/Image/ExifBuilder.cpp index 1f8eebc..0c5f2f4 100644 --- a/openVulkanoCpp/Image/ExifBuilder.cpp +++ b/openVulkanoCpp/Image/ExifBuilder.cpp @@ -191,11 +191,40 @@ namespace namespace OpenVulkano::Image { - GPSCoords::GPSCoords(int32_t valueForAll) + GPSCoords::GPSCoords(float decimalDegrees, bool isLatitude) { - this->degrees = valueForAll; - this->minutes = valueForAll; - this->seconds = valueForAll; + degrees = static_cast(decimalDegrees); + + float fractionalDegrees = decimalDegrees - degrees; + minutes = static_cast(std::abs(fractionalDegrees) * 60); + + float fractionalMinutes = (std::abs(fractionalDegrees) * 60) - minutes; + seconds = static_cast(fractionalMinutes * 60); + + if (isLatitude) + { + if (decimalDegrees < 0) + { + latitudeRef = LatitudeRef::SOUTH; + } + else + { + latitudeRef = LatitudeRef::NORTH; + } + } + else + { + if (decimalDegrees < 0) + { + longitudeRef = LongitudeRef::WEST; + } + else + { + longitudeRef = LongitudeRef::EAST; + } + } + + degrees = std::abs(degrees); } GPSCoords::GPSCoords(int32_t degrees, int32_t minutes, int32_t seconds) @@ -410,7 +439,7 @@ namespace OpenVulkano::Image // Latitude Ref AppendTagAndValueType(result, 1, (uint16_t) IFDValueType::ASCII); AppendU32(result, 2); // 2 for N/S + \0 - AppendU8(result, latitudeRef == LatitudeRef::NORTH ? 'N' : 'S'); + AppendU8(result, latitude.latitudeRef == LatitudeRef::NORTH ? 'N' : 'S'); AppendU8(result, 0); AppendU8(result, 0); // padding AppendU8(result, 0); // padding @@ -423,7 +452,7 @@ namespace OpenVulkano::Image // Longitude Ref AppendTagAndValueType(result, 3, (uint16_t) IFDValueType::ASCII); AppendU32(result, 2); // 2 for E/W + \0 - AppendU8(result, longitudeRef == LongitudeRef::EAST ? 'E' : 'W'); + AppendU8(result, longitude.longitudeRef == LongitudeRef::EAST ? 'E' : 'W'); AppendU8(result, 0); AppendU8(result, 0); // padding AppendU8(result, 0); // padding @@ -504,7 +533,7 @@ namespace OpenVulkano::Image { auto now = std::chrono::system_clock::now(); std::time_t currentTime = std::chrono::system_clock::to_time_t(now); - std::tm *timeInfo = std::localtime(¤tTime); + std::tm* timeInfo = std::localtime(¤tTime); std::ostringstream oss; oss << std::put_time(timeInfo, "%Y:%m:%d %H:%M:%S"); return oss.str(); diff --git a/openVulkanoCpp/Image/ExifBuilder.hpp b/openVulkanoCpp/Image/ExifBuilder.hpp index 8548dac..4bc238b 100644 --- a/openVulkanoCpp/Image/ExifBuilder.hpp +++ b/openVulkanoCpp/Image/ExifBuilder.hpp @@ -36,8 +36,10 @@ namespace OpenVulkano::Image struct GPSCoords { int32_t degrees, minutes, seconds; + LatitudeRef latitudeRef = LatitudeRef::NORTH; + LongitudeRef longitudeRef = LongitudeRef::EAST; - GPSCoords(int32_t valueForAll = 0); + GPSCoords(float decimalDegrees, bool isLatitude); GPSCoords(int32_t degrees, int32_t minutes, int32_t seconds); }; @@ -54,10 +56,7 @@ namespace OpenVulkano::Image std::string dateTaken; // format: yyyy:mm:dd hh:mm:ss std::string softwareUsed = "OpenVulkano"; - LatitudeRef latitudeRef = LatitudeRef::NORTH; GPSCoords latitude = { 0, 0, 0 }; - - LongitudeRef longitudeRef = LongitudeRef::EAST; GPSCoords longitude = { 0, 0, 0 }; bool altitudeIsAboveSeaLevel = true; From e16ef9cbe12be1f1fdfc3134cdd6e01500445c2d Mon Sep 17 00:00:00 2001 From: Vladyslav Baranovskyi Date: Thu, 26 Sep 2024 13:55:33 +0300 Subject: [PATCH 6/9] Partially builds on linux... --- openVulkanoCpp/Image/ExifBuilder.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openVulkanoCpp/Image/ExifBuilder.cpp b/openVulkanoCpp/Image/ExifBuilder.cpp index 0c5f2f4..24b1cff 100644 --- a/openVulkanoCpp/Image/ExifBuilder.cpp +++ b/openVulkanoCpp/Image/ExifBuilder.cpp @@ -22,7 +22,7 @@ namespace constexpr int TIFF_HEADER_SIZE = 4; constexpr std::array TIFF_HEADER = { 0x4d, 0x4d, 0, 0x2a }; - constexpr bool LITTLE_ENDIAN = std::endian::native == std::endian::little; + constexpr bool IS_LITTLE_ENDIAN = std::endian::native == std::endian::little; enum class IFDTag : uint16_t { @@ -90,7 +90,7 @@ namespace int AppendU16(std::vector& array, uint16_t value) { - if constexpr (LITTLE_ENDIAN) + if constexpr (IS_LITTLE_ENDIAN) { value = ::EndianSwap(value); } @@ -105,7 +105,7 @@ namespace int AppendU32(std::vector& array, uint32_t value) { - if constexpr (LITTLE_ENDIAN) + if constexpr (IS_LITTLE_ENDIAN) { value = ::EndianSwap(value); } @@ -254,8 +254,8 @@ namespace OpenVulkano::Image dateTaken = GetCurrentTimestamp(); } - AppendVector(result, EXIF_HEADER_AND_PADDING); - AppendVector(result, TIFF_HEADER); + AppendVector(result, EXIF_HEADER_AND_PADDING); + AppendVector(result, TIFF_HEADER); int numberOfMainTags = 1; // 1 is for GPS Info tag numberOfMainTags += orientation != 0; From 0ea1c5c7db735069c1199902a3b17d962b349947 Mon Sep 17 00:00:00 2001 From: Vladyslav Baranovskyi Date: Thu, 26 Sep 2024 19:22:22 +0300 Subject: [PATCH 7/9] Introduced IFDGPSTag enum --- openVulkanoCpp/Image/ExifBuilder.cpp | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/openVulkanoCpp/Image/ExifBuilder.cpp b/openVulkanoCpp/Image/ExifBuilder.cpp index 24b1cff..a67d970 100644 --- a/openVulkanoCpp/Image/ExifBuilder.cpp +++ b/openVulkanoCpp/Image/ExifBuilder.cpp @@ -39,6 +39,18 @@ namespace GPS_INFO_OFFSET = 0x8825, }; + enum class IFDGPSTag : uint16_t + { + LATITUDE_REF = 1, + LATITUDE, + LONGITUDE_REF, + LONGITUDE, + ALTITUDE_REF, + ALTITUDE, + TRACK_REF = 14, + TRACK, + }; + enum class IFDValueType { BYTE = 1, @@ -437,7 +449,7 @@ namespace OpenVulkano::Image AppendU16(result, numberOfGPSInfoTags); // Latitude Ref - AppendTagAndValueType(result, 1, (uint16_t) IFDValueType::ASCII); + AppendTagAndValueType(result, (uint16_t) IFDGPSTag::LATITUDE_REF, (uint16_t) IFDValueType::ASCII); AppendU32(result, 2); // 2 for N/S + \0 AppendU8(result, latitude.latitudeRef == LatitudeRef::NORTH ? 'N' : 'S'); AppendU8(result, 0); @@ -445,12 +457,12 @@ namespace OpenVulkano::Image AppendU8(result, 0); // padding // Latitude - AppendTagAndValueType(result, 2, (uint16_t) IFDValueType::RATIONAL); + AppendTagAndValueType(result, (uint16_t) IFDGPSTag::LATITUDE, (uint16_t) IFDValueType::RATIONAL); AppendU32(result, 3); // number of components int latitudeOffset = AppendU32NES(result, 0); // 0 * sizeof(RationalValue) // Longitude Ref - AppendTagAndValueType(result, 3, (uint16_t) IFDValueType::ASCII); + AppendTagAndValueType(result, (uint16_t) IFDGPSTag::LONGITUDE_REF, (uint16_t) IFDValueType::ASCII); AppendU32(result, 2); // 2 for E/W + \0 AppendU8(result, longitude.longitudeRef == LongitudeRef::EAST ? 'E' : 'W'); AppendU8(result, 0); @@ -458,12 +470,12 @@ namespace OpenVulkano::Image AppendU8(result, 0); // padding // Longitude - AppendTagAndValueType(result, 4, (uint16_t) IFDValueType::RATIONAL); + AppendTagAndValueType(result, (uint16_t) IFDGPSTag::LONGITUDE, (uint16_t) IFDValueType::RATIONAL); AppendU32(result, 3); // number of components int longitudeOffset = AppendU32NES(result, 24); // 3 * sizeof(RationalValue) // Altitude Ref - AppendTagAndValueType(result, 5, (uint16_t) IFDValueType::BYTE); + AppendTagAndValueType(result, (uint16_t) IFDGPSTag::ALTITUDE_REF, (uint16_t) IFDValueType::BYTE); AppendU32(result, 1); // number of components AppendU8(result, altitudeIsAboveSeaLevel ? 0 : 1); AppendU8(result, 0); // padding @@ -471,12 +483,12 @@ namespace OpenVulkano::Image AppendU8(result, 0); // padding // Altitude - AppendTagAndValueType(result, 6, (uint16_t) IFDValueType::RATIONAL); + AppendTagAndValueType(result, (uint16_t) IFDGPSTag::ALTITUDE, (uint16_t) IFDValueType::RATIONAL); AppendU32(result, 1); // number of components int altitudeOffset = AppendU32NES(result, 48); // 6 * sizeof(RationalValue) // Track Ref - AppendTagAndValueType(result, 14, (uint16_t) IFDValueType::ASCII); + AppendTagAndValueType(result, (uint16_t) IFDGPSTag::TRACK_REF, (uint16_t) IFDValueType::ASCII); AppendU32(result, 2); // 2 for T/M + \0 AppendU8(result, trackRef == GPSTrackRef::TRUE_NORTH ? 'T' : 'M'); AppendU8(result, 0); @@ -484,7 +496,7 @@ namespace OpenVulkano::Image AppendU8(result, 0); // padding // Track - AppendTagAndValueType(result, 15, (uint16_t) IFDValueType::RATIONAL); + AppendTagAndValueType(result, (uint16_t) IFDGPSTag::TRACK, (uint16_t) IFDValueType::RATIONAL); AppendU32(result, 1); // number of components int trackOffset = AppendU32NES(result, 56); // 7 * sizeof(RationalValue) From 85974069e3b1942a925385c01e9d199d6770b608 Mon Sep 17 00:00:00 2001 From: Vladyslav Baranovskyi Date: Fri, 27 Sep 2024 19:49:23 +0300 Subject: [PATCH 8/9] Huge refactor I did my best... --- openVulkanoCpp/Image/ExifBuilder.cpp | 228 ++++++++++----------------- 1 file changed, 80 insertions(+), 148 deletions(-) diff --git a/openVulkanoCpp/Image/ExifBuilder.cpp b/openVulkanoCpp/Image/ExifBuilder.cpp index a67d970..ed0c005 100644 --- a/openVulkanoCpp/Image/ExifBuilder.cpp +++ b/openVulkanoCpp/Image/ExifBuilder.cpp @@ -199,6 +199,54 @@ namespace *ptr += valueToAdd; *ptr = EndianSwap(*ptr); } + + int AppendTagValueTypeAndString(std::vector& array, IFDTag tag, std::vector &otherArray, const std::string& str) + { + AppendTagAndValueType(array, (uint16_t) tag, (uint16_t) IFDValueType::ASCII); + AppendU32(array, str.size() + 1); + int offset = AppendU32(array, otherArray.size() + 1); + int offsetInData = AppendVector(otherArray, (char*) str.c_str(), str.size() + 1); + uint32_t* ptr = (uint32_t*) (array.data() + offset); + *ptr = offsetInData; + return offset; + } + + int AppendTagValueTypeAndRational(std::vector& array, IFDTag tag, std::vector &otherArray, const OpenVulkano::Image::RationalValue& value) + { + AppendTagAndValueType(array, (uint16_t) tag, (uint16_t) IFDValueType::RATIONAL); + AppendU32(array, 1); // number of components + int offset = AppendU32(array, otherArray.size()); + int offsetInData = AppendRational(otherArray, value); + uint32_t* ptr = (uint32_t*) (array.data() + offset); + *ptr = offsetInData; + return offset; + } + + void AppendTagValueTypeAndShort(std::vector& array, IFDTag tag, uint16_t value) + { + AppendTagAndValueType(array, (uint16_t ) tag, (uint16_t) IFDValueType::SHORT); + AppendU32(array, 1); + AppendU16(array, value); + AppendU16(array, 0); // padding + } + + void AppendTagValueTypeAndByte(std::vector& array, IFDGPSTag tag, IFDValueType valueType, uint16_t byte) + { + AppendTagAndValueType(array, (uint16_t) tag, (uint16_t) valueType); + AppendU32(array, (valueType == IFDValueType::BYTE) ? 1 : 2); // 2 for N/S/E/W + \0, 1 for a single byte + AppendU8(array, byte); + AppendU8(array, 0); // padding + AppendU8(array, 0); // padding + AppendU8(array, 0); // padding + } + + int AppendTagValueTypeAndGPSRational(std::vector& array, IFDGPSTag tag, int numberOfComponents, int initialOffsetValue) + { + AppendTagAndValueType(array, (uint16_t) tag, (uint16_t) IFDValueType::RATIONAL); + AppendU32(array, numberOfComponents); // number of components + int offset = AppendU32NES(array, initialOffsetValue); + return offset; + } } namespace OpenVulkano::Image @@ -283,163 +331,89 @@ namespace OpenVulkano::Image AppendU32(result, 8); // Append offset to the ifd AppendU16(result, numberOfMainTags); - // offsets in result array where the offset to the data should be stored - int makeOffset = 0; - int modelOffset = 0; - int dateTakenOffset = 0; - int softwareUsedOffset = 0; + std::vector offsets; int gpsInfoOffset = 0; // Make if (!make.empty()) { - AppendTagAndValueType(result, (uint16_t) IFDTag::MAKE, (uint16_t) IFDValueType::ASCII); - AppendU32(result, make.size() + 1); - makeOffset = AppendU32(result, data.size() + 1); - int offsetInData = AppendVector(data, (char *)make.c_str(), make.size() + 1); - uint32_t* ptr = (uint32_t *)(result.data() + makeOffset); - *ptr = offsetInData; + offsets.push_back(AppendTagValueTypeAndString(result, IFDTag::MAKE, data, make)); } // Model if (!model.empty()) { - AppendTagAndValueType(result, (uint16_t) IFDTag::MODEL, (uint16_t) IFDValueType::ASCII); - AppendU32(result, model.size() + 1); - modelOffset = AppendU32(result, data.size() + 1); - int offsetInData = AppendVector(data, (char *)model.c_str(), model.size() + 1); - uint32_t* ptr = (uint32_t *)(result.data() + modelOffset); - *ptr = offsetInData; + offsets.push_back(AppendTagValueTypeAndString(result, IFDTag::MODEL, data, model)); } // Orientation if (orientation != 0) { - AppendTagAndValueType(result, (uint16_t) IFDTag::ORIENTATION, (uint16_t) IFDValueType::SHORT); - AppendU32(result, 1); - AppendU16(result, (uint16_t)orientation); - AppendU16(result, 0); // padding + AppendTagValueTypeAndShort(result, IFDTag::ORIENTATION, orientation); } // xResolution - int xResolutionOffset = 0; if (xResolution.nominator || xResolution.denominator) { - AppendTagAndValueType(result, (uint16_t) IFDTag::X_RESOLUTION, (uint16_t) IFDValueType::RATIONAL); - AppendU32(result, 1); // number of components - xResolutionOffset = AppendU32(result, data.size()); - int offsetInData = AppendRational(data, xResolution); - uint32_t* ptr = (uint32_t *)(result.data() + xResolutionOffset); - *ptr = offsetInData; + offsets.push_back(AppendTagValueTypeAndRational(result, IFDTag::X_RESOLUTION, data, xResolution)); } // yResolution - int yResolutionOffset = 0; if (yResolution.nominator || yResolution.denominator) { - AppendTagAndValueType(result, (uint16_t) IFDTag::Y_RESOLUTION, (uint16_t) IFDValueType::RATIONAL); - AppendU32(result, 1); // number of components - yResolutionOffset = AppendU32(result, data.size()); - int offsetInData = AppendRational(data, yResolution); - uint32_t* ptr = (uint32_t *)(result.data() + yResolutionOffset); - *ptr = offsetInData; + offsets.push_back(AppendTagValueTypeAndRational(result, IFDTag::Y_RESOLUTION, data, yResolution)); } // Exposure Time - int exposureTimeOffset = 0; if (exposureTime.nominator || exposureTime.denominator) { - AppendTagAndValueType(result, (uint16_t) IFDTag::EXPOSURE_TIME, (uint16_t) IFDValueType::RATIONAL); - AppendU32(result, 1); // number of components - exposureTimeOffset = AppendU32(result, data.size()); - int offsetInData = AppendRational(data, exposureTime); - uint32_t* ptr = (uint32_t *)(result.data() + exposureTimeOffset); - *ptr = offsetInData; + offsets.push_back(AppendTagValueTypeAndRational(result, IFDTag::EXPOSURE_TIME, data, exposureTime)); } // ResolutionUnit if (resolutionUnit != 0) { - AppendTagAndValueType(result, (uint16_t) IFDTag::RESOLUTION_UNIT, (uint16_t) IFDValueType::SHORT); - AppendU32(result, 1); // number of components - AppendU16(result, resolutionUnit); - AppendU16(result, 0); // padding + AppendTagValueTypeAndShort(result, IFDTag::RESOLUTION_UNIT, resolutionUnit); } // Software Used if (!softwareUsed.empty()) { - AppendTagAndValueType(result, (uint16_t) IFDTag::SOFTWARE_USED, (uint16_t) IFDValueType::ASCII); - AppendU32(result, softwareUsed.size() + 1); - softwareUsedOffset = AppendU32(result, data.size() + 1); - int offsetInData = AppendVector(data, (char *)softwareUsed.c_str(), softwareUsed.size() + 1); - uint32_t* ptr = (uint32_t *)(result.data() + softwareUsedOffset); - *ptr = offsetInData; + offsets.push_back(AppendTagValueTypeAndString(result, IFDTag::SOFTWARE_USED, data, softwareUsed)); } // Date Taken // NOTE(vb): For some reason windows file properties doesn't print date taken field! // Even though other software does provide this information without a problem { - AppendTagAndValueType(result, (uint16_t) IFDTag::DATE_TAKEN, (uint16_t) IFDValueType::ASCII); - AppendU32(result, dateTaken.size() + 1); - dateTakenOffset = AppendU32(result, data.size() + 1); - int offsetInData = AppendVector(data, (char *)dateTaken.c_str(), dateTaken.size() + 1); - uint32_t* ptr = (uint32_t *)(result.data() + dateTakenOffset); - *ptr = offsetInData; + 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); // to be filled + { + 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); int resultSize = result.size(); AppendVector(result, data); - int ifdAndSubdataSize = result.size(); + // Resolve offsets { const int valueToAdd = resultSize - EXIF_HEADER_SIZE; - if (!model.empty()) + for (const auto& offset: offsets) { - AddValueToU32AndEndianSwap(result.data() + modelOffset, valueToAdd); - } - - if (!make.empty()) - { - AddValueToU32AndEndianSwap(result.data() + makeOffset, valueToAdd); - } - - if (xResolutionOffset) - { - AddValueToU32AndEndianSwap(result.data() + xResolutionOffset, valueToAdd); - } - - if (yResolutionOffset) - { - AddValueToU32AndEndianSwap(result.data() + yResolutionOffset, valueToAdd); - } - - if (exposureTimeOffset) - { - AddValueToU32AndEndianSwap(result.data() + exposureTimeOffset, valueToAdd); - } - - if (dateTakenOffset) - { - AddValueToU32AndEndianSwap(result.data() + dateTakenOffset, valueToAdd); - } - - if (softwareUsedOffset) - { - AddValueToU32AndEndianSwap(result.data() + softwareUsedOffset, valueToAdd); + AddValueToU32AndEndianSwap(result.data() + offset, valueToAdd); } } + // Resolve GPS info offset { + int ifdAndSubdataSize = result.size(); uint32_t *ptr = (uint32_t *) (result.data() + gpsInfoOffset); *ptr = EndianSwap((uint32_t)(ifdAndSubdataSize - EXIF_HEADER_SIZE)); } @@ -447,82 +421,40 @@ namespace OpenVulkano::Image // Writing GPS Info structure int numberOfGPSInfoTags = 8; AppendU16(result, numberOfGPSInfoTags); + offsets.resize(0); // Latitude Ref - AppendTagAndValueType(result, (uint16_t) IFDGPSTag::LATITUDE_REF, (uint16_t) IFDValueType::ASCII); - AppendU32(result, 2); // 2 for N/S + \0 - AppendU8(result, latitude.latitudeRef == LatitudeRef::NORTH ? 'N' : 'S'); - AppendU8(result, 0); - AppendU8(result, 0); // padding - AppendU8(result, 0); // padding + AppendTagValueTypeAndByte(result, IFDGPSTag::LATITUDE_REF, IFDValueType::ASCII, (latitude.latitudeRef == LatitudeRef::NORTH) ? 'N' : 'S'); // Latitude - AppendTagAndValueType(result, (uint16_t) IFDGPSTag::LATITUDE, (uint16_t) IFDValueType::RATIONAL); - AppendU32(result, 3); // number of components - int latitudeOffset = AppendU32NES(result, 0); // 0 * sizeof(RationalValue) + offsets.push_back(AppendTagValueTypeAndGPSRational(result, IFDGPSTag::LATITUDE, 3, 0)); // 0 * sizeof(RationalValue) // Longitude Ref - AppendTagAndValueType(result, (uint16_t) IFDGPSTag::LONGITUDE_REF, (uint16_t) IFDValueType::ASCII); - AppendU32(result, 2); // 2 for E/W + \0 - AppendU8(result, longitude.longitudeRef == LongitudeRef::EAST ? 'E' : 'W'); - AppendU8(result, 0); - AppendU8(result, 0); // padding - AppendU8(result, 0); // padding + AppendTagValueTypeAndByte(result, IFDGPSTag::LONGITUDE_REF, IFDValueType::ASCII, (longitude.longitudeRef == LongitudeRef::EAST) ? 'E' : 'W'); // Longitude - AppendTagAndValueType(result, (uint16_t) IFDGPSTag::LONGITUDE, (uint16_t) IFDValueType::RATIONAL); - AppendU32(result, 3); // number of components - int longitudeOffset = AppendU32NES(result, 24); // 3 * sizeof(RationalValue) + offsets.push_back(AppendTagValueTypeAndGPSRational(result, IFDGPSTag::LONGITUDE, 3, 24)); // 3 * sizeof(RationalValue) // Altitude Ref - AppendTagAndValueType(result, (uint16_t) IFDGPSTag::ALTITUDE_REF, (uint16_t) IFDValueType::BYTE); - AppendU32(result, 1); // number of components - AppendU8(result, altitudeIsAboveSeaLevel ? 0 : 1); - AppendU8(result, 0); // padding - AppendU8(result, 0); // padding - AppendU8(result, 0); // padding + AppendTagValueTypeAndByte(result, IFDGPSTag::ALTITUDE_REF, IFDValueType::BYTE, altitudeIsAboveSeaLevel ? 0 : 1); // Altitude - AppendTagAndValueType(result, (uint16_t) IFDGPSTag::ALTITUDE, (uint16_t) IFDValueType::RATIONAL); - AppendU32(result, 1); // number of components - int altitudeOffset = AppendU32NES(result, 48); // 6 * sizeof(RationalValue) + offsets.push_back(AppendTagValueTypeAndGPSRational(result, IFDGPSTag::ALTITUDE, 1, 48)); // 6 * sizeof(RationalValue) // Track Ref - AppendTagAndValueType(result, (uint16_t) IFDGPSTag::TRACK_REF, (uint16_t) IFDValueType::ASCII); - AppendU32(result, 2); // 2 for T/M + \0 - AppendU8(result, trackRef == GPSTrackRef::TRUE_NORTH ? 'T' : 'M'); - AppendU8(result, 0); - AppendU8(result, 0); // padding - AppendU8(result, 0); // padding + AppendTagValueTypeAndByte(result, IFDGPSTag::TRACK_REF, IFDValueType::ASCII, (trackRef == GPSTrackRef::TRUE_NORTH) ? 'T' : 'M'); // Track - AppendTagAndValueType(result, (uint16_t) IFDGPSTag::TRACK, (uint16_t) IFDValueType::RATIONAL); - AppendU32(result, 1); // number of components - int trackOffset = AppendU32NES(result, 56); // 7 * sizeof(RationalValue) + offsets.push_back(AppendTagValueTypeAndGPSRational(result, IFDGPSTag::TRACK, 1, 56)); // 7 * sizeof(RationalValue) // { int sizeOfResultSoFar = result.size(); const int valueToAdd = sizeOfResultSoFar - EXIF_HEADER_SIZE; - // Latitude + for(const auto &offset : offsets) { - AddValueToU32AndEndianSwap(result.data() + latitudeOffset, valueToAdd); - } - - // Longitude - { - AddValueToU32AndEndianSwap(result.data() + longitudeOffset, valueToAdd); - } - - // Altitude - { - AddValueToU32AndEndianSwap(result.data() + altitudeOffset, valueToAdd); - } - - // Track - { - AddValueToU32AndEndianSwap(result.data() + trackOffset, valueToAdd); + AddValueToU32AndEndianSwap(result.data() + offset, valueToAdd); } } @@ -534,7 +466,7 @@ namespace OpenVulkano::Image AppendU32(result, altitude); AppendU32(result, 1); // denominator for altitude - int const TRACK_PRECISION = 10000; + const int TRACK_PRECISION = 10000; AppendU32(result, track * TRACK_PRECISION); AppendU32(result, TRACK_PRECISION); From 61043119df5c51a2895cb1746e22a31438cf0554 Mon Sep 17 00:00:00 2001 From: Vladyslav Baranovskyi Date: Mon, 30 Sep 2024 15:01:00 +0300 Subject: [PATCH 9/9] Using std::variant, changed EndianSwap, new function StringFromTime, minor tweaks --- openVulkanoCpp/Image/ExifBuilder.cpp | 123 +++++++++++++-------------- openVulkanoCpp/Image/ExifBuilder.hpp | 18 ++-- 2 files changed, 69 insertions(+), 72 deletions(-) diff --git a/openVulkanoCpp/Image/ExifBuilder.cpp b/openVulkanoCpp/Image/ExifBuilder.cpp index ed0c005..1ec4361 100644 --- a/openVulkanoCpp/Image/ExifBuilder.cpp +++ b/openVulkanoCpp/Image/ExifBuilder.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -60,23 +59,13 @@ namespace RATIONAL = 5, }; - uint32_t EndianSwap(uint32_t value) + template + T EndianSwap(T value) { - uint32_t result; + T result; - char *ptr = (char *) &value; - std::reverse(ptr, ptr + 4); - result = value; - - return result; - } - - uint16_t EndianSwap(uint16_t value) - { - uint16_t result; - - char *ptr = (char *) &value; - std::reverse(ptr, ptr + 2); + char* ptr = reinterpret_cast(&value); + std::reverse(ptr, ptr + sizeof(T)); result = value; return result; @@ -247,6 +236,26 @@ namespace int offset = AppendU32NES(array, initialOffsetValue); return offset; } + + char CoordRefToChar(const std::variant& ref) + { + char c = 0; + if (std::holds_alternative(ref)) + { + auto lat = std::get(ref); + c = (lat == OpenVulkano::Image::LatitudeRef::NORTH ? 'N' : 'S'); + } + else if (std::holds_alternative(ref)) + { + auto lon = std::get(ref); + c = (lon == OpenVulkano::Image::LongitudeRef::EAST ? 'E' : 'W'); + } + else + { + throw std::runtime_error("An alternative does not contain neither LatitudeRef nor LongitudeRef!"); + } + return c; + } } namespace OpenVulkano::Image @@ -263,45 +272,35 @@ namespace OpenVulkano::Image if (isLatitude) { - if (decimalDegrees < 0) - { - latitudeRef = LatitudeRef::SOUTH; - } - else - { - latitudeRef = LatitudeRef::NORTH; - } + ref = (decimalDegrees < 0) ? LatitudeRef::SOUTH : LatitudeRef::NORTH; } else { - if (decimalDegrees < 0) - { - longitudeRef = LongitudeRef::WEST; - } - else - { - longitudeRef = LongitudeRef::EAST; - } + ref = (decimalDegrees < 0) ? LongitudeRef::WEST : LongitudeRef::EAST; } degrees = std::abs(degrees); } - GPSCoords::GPSCoords(int32_t degrees, int32_t minutes, int32_t seconds) + GPSCoords::GPSCoords(int32_t degrees, int32_t minutes, int32_t seconds, LatitudeRef ref) + : degrees(degrees), minutes(minutes), seconds(seconds), ref(ref) + { + } + + GPSCoords::GPSCoords(int32_t degrees, int32_t minutes, int32_t seconds, LongitudeRef ref) + : degrees(degrees), minutes(minutes), seconds(seconds), ref(ref) { - this->degrees = degrees; - this->minutes = minutes; - this->seconds = seconds; } void ExifBuilder::SetAltitude(float level) { altitudeIsAboveSeaLevel = level >= 0; - if (level < 0) - { - level = -level; - } - altitude = level; + altitude = std::abs(level); + } + + void ExifBuilder::SetTime(std::time_t timestamp) + { + dateTaken = StringFromTime(timestamp); } std::vector ExifBuilder::Build() @@ -334,67 +333,54 @@ namespace OpenVulkano::Image std::vector offsets; int gpsInfoOffset = 0; - // Make if (!make.empty()) { offsets.push_back(AppendTagValueTypeAndString(result, IFDTag::MAKE, data, make)); } - // Model if (!model.empty()) { offsets.push_back(AppendTagValueTypeAndString(result, IFDTag::MODEL, data, model)); } - // Orientation if (orientation != 0) { AppendTagValueTypeAndShort(result, IFDTag::ORIENTATION, orientation); } - // xResolution if (xResolution.nominator || xResolution.denominator) { offsets.push_back(AppendTagValueTypeAndRational(result, IFDTag::X_RESOLUTION, data, xResolution)); } - // yResolution if (yResolution.nominator || yResolution.denominator) { offsets.push_back(AppendTagValueTypeAndRational(result, IFDTag::Y_RESOLUTION, data, yResolution)); } - // Exposure Time if (exposureTime.nominator || exposureTime.denominator) { offsets.push_back(AppendTagValueTypeAndRational(result, IFDTag::EXPOSURE_TIME, data, exposureTime)); } - // ResolutionUnit if (resolutionUnit != 0) { AppendTagValueTypeAndShort(result, IFDTag::RESOLUTION_UNIT, resolutionUnit); } - // Software Used if (!softwareUsed.empty()) { offsets.push_back(AppendTagValueTypeAndString(result, IFDTag::SOFTWARE_USED, data, softwareUsed)); } - // Date Taken // NOTE(vb): For some reason windows file properties doesn't print date taken field! // Even though other software does provide this information without a problem - { - offsets.push_back(AppendTagValueTypeAndString(result, IFDTag::DATE_TAKEN, data, dateTaken)); - } + 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); - } + 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); @@ -424,13 +410,15 @@ namespace OpenVulkano::Image offsets.resize(0); // Latitude Ref - AppendTagValueTypeAndByte(result, IFDGPSTag::LATITUDE_REF, IFDValueType::ASCII, (latitude.latitudeRef == LatitudeRef::NORTH) ? 'N' : 'S'); + char latitudeRef = CoordRefToChar(latitude.ref); + AppendTagValueTypeAndByte(result, IFDGPSTag::LATITUDE_REF, IFDValueType::ASCII, latitudeRef); // Latitude offsets.push_back(AppendTagValueTypeAndGPSRational(result, IFDGPSTag::LATITUDE, 3, 0)); // 0 * sizeof(RationalValue) // Longitude Ref - AppendTagValueTypeAndByte(result, IFDGPSTag::LONGITUDE_REF, IFDValueType::ASCII, (longitude.longitudeRef == LongitudeRef::EAST) ? 'E' : 'W'); + char longitudeRef = CoordRefToChar(longitude.ref); + AppendTagValueTypeAndByte(result, IFDGPSTag::LONGITUDE_REF, IFDValueType::ASCII, longitudeRef); // Longitude offsets.push_back(AppendTagValueTypeAndGPSRational(result, IFDGPSTag::LONGITUDE, 3, 24)); // 3 * sizeof(RationalValue) @@ -466,20 +454,25 @@ namespace OpenVulkano::Image AppendU32(result, altitude); AppendU32(result, 1); // denominator for altitude - const int TRACK_PRECISION = 10000; + constexpr int TRACK_PRECISION = 10000; AppendU32(result, track * TRACK_PRECISION); AppendU32(result, TRACK_PRECISION); return result; } - std::string ExifBuilder::GetCurrentTimestamp() + std::string ExifBuilder::StringFromTime(std::time_t time) { - auto now = std::chrono::system_clock::now(); - std::time_t currentTime = std::chrono::system_clock::to_time_t(now); - std::tm* timeInfo = std::localtime(¤tTime); + std::tm* timeInfo = std::localtime(&time); std::ostringstream oss; oss << std::put_time(timeInfo, "%Y:%m:%d %H:%M:%S"); return oss.str(); } + + std::string ExifBuilder::GetCurrentTimestamp() + { + auto now = std::chrono::system_clock::now(); + 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 4bc238b..2f13771 100644 --- a/openVulkanoCpp/Image/ExifBuilder.hpp +++ b/openVulkanoCpp/Image/ExifBuilder.hpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include namespace OpenVulkano::Image { @@ -36,11 +38,11 @@ namespace OpenVulkano::Image struct GPSCoords { int32_t degrees, minutes, seconds; - LatitudeRef latitudeRef = LatitudeRef::NORTH; - LongitudeRef longitudeRef = LongitudeRef::EAST; + std::variant ref; GPSCoords(float decimalDegrees, bool isLatitude); - GPSCoords(int32_t degrees, int32_t minutes, int32_t seconds); + GPSCoords(int32_t degrees, int32_t minutes, int32_t seconds, LatitudeRef ref); + GPSCoords(int32_t degrees, int32_t minutes, int32_t seconds, LongitudeRef ref); }; class ExifBuilder @@ -56,8 +58,8 @@ namespace OpenVulkano::Image std::string dateTaken; // format: yyyy:mm:dd hh:mm:ss std::string softwareUsed = "OpenVulkano"; - GPSCoords latitude = { 0, 0, 0 }; - GPSCoords longitude = { 0, 0, 0 }; + GPSCoords latitude = { 0, 0, 0, LatitudeRef::NORTH }; + GPSCoords longitude = { 0, 0, 0, LongitudeRef::EAST }; bool altitudeIsAboveSeaLevel = true; uint32_t altitude = 0; @@ -67,8 +69,10 @@ namespace OpenVulkano::Image void SetAltitude(float level); + void SetTime(std::time_t timestamp); // Typical usage is -> jpeg_write_marker(cinfo, JPEG_APP0 + 1, exif_data.data(), exif_data.size()); - std::vector Build(); - static std::string GetCurrentTimestamp(); + [[nodiscard]] std::vector Build(); + [[nodiscard]] static std::string StringFromTime(std::time_t time); + [[nodiscard]] static std::string GetCurrentTimestamp(); }; } \ No newline at end of file