29namespace helios::detail {
34 constexpr uint16_t TYPE_BYTE = 1;
35 constexpr uint16_t TYPE_ASCII = 2;
36 constexpr uint16_t TYPE_SHORT = 3;
37 constexpr uint16_t TYPE_LONG = 4;
38 constexpr uint16_t TYPE_RATIONAL = 5;
39 constexpr uint16_t TYPE_UNDEFINED = 7;
40 constexpr uint16_t TYPE_SRATIONAL = 10;
43 constexpr size_t APP1_MAX_PAYLOAD = 65533;
46 constexpr size_t IFD_ENTRY_VALUE_BYTES = 4;
50 void putU16(std::vector<unsigned char> &b, uint16_t v) {
51 b.push_back(
static_cast<unsigned char>(v & 0xFF));
52 b.push_back(
static_cast<unsigned char>((v >> 8) & 0xFF));
54 void putU32(std::vector<unsigned char> &b, uint32_t v) {
55 b.push_back(
static_cast<unsigned char>(v & 0xFF));
56 b.push_back(
static_cast<unsigned char>((v >> 8) & 0xFF));
57 b.push_back(
static_cast<unsigned char>((v >> 16) & 0xFF));
58 b.push_back(
static_cast<unsigned char>((v >> 24) & 0xFF));
60 void patchU32(std::vector<unsigned char> &b,
size_t pos, uint32_t v) {
61 b[pos] =
static_cast<unsigned char>(v & 0xFF);
62 b[pos + 1] =
static_cast<unsigned char>((v >> 8) & 0xFF);
63 b[pos + 2] =
static_cast<unsigned char>((v >> 16) & 0xFF);
64 b[pos + 3] =
static_cast<unsigned char>((v >> 24) & 0xFF);
78 std::array<unsigned char, 4> inline_value{};
80 std::vector<unsigned char> external_data;
81 bool inline_only =
true;
85 bool is_pointer_placeholder =
false;
87 int pointer_target_index = -1;
90 size_t typeSize(uint16_t t) {
109 void finalizeEntry(IFDEntry &e,
const std::vector<unsigned char> &raw) {
110 const size_t bytes = raw.size();
111 if (bytes <= IFD_ENTRY_VALUE_BYTES) {
112 e.inline_only =
true;
113 e.inline_value.fill(0);
114 std::memcpy(e.inline_value.data(), raw.data(), bytes);
116 e.inline_only =
false;
117 e.external_data = raw;
121 IFDEntry makeAscii(uint16_t tag,
const std::string &s) {
126 e.count =
static_cast<uint32_t
>(s.size() + 1);
127 std::vector<unsigned char> raw(s.size() + 1, 0);
129 std::memcpy(raw.data(), s.data(), s.size());
131 finalizeEntry(e, raw);
135 IFDEntry makeUndefined(uint16_t tag,
const std::string &s) {
139 e.type = TYPE_UNDEFINED;
140 e.count =
static_cast<uint32_t
>(s.size());
141 std::vector<unsigned char> raw(s.size());
143 std::memcpy(raw.data(), s.data(), s.size());
145 finalizeEntry(e, raw);
149 IFDEntry makeShort(uint16_t tag, uint16_t v) {
156 std::vector<unsigned char> raw(2);
157 raw[0] =
static_cast<unsigned char>(v & 0xFF);
158 raw[1] =
static_cast<unsigned char>((v >> 8) & 0xFF);
159 finalizeEntry(e, raw);
163 IFDEntry makeLong(uint16_t tag, uint32_t v) {
168 std::vector<unsigned char> raw(4);
169 raw[0] =
static_cast<unsigned char>(v & 0xFF);
170 raw[1] =
static_cast<unsigned char>((v >> 8) & 0xFF);
171 raw[2] =
static_cast<unsigned char>((v >> 16) & 0xFF);
172 raw[3] =
static_cast<unsigned char>((v >> 24) & 0xFF);
173 finalizeEntry(e, raw);
177 IFDEntry makeSRational(uint16_t tag, int32_t numerator, int32_t denominator) {
180 e.type = TYPE_SRATIONAL;
182 std::vector<unsigned char> raw(8);
183 const uint32_t n =
static_cast<uint32_t
>(numerator);
184 const uint32_t d =
static_cast<uint32_t
>(denominator);
185 raw[0] =
static_cast<unsigned char>(n & 0xFF);
186 raw[1] =
static_cast<unsigned char>((n >> 8) & 0xFF);
187 raw[2] =
static_cast<unsigned char>((n >> 16) & 0xFF);
188 raw[3] =
static_cast<unsigned char>((n >> 24) & 0xFF);
189 raw[4] =
static_cast<unsigned char>(d & 0xFF);
190 raw[5] =
static_cast<unsigned char>((d >> 8) & 0xFF);
191 raw[6] =
static_cast<unsigned char>((d >> 16) & 0xFF);
192 raw[7] =
static_cast<unsigned char>((d >> 24) & 0xFF);
193 finalizeEntry(e, raw);
197 IFDEntry makeRational(uint16_t tag, uint32_t numerator, uint32_t denominator) {
200 e.type = TYPE_RATIONAL;
202 std::vector<unsigned char> raw(8);
203 raw[0] =
static_cast<unsigned char>(numerator & 0xFF);
204 raw[1] =
static_cast<unsigned char>((numerator >> 8) & 0xFF);
205 raw[2] =
static_cast<unsigned char>((numerator >> 16) & 0xFF);
206 raw[3] =
static_cast<unsigned char>((numerator >> 24) & 0xFF);
207 raw[4] =
static_cast<unsigned char>(denominator & 0xFF);
208 raw[5] =
static_cast<unsigned char>((denominator >> 8) & 0xFF);
209 raw[6] =
static_cast<unsigned char>((denominator >> 16) & 0xFF);
210 raw[7] =
static_cast<unsigned char>((denominator >> 24) & 0xFF);
211 finalizeEntry(e, raw);
215 IFDEntry makeRationalArray(uint16_t tag,
const std::vector<std::pair<uint32_t, uint32_t>> &rats) {
218 e.type = TYPE_RATIONAL;
219 e.count =
static_cast<uint32_t
>(rats.size());
220 std::vector<unsigned char> raw(rats.size() * 8);
221 for (
size_t i = 0; i < rats.size(); ++i) {
222 const uint32_t n = rats[i].first;
223 const uint32_t d = rats[i].second;
224 raw[i * 8 + 0] =
static_cast<unsigned char>(n & 0xFF);
225 raw[i * 8 + 1] =
static_cast<unsigned char>((n >> 8) & 0xFF);
226 raw[i * 8 + 2] =
static_cast<unsigned char>((n >> 16) & 0xFF);
227 raw[i * 8 + 3] =
static_cast<unsigned char>((n >> 24) & 0xFF);
228 raw[i * 8 + 4] =
static_cast<unsigned char>(d & 0xFF);
229 raw[i * 8 + 5] =
static_cast<unsigned char>((d >> 8) & 0xFF);
230 raw[i * 8 + 6] =
static_cast<unsigned char>((d >> 16) & 0xFF);
231 raw[i * 8 + 7] =
static_cast<unsigned char>((d >> 24) & 0xFF);
233 finalizeEntry(e, raw);
238 std::vector<std::pair<uint32_t, uint32_t>> decimalDegToDMSRationals(
double deg) {
239 double abs_deg = std::fabs(deg);
240 uint32_t d =
static_cast<uint32_t
>(std::floor(abs_deg));
241 double rem_min = (abs_deg -
static_cast<double>(d)) * 60.0;
242 uint32_t m =
static_cast<uint32_t
>(std::floor(rem_min));
243 double rem_sec = (rem_min -
static_cast<double>(m)) * 60.0;
245 constexpr uint32_t SEC_DENOM = 1000000;
246 uint32_t s_num =
static_cast<uint32_t
>(std::round(rem_sec *
static_cast<double>(SEC_DENOM)));
247 return {{d, 1u}, {m, 1u}, {s_num, SEC_DENOM}};
251 std::pair<uint32_t, uint32_t> shutterSpeedRational(
float seconds) {
252 if (seconds <= 0.f) {
255 if (seconds < 1.0f) {
257 double n = std::round(1.0 /
static_cast<double>(seconds));
258 if (n < 1.0) n = 1.0;
259 if (n > 4294967294.0) n = 4294967294.0;
260 return {1u,
static_cast<uint32_t
>(n)};
263 return {
static_cast<uint32_t
>(std::round(seconds * 1000.0)), 1000u};
276 size_t emitIFD(std::vector<unsigned char> &tiff_buf,
278 std::vector<IFDEntry> &entries,
279 uint32_t next_ifd_offset,
280 const std::array<uint32_t, 2> &subifd_offsets) {
283 const size_t n = entries.size();
284 const size_t record_bytes = 2 + n * 12 + 4;
285 const size_t data_blob_offset = base_offset + record_bytes;
288 if (tiff_buf.size() < base_offset) {
289 tiff_buf.resize(base_offset, 0);
293 std::vector<uint32_t> entry_external_offset(n, 0);
294 size_t running = data_blob_offset;
295 for (
size_t i = 0; i < n; ++i) {
296 if (!entries[i].inline_only) {
299 if (running % 2 != 0) {
302 entry_external_offset[i] =
static_cast<uint32_t
>(running);
303 running += entries[i].external_data.size();
308 const size_t pre_size = tiff_buf.size();
311 tiff_buf.resize(running, 0);
313 size_t cursor = base_offset;
315 tiff_buf[cursor++] =
static_cast<unsigned char>(n & 0xFF);
316 tiff_buf[cursor++] =
static_cast<unsigned char>((n >> 8) & 0xFF);
319 for (
size_t i = 0; i < n; ++i) {
320 const IFDEntry &e = entries[i];
322 tiff_buf[cursor + 0] =
static_cast<unsigned char>(e.tag & 0xFF);
323 tiff_buf[cursor + 1] =
static_cast<unsigned char>((e.tag >> 8) & 0xFF);
325 tiff_buf[cursor + 2] =
static_cast<unsigned char>(e.type & 0xFF);
326 tiff_buf[cursor + 3] =
static_cast<unsigned char>((e.type >> 8) & 0xFF);
328 tiff_buf[cursor + 4] =
static_cast<unsigned char>(e.count & 0xFF);
329 tiff_buf[cursor + 5] =
static_cast<unsigned char>((e.count >> 8) & 0xFF);
330 tiff_buf[cursor + 6] =
static_cast<unsigned char>((e.count >> 16) & 0xFF);
331 tiff_buf[cursor + 7] =
static_cast<unsigned char>((e.count >> 24) & 0xFF);
334 if (e.is_pointer_placeholder) {
335 const uint32_t target = (e.pointer_target_index >= 0)
336 ? subifd_offsets[
static_cast<size_t>(e.pointer_target_index)]
338 patchU32(tiff_buf, cursor + 8, target);
339 }
else if (e.inline_only) {
340 tiff_buf[cursor + 8] = e.inline_value[0];
341 tiff_buf[cursor + 9] = e.inline_value[1];
342 tiff_buf[cursor + 10] = e.inline_value[2];
343 tiff_buf[cursor + 11] = e.inline_value[3];
345 patchU32(tiff_buf, cursor + 8, entry_external_offset[i]);
351 patchU32(tiff_buf, cursor, next_ifd_offset);
355 for (
size_t i = 0; i < n; ++i) {
356 if (!entries[i].inline_only) {
357 const uint32_t off = entry_external_offset[i];
358 std::memcpy(&tiff_buf[off], entries[i].external_data.data(), entries[i].external_data.size());
366 std::vector<IFDEntry> buildExifSubIFD(
const ImageEXIFData &m) {
367 std::vector<IFDEntry> e;
368 if (m.exposure_time_s > 0.f) {
369 auto r = shutterSpeedRational(m.exposure_time_s);
370 e.push_back(makeRational(0x829A, r.first, r.second));
372 if (m.f_number > 0.f) {
374 uint32_t num =
static_cast<uint32_t
>(std::round(
static_cast<double>(m.f_number) * 100.0));
375 e.push_back(makeRational(0x829D, num, 100u));
377 if (!m.datetime_original.empty()) {
378 e.push_back(makeAscii(0x9003, m.datetime_original));
380 if (!m.datetime_digitized.empty()) {
381 e.push_back(makeAscii(0x9004, m.datetime_digitized));
383 if (!m.offset_time.empty()) {
385 e.push_back(makeAscii(0x9010, m.offset_time));
386 e.push_back(makeAscii(0x9011, m.offset_time));
387 e.push_back(makeAscii(0x9012, m.offset_time));
390 e.push_back(makeShort(0x8827,
static_cast<uint16_t
>(std::min<unsigned int>(m.iso, 65535u))));
392 if (m.has_exposure_bias) {
394 int32_t num =
static_cast<int32_t
>(std::round(
static_cast<double>(m.exposure_bias_ev) * 100.0));
395 e.push_back(makeSRational(0x9204, num, 100));
397 if (m.max_aperture_value_apex > 0.f) {
399 uint32_t num =
static_cast<uint32_t
>(std::round(
static_cast<double>(m.max_aperture_value_apex) * 100.0));
400 e.push_back(makeRational(0x9205, num, 100u));
402 if (m.subject_distance_m > 0.f) {
404 uint32_t num =
static_cast<uint32_t
>(std::round(
static_cast<double>(m.subject_distance_m) * 1000.0));
405 e.push_back(makeRational(0x9206, num, 1000u));
407 if (m.focal_length_mm > 0.f) {
409 uint32_t num =
static_cast<uint32_t
>(std::round(
static_cast<double>(m.focal_length_mm) * 1000.0));
410 e.push_back(makeRational(0x920A, num, 1000u));
412 if (m.pixel_x_dimension > 0) {
413 e.push_back(makeLong(0xA002, m.pixel_x_dimension));
415 if (m.pixel_y_dimension > 0) {
416 e.push_back(makeLong(0xA003, m.pixel_y_dimension));
418 if (m.focal_plane_x_resolution > 0.f) {
419 uint32_t num =
static_cast<uint32_t
>(std::round(
static_cast<double>(m.focal_plane_x_resolution) * 1000.0));
420 e.push_back(makeRational(0xA20E, num, 1000u));
422 if (m.focal_plane_y_resolution > 0.f) {
423 uint32_t num =
static_cast<uint32_t
>(std::round(
static_cast<double>(m.focal_plane_y_resolution) * 1000.0));
424 e.push_back(makeRational(0xA20F, num, 1000u));
426 if (m.focal_plane_x_resolution > 0.f || m.focal_plane_y_resolution > 0.f) {
427 e.push_back(makeShort(0xA210,
static_cast<uint16_t
>(m.focal_plane_resolution_unit)));
429 if (m.exposure_mode != (
unsigned int) -1) {
430 e.push_back(makeShort(0xA402,
static_cast<uint16_t
>(m.exposure_mode)));
432 if (m.white_balance != (
unsigned int) -1) {
433 e.push_back(makeShort(0xA403,
static_cast<uint16_t
>(m.white_balance)));
435 if (m.digital_zoom_ratio > 0.f) {
437 uint32_t num =
static_cast<uint32_t
>(std::round(
static_cast<double>(m.digital_zoom_ratio) * 100.0));
438 e.push_back(makeRational(0xA404, num, 100u));
440 if (m.focal_length_in_35mm > 0) {
441 e.push_back(makeShort(0xA405,
static_cast<uint16_t
>(std::min<unsigned int>(m.focal_length_in_35mm, 65535u))));
443 if (!m.lens_make.empty()) {
444 e.push_back(makeAscii(0xA433, m.lens_make));
446 if (!m.lens_model.empty()) {
447 e.push_back(makeAscii(0xA434, m.lens_model));
449 if (!m.lens_specification.empty()) {
452 e.push_back(makeAscii(0xA435, m.lens_specification));
456 e.push_back(makeUndefined(0x9000,
"0232"));
459 std::sort(e.begin(), e.end(), [](
const IFDEntry &a,
const IFDEntry &b) { return a.tag < b.tag; });
464 std::vector<IFDEntry> buildGPSIFD(
const ImageEXIFData &m) {
465 std::vector<IFDEntry> e;
473 std::vector<unsigned char> raw = {2, 3, 0, 0};
474 finalizeEntry(v, raw);
478 e.push_back(makeAscii(0x0001, (m.latitude_deg >= 0.0) ?
"N" :
"S"));
480 e.push_back(makeRationalArray(0x0002, decimalDegToDMSRationals(m.latitude_deg)));
482 e.push_back(makeAscii(0x0003, (m.longitude_deg >= 0.0) ?
"E" :
"W"));
484 e.push_back(makeRationalArray(0x0004, decimalDegToDMSRationals(m.longitude_deg)));
491 std::vector<unsigned char> raw(1);
492 raw[0] = (m.altitude_m >= 0.0) ? 0 : 1;
493 finalizeEntry(v, raw);
498 const double abs_alt = std::fabs(m.altitude_m);
499 const uint32_t num =
static_cast<uint32_t
>(std::round(abs_alt * 1000.0));
500 e.push_back(makeRational(0x0006, num, 1000u));
503 if (!m.gps_timestamp_hms.empty()) {
504 int hh = 0, mm = 0, ss = 0;
505 if (std::sscanf(m.gps_timestamp_hms.c_str(),
"%d:%d:%d", &hh, &mm, &ss) == 3) {
506 std::vector<std::pair<uint32_t, uint32_t>> ts = {
507 {
static_cast<uint32_t
>(hh), 1u},
508 {
static_cast<uint32_t
>(mm), 1u},
509 {
static_cast<uint32_t
>(ss), 1u}};
510 e.push_back(makeRationalArray(0x0007, ts));
514 e.push_back(makeAscii(0x0010,
"T"));
517 double dir = m.img_direction_deg;
518 if (dir < 0.0) dir += 360.0;
519 if (dir >= 360.0) dir -= 360.0;
520 uint32_t num =
static_cast<uint32_t
>(std::round(dir * 100.0));
521 e.push_back(makeRational(0x0011, num, 100u));
524 if (!m.gps_datestamp.empty()) {
525 e.push_back(makeAscii(0x001D, m.gps_datestamp));
528 std::sort(e.begin(), e.end(), [](
const IFDEntry &a,
const IFDEntry &b) { return a.tag < b.tag; });
543 std::vector<IFDEntry> ifd0;
544 if (!m.
make.empty()) ifd0.push_back(makeAscii(0x010F, m.
make));
545 if (!m.
model.empty()) ifd0.push_back(makeAscii(0x0110, m.
model));
546 ifd0.push_back(makeShort(0x0112,
static_cast<uint16_t
>(m.
orientation)));
552 exif_ptr.tag = 0x8769;
553 exif_ptr.type = TYPE_LONG;
555 exif_ptr.is_pointer_placeholder =
true;
556 exif_ptr.pointer_target_index = 0;
557 exif_ptr.inline_only =
true;
558 ifd0.push_back(exif_ptr);
563 gps_ptr.tag = 0x8825;
564 gps_ptr.type = TYPE_LONG;
566 gps_ptr.is_pointer_placeholder =
true;
567 gps_ptr.pointer_target_index = 1;
568 gps_ptr.inline_only =
true;
569 ifd0.push_back(gps_ptr);
572 std::sort(ifd0.begin(), ifd0.end(), [](
const IFDEntry &a,
const IFDEntry &b) { return a.tag < b.tag; });
574 std::vector<IFDEntry> exif_sub = buildExifSubIFD(m);
575 std::vector<IFDEntry> gps_ifd;
577 gps_ifd = buildGPSIFD(m);
589 std::vector<unsigned char> tiff;
594 putU16(tiff, 0x002A);
598 auto ifdRecordSize = [](
const std::vector<IFDEntry> &v) {
599 size_t s = 2 + v.size() * 12 + 4;
600 for (
const auto &e: v) {
601 if (!e.inline_only) {
602 if (s % 2 != 0) s += 1;
603 s += e.external_data.size();
609 const size_t ifd0_base = 8;
610 const size_t ifd0_size = ifdRecordSize(ifd0);
611 const size_t exif_base = ifd0_base + ifd0_size;
612 const size_t exif_size = ifdRecordSize(exif_sub);
613 const size_t gps_base = exif_base + exif_size;
615 std::array<uint32_t, 2> subifd_offsets = {
static_cast<uint32_t
>(exif_base),
616 m.
gps_valid ?
static_cast<uint32_t
>(gps_base) : 0u};
619 emitIFD(tiff, ifd0_base, ifd0, 0u, subifd_offsets);
621 emitIFD(tiff, exif_base, exif_sub, 0u, subifd_offsets);
624 emitIFD(tiff, gps_base, gps_ifd, 0u, subifd_offsets);
628 std::vector<unsigned char> payload = {
'E',
'x',
'i',
'f', 0, 0};
629 payload.reserve(6 + tiff.size());
630 payload.insert(payload.end(), tiff.begin(), tiff.end());
632 if (payload.size() > APP1_MAX_PAYLOAD) {
633 helios_runtime_error(
"ERROR (buildEXIFAppSegment): EXIF payload (" + std::to_string(payload.size()) +
634 " bytes) exceeds JPEG APP1 size limit (" + std::to_string(APP1_MAX_PAYLOAD) +
").");
641 pugi::xml_document doc;
643 pugi::xml_node xpacket_begin = doc.append_child(pugi::node_pi);
644 xpacket_begin.set_name(
"xpacket");
645 xpacket_begin.set_value(
"begin=\"\xEF\xBB\xBF\" id=\"W5M0MpCehiHzreSzNTczkc9d\"");
647 pugi::xml_node x_xmpmeta = doc.append_child(
"x:xmpmeta");
648 x_xmpmeta.append_attribute(
"xmlns:x") =
"adobe:ns:meta/";
649 x_xmpmeta.append_attribute(
"x:xmptk") =
"Helios";
651 pugi::xml_node rdf = x_xmpmeta.append_child(
"rdf:RDF");
652 rdf.append_attribute(
"xmlns:rdf") =
"http://www.w3.org/1999/02/22-rdf-syntax-ns#";
654 pugi::xml_node desc = rdf.append_child(
"rdf:Description");
655 desc.append_attribute(
"rdf:about") =
"";
656 desc.append_attribute(
"xmlns:tiff") =
"http://ns.adobe.com/tiff/1.0/";
657 desc.append_attribute(
"xmlns:xmp") =
"http://ns.adobe.com/xap/1.0/";
658 desc.append_attribute(
"xmlns:Camera") =
"http://pix4d.com/camera/1.0";
660 if (!m.
make.empty()) desc.append_attribute(
"tiff:Make") = m.
make.c_str();
661 if (!m.
model.empty()) desc.append_attribute(
"tiff:Model") = m.
model.c_str();
662 if (!m.
software.empty()) desc.append_attribute(
"xmp:CreatorTool") = m.
software.c_str();
664 auto fmt = [](
double v) {
665 std::ostringstream oss;
667 oss << std::fixed << v;
672 desc.append_attribute(
"Camera:Yaw") = fmt(m.
yaw_deg).c_str();
673 desc.append_attribute(
"Camera:Pitch") = fmt(m.
pitch_deg).c_str();
674 desc.append_attribute(
"Camera:Roll") = fmt(m.
roll_deg).c_str();
677 pugi::xml_node xpacket_end = doc.append_child(pugi::node_pi);
678 xpacket_end.set_name(
"xpacket");
679 xpacket_end.set_value(
"end=\"w\"");
681 std::ostringstream xml_oss;
682 doc.save(xml_oss,
"", pugi::format_raw | pugi::format_no_declaration);
683 const std::string xml = xml_oss.str();
686 static const char NS_ID[] =
"http://ns.adobe.com/xap/1.0/";
687 const size_t ns_len = std::strlen(NS_ID) + 1;
688 std::vector<unsigned char> payload;
689 payload.reserve(ns_len + xml.size());
690 payload.insert(payload.end(), NS_ID, NS_ID + ns_len);
691 payload.insert(payload.end(), xml.begin(), xml.end());
693 if (payload.size() > APP1_MAX_PAYLOAD) {
694 helios_runtime_error(
"ERROR (buildXMPAppSegment): XMP payload (" + std::to_string(payload.size()) +
695 " bytes) exceeds JPEG APP1 size limit (" + std::to_string(APP1_MAX_PAYLOAD) +
").");