18 return exif_data_new_from_data(
19 reinterpret_cast<const unsigned char*
>(buffer.constData()),
20 static_cast<unsigned int>(buffer.size())
26 ExifData *data = exif_data_new();
29 exif_data_set_option(data, EXIF_DATA_OPTION_FOLLOW_SPECIFICATION);
30 exif_data_set_data_type(data, EXIF_DATA_TYPE_COMPRESSED);
31 exif_data_set_byte_order(data, EXIF_BYTE_ORDER_INTEL);
41 qCWarning(ExifUtilityLog) <<
"Null EXIF data";
47 qCWarning(ExifUtilityLog) <<
"TIFF/DNG format not supported for EXIF writing";
49 qCWarning(ExifUtilityLog) <<
"Not a valid JPEG file";
55 unsigned char *exifBuffer =
nullptr;
56 unsigned int exifSize = 0;
57 exif_data_save_data(data, &exifBuffer, &exifSize);
59 if (!exifBuffer || exifSize == 0) {
60 qCWarning(ExifUtilityLog) <<
"Failed to generate EXIF data";
61 if (exifBuffer) free(exifBuffer);
70 while (pos < buffer.size() - 3) {
71 if (
static_cast<unsigned char>(buffer[pos]) == 0xFF) {
72 unsigned char marker =
static_cast<unsigned char>(buffer[pos + 1]);
76 int segmentLen = (
static_cast<unsigned char>(buffer[pos + 2]) << 8) |
77 static_cast<unsigned char>(buffer[pos + 3]);
78 app1End = pos + 2 + segmentLen;
80 }
else if (marker == 0xDA) {
82 }
else if (marker >= 0xE0 && marker <= 0xEF) {
83 int segmentLen = (
static_cast<unsigned char>(buffer[pos + 2]) << 8) |
84 static_cast<unsigned char>(buffer[pos + 3]);
85 pos += 2 + segmentLen;
87 }
else if (marker == 0xD8 || marker == 0xD9 || marker == 0x00) {
97 newApp1.append(
'\xFF');
98 newApp1.append(
'\xE1');
100 int app1Len = exifSize + 2;
101 newApp1.append(
static_cast<char>((app1Len >> 8) & 0xFF));
102 newApp1.append(
static_cast<char>(app1Len & 0xFF));
103 newApp1.append(
reinterpret_cast<const char*
>(exifBuffer), exifSize);
108 QByteArray newBuffer;
109 newBuffer.reserve(buffer.size() + newApp1.size());
112 newBuffer.append(buffer.left(2));
115 newBuffer.append(newApp1);
118 if (app1Start > 0 && app1End > app1Start && app1End <= buffer.size()) {
119 newBuffer.append(buffer.mid(app1End));
121 newBuffer.append(buffer.mid(2));
130 return buffer.size() >= 2 &&
131 static_cast<unsigned char>(buffer[0]) == 0xFF &&
132 static_cast<unsigned char>(buffer[1]) == 0xD8;
137 if (buffer.size() < 4) {
142 if (buffer[0] ==
'I' && buffer[1] ==
'I') {
143 return static_cast<unsigned char>(buffer[2]) == 0x2A &&
144 static_cast<unsigned char>(buffer[3]) == 0x00;
146 if (buffer[0] ==
'M' && buffer[1] ==
'M') {
147 return static_cast<unsigned char>(buffer[2]) == 0x00 &&
148 static_cast<unsigned char>(buffer[3]) == 0x2A;
155 if (buffer.size() < 12) {
169 QByteArray app1Marker(
"\xFF\xE1", 2);
170 int app1Pos = buffer.indexOf(app1Marker, 2);
171 if (app1Pos < 0 || app1Pos + 10 > buffer.size()) {
175 QByteArray exifHeader(
"Exif\0\0", 6);
176 return buffer.mid(app1Pos + 4, 6) == exifHeader;
185 if (!data)
return QString();
187 ExifEntry *entry = exif_content_get_entry(data->ifd[ifd], tag);
189 entry = exif_data_get_entry(data, tag);
196 exif_entry_get_value(entry, value,
sizeof(value));
197 return QString::fromUtf8(value).trimmed();
204 ExifEntry *entry = exif_content_get_entry(data->ifd[ifd], tag);
205 if (!entry || entry->size < 2) {
209 ExifByteOrder order = exif_data_get_byte_order(data);
210 return exif_get_short(entry->data, order);
215 if (!data)
return 0.0;
217 ExifEntry *entry = exif_content_get_entry(data->ifd[ifd], tag);
218 if (!entry || entry->size < 8) {
222 ExifByteOrder order = exif_data_get_byte_order(data);
223 ExifRational rational = exif_get_rational(entry->data, order);
224 if (rational.denominator == 0) {
227 return static_cast<double>(rational.numerator) /
static_cast<double>(rational.denominator);
234ExifEntry*
initTag(ExifData *data, ExifIfd ifd, ExifTag tag)
236 if (!data)
return nullptr;
239 ExifEntry *entry = exif_content_get_entry(data->ifd[ifd], tag);
245 entry = exif_entry_new();
246 if (!entry)
return nullptr;
252 exif_content_add_entry(data->ifd[ifd], entry);
255 exif_entry_initialize(entry, tag);
258 exif_entry_unref(entry);
260 return exif_content_get_entry(data->ifd[ifd], tag);
263ExifEntry*
createTag(ExifData *data, ExifIfd ifd, ExifTag tag, ExifFormat format,
unsigned long components)
265 if (!data)
return nullptr;
268 ExifMem *mem = exif_mem_new_default();
269 if (!mem)
return nullptr;
272 ExifEntry *entry = exif_entry_new_mem(mem);
279 size_t size = exif_format_get_size(format) * components;
280 unsigned char *buf =
static_cast<unsigned char*
>(exif_mem_alloc(mem, size));
282 exif_entry_unref(entry);
291 entry->format = format;
292 entry->components = components;
295 exif_content_add_entry(data->ifd[ifd], entry);
299 exif_entry_unref(entry);
301 return exif_content_get_entry(data->ifd[ifd], tag);
310 if (!entry || entry->components < 3 || !entry->data) {
314 double degrees = 0.0;
315 double minutes = 0.0;
316 double seconds = 0.0;
318 for (
unsigned int i = 0; i < entry->components && i < 3; i++) {
319 ExifRational rational = exif_get_rational(entry->data + i * 8, order);
320 if (rational.denominator == 0) {
323 double value =
static_cast<double>(rational.numerator) /
static_cast<double>(rational.denominator);
325 case 0: degrees = value;
break;
326 case 1: minutes = value;
break;
327 case 2: seconds = value;
break;
331 return degrees + (minutes / 60.0) + (seconds / 3600.0);
336 if (!entry || !entry->data || entry->size < 24)
return;
338 double absVal = fabs(value);
339 int degrees =
static_cast<int>(absVal);
340 double minutesF = (absVal - degrees) * 60.0;
341 int minutes =
static_cast<int>(minutesF);
342 double seconds = (minutesF - minutes) * 60.0;
344 ExifRational rationals[3] = {
345 {
static_cast<ExifLong
>(degrees), 1},
346 {
static_cast<ExifLong
>(minutes), 1},
347 {
static_cast<ExifLong
>(
static_cast<int>(seconds * 1000)), 1000}
350 for (
int i = 0; i < 3; i++) {
351 exif_set_rational(entry->data + i * 8, order, rationals[i]);
355void writeRational(ExifEntry *entry, ExifByteOrder order,
double value,
int denominator)
357 if (!entry || !entry->data || entry->size < 8)
return;
359 ExifRational rational = {
360 static_cast<ExifLong
>(
static_cast<int>(fabs(value) * denominator)),
361 static_cast<ExifLong
>(denominator)
363 exif_set_rational(entry->data, order, rational);
368 if (!entry || !entry->data || entry->size < 2)
return;
369 entry->data[0] =
static_cast<unsigned char>(value);
370 entry->data[1] =
'\0';
375 if (!entry || !entry->data || entry->size < 1)
return;
376 entry->data[0] = value;
385 if (!data || !dateTime.isValid()) {
390 const QString dateStr = dateTime.toString(QStringLiteral(
"yyyy:MM:dd hh:mm:ss"));
391 const QByteArray dateBytes = dateStr.toLatin1();
395 const ExifTag tags[] = {EXIF_TAG_DATE_TIME_ORIGINAL, EXIF_TAG_DATE_TIME_DIGITIZED};
396 bool success =
false;
398 for (ExifTag tag : tags) {
399 ExifEntry *entry =
initTag(data, EXIF_IFD_EXIF, tag);
401 qCWarning(ExifUtilityLog) <<
"Failed to create DateTime tag:" << tag;
406 if (entry->size >= 20 && entry->data) {
407 memcpy(entry->data, dateBytes.constData(), qMin(
static_cast<qsizetype
>(19), dateBytes.size()));
408 entry->data[19] =
'\0';
411 qCWarning(ExifUtilityLog) <<
"DateTime entry size mismatch:" << entry->size <<
"for tag" << tag;
#define QGC_LOGGING_CATEGORY(name, categoryStr)
ExifEntry * initTag(ExifData *data, ExifIfd ifd, ExifTag tag)
double readRational(ExifData *data, ExifTag tag, ExifIfd ifd)
Read a rational value from an EXIF tag.
void writeGpsAltRef(ExifEntry *entry, unsigned char value)
Write a byte value to a GPS altitude reference entry (0=above, 1=below sea level)
bool isTiff(const QByteArray &buffer)
ExifEntry * createTag(ExifData *data, ExifIfd ifd, ExifTag tag, ExifFormat format, unsigned long components)
ExifData * loadFromBuffer(const QByteArray &buffer)
void writeGpsCoordinate(ExifEntry *entry, ExifByteOrder order, double value)
Write GPS coordinate as EXIF rationals (degrees, minutes, seconds)
void writeRational(ExifEntry *entry, ExifByteOrder order, double value, int denominator)
Write a single rational value to an entry.
bool writeDateTimeOriginal(ExifData *data, const QDateTime &dateTime)
bool hasExifData(const QByteArray &buffer)
Check if a buffer contains valid JPEG with EXIF data.
QString readString(ExifData *data, ExifTag tag, ExifIfd ifd)
Read a string value from an EXIF tag.
bool isJpeg(const QByteArray &buffer)
Check if a buffer is a JPEG image (starts with 0xFF 0xD8)
bool saveToBuffer(ExifData *data, QByteArray &buffer)
double gpsRationalToDecimal(ExifEntry *entry, ExifByteOrder order)
Convert GPS coordinate from EXIF rational format (deg/min/sec) to decimal degrees.
void writeGpsRef(ExifEntry *entry, char value)
Write an ASCII character to a GPS reference entry (N/S/E/W)
int readShort(ExifData *data, ExifTag tag, ExifIfd ifd)
Read a short (16-bit) value from an EXIF tag.