IFEntry parseIFEntry(const unsigned char *buf, const unsigned offs, const bool alignIntel, const unsigned base, const unsigned len) { IFEntry result; // 每个条目的组成 // 2 bytes: 标记数字(数据域) // 2 bytes: 数据格式 // 4 bytes: 组件数 // 4 bytes: 数据值或数据值的偏移量 result.tag = parse16(buf + offs, alignIntel); result.format = parse16(buf + offs + 2, alignIntel); result.length = parse32(buf + offs + 4, alignIntel); result.data = parse32(buf + offs + 8, alignIntel); // 按照指定格式进行解析 switch (result.format) { case 1: result.val_byte = (unsigned char)*(buf + offs + 8); break; case 2: result.val_string = parseEXIFString(buf, result.length, result.data, base, len); break; case 3: result.val_16 = parse16((const unsigned char *)buf + offs + 8, alignIntel); break; case 4: result.val_32 = result.data; break; case 5: if (base + result.data + 8 <= len) result.val_rational = parseEXIFRational(buf + base + result.data, alignIntel); break; case 7: case 9: case 10: break; default: result.tag = 0xFF; } return result; }
int EXIFInfo::parseFrom(const unsigned char *buf, unsigned len) { // 安全性检测:所有的 JPEG 文件开始于0xFFD8,结束于0xFFD9 // 这种检查还可以确保用户提供了正确值的长度 if (!buf || len < 4) return PARSE_EXIF_ERROR_NO_EXIF; if (buf[0] != 0xFF || buf[1] != 0xD8) return PARSE_EXIF_ERROR_NO_JPEG; if (buf[len - 2] != 0xFF || buf[len - 1] != 0xD9) return PARSE_EXIF_ERROR_NO_JPEG; clear(); // 扫描EXIF头(0xFF 0xE1),做安全性检查寻找字节"Exif\0\0".该标识的长度数据是在Motorola // 字节顺序,在parse16()提供参数 'false' // 标识至少包含TIFF头信息,否则EXIF数据已损坏 // 所以此处最小数据长度必须是 // 2 bytes: 段长度 // 6 bytes: "Exif\0\0" 字符串 // 2 bytes: TIFF 头 (为 "II" 或 "MM" ) // 2 bytes: TIFF 改变 (两字节 0x2a00 在 Motorola 字节顺序下) // 4 bytes: 第一个IFD的偏移量 // ========= // 16 bytes unsigned offs = 0; // 在缓冲区中当前偏移量 for (offs = 0; offs < len - 1; offs++) if (buf[offs] == 0xFF && buf[offs + 1] == 0xE1) break; if (offs + 4 > len) return PARSE_EXIF_ERROR_NO_EXIF; offs += 2; unsigned short section_length = parse16(buf + offs, false); if (offs + section_length > len || section_length < 16) return PARSE_EXIF_ERROR_CORRUPT; offs += 2; return parseFromEXIFSegment(buf + offs, len - offs); }
int ParseEXIF(unsigned char *buf, unsigned len, EXIFInfo &result) { bool alignIntel = true; // byte alignment unsigned offs = 0; // current offset into buffer if(len == 0) return PARSE_EXIF_ERROR_NO_EXIF; // Prepare return structure memset(&result, 0, sizeof(result)); // Scan for EXIF header and do a sanity check for(offs = 0; offs < len-1; offs++) if(buf[offs] == 0xFF && buf[offs+1] == 0xE1) break; if(offs == len-1) return PARSE_EXIF_ERROR_NO_EXIF; offs += 4; if(buf[offs] != 0x45 || buf[offs+1] != 0x78 || buf[offs+2] != 0x69) return PARSE_EXIF_ERROR_NO_EXIF; // Get byte alignment (Motorola or Intel) offs += 6; if(buf[offs] == 0x49 && buf[offs+1] == 0x49) alignIntel = true; else { if(buf[offs] == 0x4d && buf[offs+1] == 0x4d) alignIntel = false; else return PARSE_EXIF_ERROR_UNKNOWN_BYTEALIGN; } result.byteAlign = alignIntel; // Get offset into first IFD offs += 4; unsigned x = parse32(buf+offs, alignIntel); if(offs + x >= len) { return PARSE_EXIF_ERROR_CORRUPT; } // Jump to the first IFD, scan tags there. offs += x-4; int nentries = parse16(buf+offs, alignIntel); offs += 2; unsigned ifdOffset = offs-10; unsigned exifSubIFD = 0; unsigned gpsIFD = 0; for(int j = 0; j < nentries; j++) { unsigned short tag = parse16(buf+offs, alignIntel); unsigned ncomp = parse32(buf+offs+4, alignIntel); unsigned coffs = parse32(buf+offs+8, alignIntel); switch(tag) { case 0x8769: // EXIF subIFD offset exifSubIFD = ifdOffset + coffs; break; case 0x10F: case 0x110: case 0x10E: break; case 0x132: // EXIF/TIFF date/time of image copyEXIFString(&result.dateTimeModified, ncomp, ifdOffset, coffs, buf); break; case 0x8825: gpsIFD = ifdOffset + coffs; break; } offs += 12; } if(!exifSubIFD) return 0; // At the EXIF SubIFD, read the rest of the EXIF tags offs = exifSubIFD; nentries = parse16(buf+offs, alignIntel); offs += 2; for(int j = 0; j < nentries; j++) { unsigned short tag = parse16(buf+offs, alignIntel); unsigned ncomp = parse32(buf+offs+4, alignIntel); unsigned coffs = parse32(buf+offs+8, alignIntel); switch(tag) { case 0x9003: // original image date/time string copyEXIFString(&result.dateTimeOriginal, ncomp, ifdOffset, coffs, buf); break; case 0x920a: case 0x829D: case 0x829A: break; } offs += 12; } return 0; }
//对EXIF段进行解析 'buf'必须开始于EXIF TIFF,为字节"Exif\0\0" ;'len'为buffer的长度 int EXIFInfo::parseFromEXIFSegment(const unsigned char *buf, unsigned len) { bool alignIntel = true; // 字节对齐方式 (定义在 EXIF 头信息中) unsigned offs = 0; // 缓冲区中当前偏移量 if (!buf || len < 6) return PARSE_EXIF_ERROR_NO_EXIF; if (!std::equal(buf, buf + 6, "Exif\0\0")) return PARSE_EXIF_ERROR_NO_EXIF; offs += 6; // 现在解析 TIFF 头. 前两个字节为"II"或者"MM"表示Intel或者 Motorola字节对齐方式 // 安全性检查,接下来的两个字节必须是0x2a. 最后4个字节是第一个IFD的偏移量 // 将会被添加到全局偏移计数器中 // 这块的最小大小8个字节: // 2 bytes: 'II' or 'MM' // 2 bytes: 0x002a // 4 bytes: 第一个IDF的偏移量 // ----------------------------- // 8 bytes if (offs + 8 > len) return PARSE_EXIF_ERROR_CORRUPT; unsigned tiff_header_start = offs; if (buf[offs] == 'I' && buf[offs + 1] == 'I') alignIntel = true; else { if (buf[offs] == 'M' && buf[offs + 1] == 'M') alignIntel = false; else return PARSE_EXIF_ERROR_UNKNOWN_BYTEALIGN; } this->m_byte_align = alignIntel; offs += 2; if (0x2a != parse16(buf + offs, alignIntel)) return PARSE_EXIF_ERROR_CORRUPT; offs += 2; unsigned first_ifd_offset = parse32(buf + offs, alignIntel); offs += first_ifd_offset - 4; if (offs >= len) return PARSE_EXIF_ERROR_CORRUPT; // 先解析第一个图像文件目录 (IFD0,主图像). // 一个IFD包含多个12字节的目录条目 // IFD段的前2个字节包含目录条数 // 最后4个字节包含一个偏移量对下一个IFD // 这表明IFD段必须包含6 + 12 * num个字节数 if (offs + 2 > len) return PARSE_EXIF_ERROR_CORRUPT; int num_entries = parse16(buf + offs, alignIntel); if (offs + 6 + 12 * num_entries > len) return PARSE_EXIF_ERROR_CORRUPT; offs += 2; unsigned exif_sub_ifd_offset = len; unsigned gps_sub_ifd_offset = len; while (--num_entries >= 0) { IFEntry result = parseIFEntry(buf, offs, alignIntel, tiff_header_start, len); offs += 12; switch (result.tag) { case 0x102: // 取样数 if (result.format == 3) this->m_bits_per_sample = result.val_16; break; case 0x10E: // 图像描述 if (result.format == 2) this->m_image_description = result.val_string; break; case 0x10F: // 相机制造商 if (result.format == 2) this->m_make = result.val_string; break; case 0x110: // 相机型号 if (result.format == 2) this->m_model = result.val_string; break; case 0x112: // 图片方向 if (result.format == 3) this->m_orientation = result.val_16; break; case 0x131: // 使用软件 if (result.format == 2) this->m_software = result.val_string; break; case 0x132: // EXIF/TIFF 文件修改的日期与时间 if (result.format == 2) this->m_datetime = result.val_string; break; case 0x8298: // 文件的版权信息 if (result.format == 2) this->m_copyright = result.val_string; break; case 0x8825: // GPS IFS 偏移量 gps_sub_ifd_offset = tiff_header_start + result.data; break; case 0x8769: // EXIF SubIFD 偏移量 exif_sub_ifd_offset = tiff_header_start + result.data; break; } } // 如果存在,则跳转到EXIF SubIFD,解析其所有信息。 // 这边要注意的是EXIF SubIFD可能不存在 // EXIF SubIFD包含了很多有用的信息 if (exif_sub_ifd_offset + 4 <= len) { offs = exif_sub_ifd_offset; int num_entries = parse16(buf + offs, alignIntel); if (offs + 6 + 12 * num_entries > len) return PARSE_EXIF_ERROR_CORRUPT; offs += 2; while (--num_entries >= 0) { IFEntry result = parseIFEntry(buf, offs, alignIntel, tiff_header_start, len); switch (result.tag) { case 0x829a: // 曝光时间,单位秒 if (result.format == 5) this->m_exposure_time = result.val_rational; break; case 0x829d: // 相机光圈数 if (result.format == 5) this->m_f_number = result.val_rational; break; case 0x8827: // ISO 感光度 if (result.format == 3) this->m_iso_speed_ratings = result.val_16; break; case 0x9003: //原始文件的日期和时间 if (result.format == 2) this->m_datetime_original = result.val_string; break; case 0x9004: // 数字化的日期和时间 if (result.format == 2) this->m_datetime_digitized = result.val_string; break; case 0x9201: // 快门速度(曝光时间的倒数) if (result.format == 5) this->m_shutter_speed_value = result.val_rational; break; case 0x9204: // 曝光补偿值 if (result.format == 5) this->m_exposure_bias_value = result.val_rational; break; case 0x9206: // 对焦点的距离 if (result.format == 5) this->m_subject_distance = result.val_rational; break; case 0x9209: // 是否使用闪光 if (result.format == 3) this->m_flash = result.data ? 1 : 0; break; case 0x920a: // 焦距 if (result.format == 5) this->m_focal_length = result.val_rational; break; case 0x9207: // 测光模式 if (result.format == 3) this->m_metering_mode = result.val_16; break; case 0x9291: // 图像被拍摄的低于秒级的时间 if (result.format == 2) this->m_sub_sec_time_original = result.val_string; break; case 0xa002: // EXIF 图像宽度 if (result.format == 4) this->m_image_width = result.val_32; else if (result.format == 3) this->m_image_width = result.val_16; else this->m_image_width = result.data; break; case 0xa003: // EXIF 图像高度 if (result.format == 4) this->m_image_height = result.val_32; else if (result.format == 3) this->m_image_height = result.val_16; else this->m_image_height = result.data; break; case 0xa405: // 35mm等效焦距 if (result.format == 3) this->m_focal_length_in_35mm = result.val_16; break; } offs += 12; } } // 如果GPS存在则跳转到 GPS SubIFD 进行解析 if (gps_sub_ifd_offset + 4 <= len) { offs = gps_sub_ifd_offset; int num_entries = parse16(buf + offs, alignIntel); if (offs + 6 + 12 * num_entries > len) return PARSE_EXIF_ERROR_CORRUPT; offs += 2; while (--num_entries >= 0) { unsigned short tag = parse16(buf + offs, alignIntel); unsigned short format = parse16(buf + offs + 2, alignIntel); unsigned length = parse32(buf + offs + 4, alignIntel); unsigned data = parse32(buf + offs + 8, alignIntel); switch (tag) { case 1: // GPS 南 或者 北 this->GeoLocation.LatComponents.direction = *(buf + offs + 8); if ('S' == this->GeoLocation.LatComponents.direction) this->GeoLocation.latitude = -this->GeoLocation.latitude; break; case 2: // GPS 纬度信息 if (format == 5 && length == 3) { this->GeoLocation.LatComponents.degrees = parseEXIFRational(buf + data + tiff_header_start, alignIntel); this->GeoLocation.LatComponents.minutes = parseEXIFRational(buf + data + tiff_header_start + 8, alignIntel); this->GeoLocation.LatComponents.seconds = parseEXIFRational(buf + data + tiff_header_start + 16, alignIntel); this->GeoLocation.latitude = this->GeoLocation.LatComponents.degrees + this->GeoLocation.LatComponents.minutes / 60 + this->GeoLocation.LatComponents.seconds / 3600; if ('S' == this->GeoLocation.LatComponents.direction) this->GeoLocation.latitude = -this->GeoLocation.latitude; } break; case 3: // GPS 东或西 this->GeoLocation.LonComponents.direction = *(buf + offs + 8); if ('W' == this->GeoLocation.LonComponents.direction) this->GeoLocation.longitude = -this->GeoLocation.longitude; break; case 4: // GPS 经度信息 if (format == 5 && length == 3) { this->GeoLocation.LonComponents.degrees = parseEXIFRational(buf + data + tiff_header_start, alignIntel); this->GeoLocation.LonComponents.minutes = parseEXIFRational(buf + data + tiff_header_start + 8, alignIntel); this->GeoLocation.LonComponents.seconds = parseEXIFRational(buf + data + tiff_header_start + 16, alignIntel); this->GeoLocation.longitude = this->GeoLocation.LonComponents.degrees + this->GeoLocation.LonComponents.minutes / 60 + this->GeoLocation.LonComponents.seconds / 3600; if ('W' == this->GeoLocation.LonComponents.direction) this->GeoLocation.longitude = -this->GeoLocation.longitude; } break; case 5: // 相对于海平面的高度 this->GeoLocation.altitude_ref = *(buf + offs + 8); if (1 == this->GeoLocation.altitude_ref) this->GeoLocation.altitude = -this->GeoLocation.altitude; break; case 6: // 与海平面的相对位置 if (format == 5) { this->GeoLocation.altitude = parseEXIFRational(buf + data + tiff_header_start, alignIntel); if (1 == this->GeoLocation.altitude_ref) this->GeoLocation.altitude = -this->GeoLocation.altitude; } break; } offs += 12; } } return PARSE_EXIF_SUCCESS; }