From c28e1adc4dd78eb7b2e5239462d969b8272359ac Mon Sep 17 00:00:00 2001 From: Vladyslav Baranovskyi Date: Tue, 17 Sep 2024 14:41:31 +0300 Subject: [PATCH] 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