/* * 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 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(); 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 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); *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, 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, 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 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(model != "") { uint32_t *ptr = (uint32_t *) (result.data() + modelOffset); *ptr += resultSize - EXIF_HEADER_SIZE; *ptr = endianSwap(*ptr); } 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); *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 = 8; 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) // 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 { 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); } // 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; } }