|
4 | 4 | #include <string> |
5 | 5 | #include <variant> |
6 | 6 |
|
| 7 | +//////////////////////////////////////////////////////////////////////////////////// |
| 8 | +// ExifTag structure: |
| 9 | +// |
| 10 | +// - tag: The tag ID (e.g., 0x010F for Manufacturer) |
| 11 | +// |
| 12 | +// - type: |
| 13 | +// 0x0001 - BYTE (8-bit unsigned integer) |
| 14 | +// 0x0002 - ASCII (8-bit byte containing one 7-bit ASCII code) |
| 15 | +// 0x0003 - SHORT (16-bit unsigned integer) |
| 16 | +// 0x0004 - LONG (32-bit unsigned integer) |
| 17 | +// 0x0005 - RATIONAL (Two LONGs: numerator and denominator) |
| 18 | +// 0x0007 - UNDEFINED (8-bit byte that can take any value depending on the field definition) |
| 19 | +// 0x0009 - SLONG (32-bit signed integer) |
| 20 | +// 0x000A - SRATIONAL (Two SLONGs: numerator and denominator) |
| 21 | +// |
7 | 22 | struct ExifTag { |
8 | | - uint16_t tag; // Tag ID |
9 | | - uint16_t type; // Data type (e.g., ASCII, BYTE, SHORT, LONG, RATIONAL) |
10 | | - uint32_t count; // Number of data items |
11 | | - std::variant<uint32_t, std::string, std::vector<uint8_t>> value; // Either a direct value, a string, or raw data |
| 23 | + uint16_t tag; |
| 24 | + uint16_t type; |
| 25 | + uint32_t count; |
| 26 | + std::vector<uint8_t> value; |
12 | 27 |
|
13 | | - ExifTag(uint16_t tag, uint16_t type, uint32_t count, uint32_t value) |
14 | | - : tag(tag), type(type), count(count), value(value) {} |
| 28 | + // Constructor for 8-bit integer values (BYTE) |
| 29 | + ExifTag(uint16_t t, uint16_t tp, uint32_t cnt, uint8_t val) |
| 30 | + : tag(t), type(tp), count(cnt), value({ val }) {} |
15 | 31 |
|
16 | | - ExifTag(uint16_t tag, uint16_t type, const std::string& value) |
17 | | - : tag(tag), type(type), count(value.size() + 1), value(value) {} // +1 for null terminator |
| 32 | + // Constructor for 16-bit integer values (SHORT) |
| 33 | + ExifTag(uint16_t t, uint16_t tp, uint32_t cnt, uint16_t val) |
| 34 | + : tag(t), type(tp), count(cnt), value(2) { |
| 35 | + std::memcpy(value.data(), &val, 2); |
| 36 | + } |
18 | 37 |
|
19 | | - ExifTag(uint16_t tag, uint16_t type, const std::vector<uint8_t>& value) |
20 | | - : tag(tag), type(type), count(value.size()), value(value) {} |
| 38 | + // Constructor for 32-bit integer values (LONG) |
| 39 | + ExifTag(uint16_t t, uint16_t tp, uint32_t cnt, uint32_t val) |
| 40 | + : tag(t), type(tp), count(cnt), value(4) { |
| 41 | + std::memcpy(value.data(), &val, 4); |
| 42 | + } |
| 43 | + |
| 44 | + // Constructor for RATIONAL (Two 32-bit integers: numerator and denominator) |
| 45 | + ExifTag(uint16_t t, uint16_t tp, uint32_t cnt, uint32_t num, uint32_t denom) |
| 46 | + : tag(t), type(tp), count(cnt), value(8) { |
| 47 | + std::memcpy(value.data(), &num, 4); |
| 48 | + std::memcpy(value.data() + 4, &denom, 4); |
| 49 | + } |
| 50 | + |
| 51 | + // Constructor for string values, copying the string into the vector |
| 52 | + ExifTag(uint16_t t, uint16_t tp, const std::string& val) |
| 53 | + : tag(t), type(tp), count(static_cast<uint32_t>(val.size() + 1)), value(val.begin(), val.end()) { |
| 54 | + value.push_back('\0'); // Null-terminate the string |
| 55 | + } |
| 56 | +}; |
| 57 | + |
| 58 | +// ExifBuilder class |
| 59 | +class ExifBuilder { |
| 60 | +private: |
| 61 | + std::vector<ExifTag> tags; // List of EXIF tags |
| 62 | + std::vector<uint8_t> extraData; // Buffer for extra data (strings, RATIONALs, etc.) |
| 63 | + |
| 64 | +public: |
| 65 | + void addTag(ExifTag&& tag) { |
| 66 | + tags.push_back(std::move(tag)); |
| 67 | + } |
| 68 | + |
| 69 | + std::vector<uint8_t> buildExifBlob() { |
| 70 | + std::vector<uint8_t> exifBlob; |
| 71 | + |
| 72 | + // Placeholder for APP1 header (to be filled in later) |
| 73 | + exifBlob.insert(exifBlob.end(), { 0xFF, 0xE1, 0x00, 0x00 }); // APP1 marker, length placeholder |
| 74 | + exifBlob.insert(exifBlob.end(), { 'E', 'x', 'i', 'f', 0x00, 0x00 }); // "Exif" identifier and padding |
| 75 | + |
| 76 | + // Write TIFF Header |
| 77 | + bool bigendian = true; |
| 78 | + appendUInt16(exifBlob, bigendian ? 0x4D4D : 0x4949); // Big-endian indicator |
| 79 | + appendUInt16(exifBlob, bigendian ? 0x002A : 0x2000); // TIFF version |
| 80 | + appendUInt32(exifBlob, bigendian ? 0x00000008 : 0x08000000); // Offset to the first IFD |
| 81 | + |
| 82 | + // Number of directory entries |
| 83 | + appendUInt16(exifBlob, static_cast<uint16_t>(tags.size())); |
| 84 | + |
| 85 | + // Calculate data offset (just after IFD entries and next IFD offset) |
| 86 | + size_t dataOffset = 8 + 2 + (tags.size() * 12) + 4; |
| 87 | + |
| 88 | + // Process each tag |
| 89 | + for (auto& tag : tags) { |
| 90 | + appendUInt16(exifBlob, tag.tag); |
| 91 | + appendUInt16(exifBlob, tag.type); |
| 92 | + appendUInt32(exifBlob, tag.count); |
| 93 | + |
| 94 | + if (tagFitsInField(tag)) { |
| 95 | + writeTagValue(exifBlob, tag); // Write values directly as is |
| 96 | + } |
| 97 | + else { |
| 98 | + appendUInt32(exifBlob, static_cast<uint32_t>(dataOffset)); |
| 99 | + appendExtraData(tag, dataOffset); |
| 100 | + } |
| 101 | + } |
| 102 | + |
| 103 | + // Write the next IFD offset (0 indicates no more IFDs) |
| 104 | + appendUInt32(exifBlob, 0); |
| 105 | + |
| 106 | + // Append the extra data (strings, RATIONALs, etc.) |
| 107 | + exifBlob.insert(exifBlob.end(), extraData.begin(), extraData.end()); |
| 108 | + |
| 109 | + // Update the APP1 segment length |
| 110 | + uint16_t exifLength = static_cast<uint16_t>(exifBlob.size() - 2); // Length excluding the APP1 marker (FF E1) |
| 111 | + exifBlob[2] = (exifLength >> 8) & 0xFF; |
| 112 | + exifBlob[3] = exifLength & 0xFF; |
| 113 | + |
| 114 | + return exifBlob; |
| 115 | + } |
| 116 | + |
| 117 | +private: |
| 118 | + // Corrected function to append a 16-bit integer in big-endian format to a vector |
| 119 | + static void appendUInt16(std::vector<uint8_t>& vec, uint16_t value, bool bigendian = true) { |
| 120 | + if (bigendian) { |
| 121 | + vec.push_back((value >> 8) & 0xFF); |
| 122 | + vec.push_back(value & 0xFF); |
| 123 | + } |
| 124 | + else { |
| 125 | + vec.push_back(value & 0xFF); |
| 126 | + vec.push_back((value >> 8) & 0xFF); |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | + // Corrected function to append a 32-bit integer in big-endian format to a vector |
| 131 | + static void appendUInt32(std::vector<uint8_t>& vec, uint32_t value, bool bigendian = true) { |
| 132 | + if (bigendian) { |
| 133 | + vec.push_back((value >> 24) & 0xFF); |
| 134 | + vec.push_back((value >> 16) & 0xFF); |
| 135 | + vec.push_back((value >> 8) & 0xFF); |
| 136 | + vec.push_back(value & 0xFF); |
| 137 | + } else { |
| 138 | + vec.push_back(value & 0xFF); |
| 139 | + vec.push_back((value >> 8) & 0xFF); |
| 140 | + vec.push_back((value >> 16) & 0xFF); |
| 141 | + vec.push_back((value >> 24) & 0xFF); |
| 142 | + } |
| 143 | + } |
| 144 | + |
| 145 | + void writeTagValue(std::vector<uint8_t>& buffer, const ExifTag& tag, bool bigendian = true) { |
| 146 | + // byte order alwas from left to the right. |
| 147 | + // in case of SHORT, added a padding 0 byte to the right. |
| 148 | + // in case of less 4-bytes STRING, added a padding 0 byte to the right, |
| 149 | + // otherwise use an offset to the extra data. |
| 150 | + // big endian similar to the standard writing, little endian inverted (intel/x86/x64). |
| 151 | + size_t bufSize = buffer.size(); |
| 152 | + switch (tag.type) { |
| 153 | + case 0x0001: // BYTE |
| 154 | + buffer.resize(bufSize + 4, 0); |
| 155 | + buffer[bufSize] = tag.value[0]; |
| 156 | + break; |
| 157 | + case 0x0003: // SHORT |
| 158 | + buffer.resize(bufSize + 4, 0); |
| 159 | + buffer[bufSize + (bigendian ? 1 : 0)] = tag.value[0]; |
| 160 | + buffer[bufSize + (bigendian ? 0 : 1)] = tag.value[1]; |
| 161 | + break; |
| 162 | + case 0x0004: // LONG |
| 163 | + buffer.resize(bufSize + 4, 0); |
| 164 | + buffer[bufSize + (bigendian ? 3 : 0)] = tag.value[0]; |
| 165 | + buffer[bufSize + (bigendian ? 2 : 1)] = tag.value[1]; |
| 166 | + buffer[bufSize + (bigendian ? 1 : 2)] = tag.value[2]; |
| 167 | + buffer[bufSize + (bigendian ? 0 : 3)] = tag.value[3]; |
| 168 | + break; |
| 169 | + case 0x0002: // ASCII |
| 170 | + buffer.resize(bufSize + tag.value.size(), 0); |
| 171 | + std::copy(tag.value.begin(), tag.value.end(), buffer.begin() + bufSize); |
| 172 | + break; |
| 173 | + } |
| 174 | + } |
| 175 | + |
| 176 | + bool tagFitsInField(const ExifTag& tag) const { |
| 177 | + if (tag.type == 0x0001 || tag.type == 0x0003 || tag.type == 0x0004) { |
| 178 | + return true; |
| 179 | + } |
| 180 | + else if (tag.type == 0x0002) { |
| 181 | + return tag.value.size() <= 4; |
| 182 | + } |
| 183 | + // tag.type == 0x0005 (RATIONAL) is always stored in extra data |
| 184 | + return false; |
| 185 | + } |
| 186 | + |
| 187 | + void appendExtraData(const ExifTag& tag, size_t& dataOffset) { |
| 188 | + const auto& data = tag.value; |
| 189 | + extraData.insert(extraData.end(), data.begin(), data.end()); |
| 190 | + dataOffset += data.size(); |
| 191 | + // add a padding 0 byte. |
| 192 | + if (data.size() % 2 != 0) { |
| 193 | + extraData.push_back(0); |
| 194 | + ++dataOffset; |
| 195 | + } |
| 196 | + } |
21 | 197 | }; |
0 commit comments