/* * 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" #include #include #include #include #include #include #include #include namespace { constexpr int EXIF_HEADER_SIZE = 6; constexpr std::array EXIF_HEADER_AND_PADDING = { 'E', 'x', 'i', 'f', 0, 0 }; 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; enum class IFDTag : uint16_t { END = 0, MAKE = 0x010f, MODEL = 0x0110, ORIENTATION = 0x0112, X_RESOLUTION = 0x011a, Y_RESOLUTION = 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; 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); result = value; return result; } template int Append(std::vector& array, T value) { int offset = array.size(); 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) { if constexpr (LITTLE_ENDIAN) { value = ::EndianSwap(value); } return Append(array, value); } // no endian swap int AppendU32NES(std::vector& array, uint32_t value) { return Append(array, value); } int AppendU32(std::vector& array, uint32_t value) { if constexpr (LITTLE_ENDIAN) { value = ::EndianSwap(value); } return Append(array, value); } template int AppendVector(std::vector& array, const std::array& values) { int offset = array.size(); for (auto value: values) { array.push_back(value); } return offset; } int AppendVector(std::vector& array, const std::vector& values) { int offset = array.size(); 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 offset; } int AppendRational(std::vector& array, const OpenVulkano::Image::RationalValue& rational) { int offset = array.size(); AppendU32(array, rational.nominator); AppendU32(array, rational.denominator); return offset; } int AppendGPSCoords(std::vector& array, const OpenVulkano::Image::GPSCoords& coords) { int offset = array.size(); AppendU32(array, coords.degrees); AppendU32(array, 1); AppendU32(array, coords.minutes); AppendU32(array, 1); AppendU32(array, coords.seconds); AppendU32(array, 1); 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 { GPSCoords::GPSCoords(float decimalDegrees, bool isLatitude) { 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) { 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 and rational values if (dateTaken.empty()) { 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 += resolutionUnit != 0; numberOfMainTags += exposureTime.nominator || exposureTime.denominator; numberOfMainTags += softwareUsed != ""; numberOfMainTags += dateTaken != ""; 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; 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; } // 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; } // 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 } // 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; } // 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; } // 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; } // 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 } // 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; } // 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; } // 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 // next ifd offset AppendU32(result, 0); int resultSize = result.size(); AppendVector(result, data); int ifdAndSubdataSize = result.size(); { const int valueToAdd = resultSize - EXIF_HEADER_SIZE; if (!model.empty()) { 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); } } { 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); // Latitude Ref AppendTagAndValueType(result, 1, (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 // Latitude AppendTagAndValueType(result, 2, (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); 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 // Longitude AppendTagAndValueType(result, 4, (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); AppendU32(result, 1); // number of components AppendU8(result, altitudeIsAboveSeaLevel ? 0 : 1); AppendU8(result, 0); // padding AppendU8(result, 0); // padding AppendU8(result, 0); // padding // Altitude AppendTagAndValueType(result, 6, (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); 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 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(); const int valueToAdd = sizeOfResultSoFar - EXIF_HEADER_SIZE; // Latitude { AddValueToU32AndEndianSwap(result.data() + latitudeOffset, valueToAdd); } // Longitude { AddValueToU32AndEndianSwap(result.data() + longitudeOffset, valueToAdd); } // Altitude { AddValueToU32AndEndianSwap(result.data() + altitudeOffset, valueToAdd); } // Track { AddValueToU32AndEndianSwap(result.data() + trackOffset, valueToAdd); } } // AppendGPSCoords(result, latitude); AppendGPSCoords(result, longitude); AppendU32(result, altitude); AppendU32(result, 1); // denominator for altitude int const TRACK_PRECISION = 10000; 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(); } }