#include "ExifParser.h" #include #include #include ExifParser::ExifParser() { } ExifParser::~ExifParser() { } double ExifParser::readTime(QByteArray& buf) { QByteArray tiffHeader("\x49\x49\x2A", 3); QByteArray createDateHeader("\x04\x90\x02", 3); // find header position uint32_t tiffHeaderIndex = buf.indexOf(tiffHeader); // find creation date header index uint32_t createDateHeaderIndex = buf.indexOf(createDateHeader); // extract size of date-time string, -1 accounting for null-termination uint32_t* sizeString = reinterpret_cast(buf.mid(createDateHeaderIndex + 4, 4).data()); uint32_t createDateStringSize = qFromLittleEndian(*sizeString) - 1; // extract location of date-time string uint32_t* dataIndex = reinterpret_cast(buf.mid(createDateHeaderIndex + 8, 4).data()); uint32_t createDateStringDataIndex = qFromLittleEndian(*dataIndex) + tiffHeaderIndex; // read out data of create date-time field QString createDate = buf.mid(createDateStringDataIndex, createDateStringSize); QStringList createDateList = createDate.split(' '); if (createDateList.count() < 2) { qWarning() << "Could not decode creation time and date: " << createDateList; return -1.0; } QStringList dateList = createDateList[0].split(':'); if (dateList.count() < 3) { qWarning() << "Could not decode creation date: " << dateList; return -1.0; } QStringList timeList = createDateList[1].split(':'); if (timeList.count() < 3) { qWarning() << "Could not decode creation time: " << timeList; return -1.0; } QDate date(dateList[0].toInt(), dateList[1].toInt(), dateList[2].toInt()); QTime time(timeList[0].toInt(), timeList[1].toInt(), timeList[2].toInt()); QDateTime tagTime(date, time); return tagTime.toMSecsSinceEpoch()/1000.0; } bool ExifParser::write(QByteArray& buf, GeoTagWorker::cameraFeedbackPacket& geotag) { QByteArray app1Header("\xff\xe1", 2); uint32_t app1HeaderInd = buf.indexOf(app1Header); uint16_t *conversionPointer = reinterpret_cast(buf.mid(app1HeaderInd + 2, 2).data()); uint16_t app1Size = *conversionPointer; uint16_t app1SizeEndian = qFromBigEndian(app1Size) + 0xa5; // change wrong endian QByteArray tiffHeader("\x49\x49\x2A", 3); uint32_t tiffHeaderInd = buf.indexOf(tiffHeader); conversionPointer = reinterpret_cast(buf.mid(tiffHeaderInd + 8, 2).data()); uint16_t numberOfTiffFields = *conversionPointer; uint32_t nextIfdOffsetInd = tiffHeaderInd + 10 + 12 * (numberOfTiffFields); conversionPointer = reinterpret_cast(buf.mid(nextIfdOffsetInd, 2).data()); uint16_t nextIfdOffset = *conversionPointer; // Definition of useful unions and structs union char2uint32_u { char c[4]; uint32_t i; }; union char2uint16_u { char c[2]; uint16_t i; }; // This struct describes a standart field used in exif files struct field_s { uint16_t tagID; // Describes which information is added here, e.g. GPS Lat uint16_t type; // Describes the data type, e.g. string, uint8_t,... uint32_t size; // Describes the size uint32_t content; // Either contains the information, or the offset to the exif header where the information is stored (if 32 bits is not enough) }; // This struct contains all the fields that we want to add to the image struct fields_s { field_s gpsVersion; field_s gpsLatRef; field_s gpsLat; field_s gpsLonRef; field_s gpsLon; field_s gpsAltRef; field_s gpsAlt; field_s gpsMapDatum; uint32_t finishedDataField; }; // These are the additional information that can not be put into a single uin32_t struct extended_s { uint32_t gpsLat[6]; uint32_t gpsLon[6]; uint32_t gpsAlt[2]; char mapDatum[7];// = {0x57,0x47,0x53,0x2D,0x38,0x34,0x00}; }; // This struct contains all the information we want to add to the image struct readable_s { fields_s fields; extended_s extendedData; }; // This union is used because for writing the information we have to use a char array, but we still want the information to be available in a more descriptive way union { char c[0xa3]; readable_s readable; } gpsData; char2uint32_u gpsIFDInd; gpsIFDInd.i = nextIfdOffset; // this will stay constant QByteArray gpsInfo("\x25\x88\x04\x00\x01\x00\x00\x00", 8); gpsInfo.append(gpsIFDInd.c[0]); gpsInfo.append(gpsIFDInd.c[1]); gpsInfo.append(gpsIFDInd.c[2]); gpsInfo.append(gpsIFDInd.c[3]); // filling values to gpsData uint32_t gpsDataExtInd = gpsIFDInd.i + 2 + sizeof(fields_s); // Filling up the fields with the corresponding values gpsData.readable.fields.gpsVersion.tagID = 0; gpsData.readable.fields.gpsVersion.type = 1; gpsData.readable.fields.gpsVersion.size = 4; gpsData.readable.fields.gpsVersion.content = 2; gpsData.readable.fields.gpsLatRef.tagID = 1; gpsData.readable.fields.gpsLatRef.type = 2; gpsData.readable.fields.gpsLatRef.size = 2; gpsData.readable.fields.gpsLatRef.content = geotag.latitude > 0 ? 'N' : 'S'; gpsData.readable.fields.gpsLat.tagID = 2; gpsData.readable.fields.gpsLat.type = 5; gpsData.readable.fields.gpsLat.size = 3; gpsData.readable.fields.gpsLat.content = gpsDataExtInd; gpsData.readable.fields.gpsLonRef.tagID = 3; gpsData.readable.fields.gpsLonRef.type = 2; gpsData.readable.fields.gpsLonRef.size = 2; gpsData.readable.fields.gpsLonRef.content = geotag.longitude > 0 ? 'E' : 'W'; gpsData.readable.fields.gpsLon.tagID = 4; gpsData.readable.fields.gpsLon.type = 5; gpsData.readable.fields.gpsLon.size = 3; gpsData.readable.fields.gpsLon.content = gpsDataExtInd + 6 * 4; gpsData.readable.fields.gpsAltRef.tagID = 5; gpsData.readable.fields.gpsAltRef.type = 1; gpsData.readable.fields.gpsAltRef.size = 1; gpsData.readable.fields.gpsAltRef.content = 0x00; gpsData.readable.fields.gpsAlt.tagID = 6; gpsData.readable.fields.gpsAlt.type = 5; gpsData.readable.fields.gpsAlt.size = 1; gpsData.readable.fields.gpsAlt.content = gpsDataExtInd + 6 * 4 * 2; gpsData.readable.fields.gpsMapDatum.tagID = 18; gpsData.readable.fields.gpsMapDatum.type = 2; gpsData.readable.fields.gpsMapDatum.size = 7; gpsData.readable.fields.gpsMapDatum.content = gpsDataExtInd + 6 * 4 * 2 + 2 * 4; gpsData.readable.fields.finishedDataField = 0; // Filling up the additional information that does not fit into the fields gpsData.readable.extendedData.gpsLat[0] = abs(static_cast(geotag.latitude)); gpsData.readable.extendedData.gpsLat[1] = 1; gpsData.readable.extendedData.gpsLat[2] = static_cast((fabs(geotag.latitude) - floor(fabs(geotag.latitude))) * 60.0); gpsData.readable.extendedData.gpsLat[3] = 1; gpsData.readable.extendedData.gpsLat[4] = static_cast((fabs(geotag.latitude) * 60.0 - floor(fabs(geotag.latitude) * 60.0)) * 60000.0); gpsData.readable.extendedData.gpsLat[5] = 1000; gpsData.readable.extendedData.gpsLon[0] = abs(static_cast(geotag.longitude)); gpsData.readable.extendedData.gpsLon[1] = 1; gpsData.readable.extendedData.gpsLon[2] = static_cast((fabs(geotag.longitude) - floor(fabs(geotag.longitude))) * 60.0); gpsData.readable.extendedData.gpsLon[3] = 1; gpsData.readable.extendedData.gpsLon[4] = static_cast((fabs(geotag.longitude) * 60.0 - floor(fabs(geotag.longitude) * 60.0)) * 60000.0); gpsData.readable.extendedData.gpsLon[5] = 1000; gpsData.readable.extendedData.gpsAlt[0] = geotag.altitude * 100; gpsData.readable.extendedData.gpsAlt[1] = 100; gpsData.readable.extendedData.mapDatum[0] = 'W'; gpsData.readable.extendedData.mapDatum[1] = 'G'; gpsData.readable.extendedData.mapDatum[2] = 'S'; gpsData.readable.extendedData.mapDatum[3] = '-'; gpsData.readable.extendedData.mapDatum[4] = '8'; gpsData.readable.extendedData.mapDatum[5] = '4'; gpsData.readable.extendedData.mapDatum[6] = 0x00; // remove 12 spaces from image description, as otherwise we need to loop through every field and correct the new address values buf.remove(nextIfdOffsetInd + 4, 12); // TODO correct size in image description // insert Gps Info to image file buf.insert(nextIfdOffsetInd, gpsInfo, 12); char numberOfFields[2] = {0x08, 0x00}; // insert number of gps specific fields that we want to add buf.insert(gpsIFDInd.i + tiffHeaderInd, numberOfFields, 2); // insert the gps data buf.insert(gpsIFDInd.i + 2 + tiffHeaderInd, gpsData.c, 0xa3); // update the new file size and exif offsets char2uint16_u converter; converter.i = qToBigEndian(app1SizeEndian); buf.replace(app1HeaderInd + 2, 2, converter.c, 2); converter.i = nextIfdOffset + 12 + 0xa5; buf.replace(nextIfdOffsetInd + 12, 2, converter.c, 2); converter.i = (numberOfTiffFields) + 1; buf.replace(tiffHeaderInd + 8, 2, converter.c, 2); return true; }