// // Main parsing function for an EXIF segment. // // PARAM: 'buf' start of the EXIF TIFF, which must be the bytes "Exif\0\0". // PARAM: 'len' length of buffer // int easyexif::EXIFInfo::parseFromEXIFSegment(const unsigned char *buf, unsigned len) { bool alignIntel = true; // byte alignment (defined in EXIF header) unsigned offs = 0; // current offset into buffer 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; // Now parsing the TIFF header. The first two bytes are either "II" or // "MM" for Intel or Motorola byte alignment. Sanity check by parsing // the unsigned short that follows, making sure it equals 0x2a. The // last 4 bytes are an offset into the first IFD, which are added to // the global offset counter. For this block, we expect the following // minimum size: // 2 bytes: 'II' or 'MM' // 2 bytes: 0x002a // 4 bytes: offset to first 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->ByteAlign = alignIntel; offs += 2; if (0x2a != parse_value<uint16_t>(buf + offs, alignIntel)) return PARSE_EXIF_ERROR_CORRUPT; offs += 2; unsigned first_ifd_offset = parse_value<uint32_t>(buf + offs, alignIntel); offs += first_ifd_offset - 4; if (offs >= len) return PARSE_EXIF_ERROR_CORRUPT; // Now parsing the first Image File Directory (IFD0, for the main image). // An IFD consists of a variable number of 12-byte directory entries. The // first two bytes of the IFD section contain the number of directory // entries in the section. The last 4 bytes of the IFD contain an offset // to the next IFD, which means this IFD must contain exactly 6 + 12 * num // bytes of data. if (offs + 2 > len) return PARSE_EXIF_ERROR_CORRUPT; int num_entries = parse_value<uint16_t>(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; readTag(result, this); switch(result.tag()) { case 0x8825: // GPS IFS offset gps_sub_ifd_offset = tiff_header_start + result.data(); break; case 0x8769: // EXIF SubIFD offset exif_sub_ifd_offset = tiff_header_start + result.data(); break; } } // Jump to the EXIF SubIFD if it exists and parse all the information // there. Note that it's possible that the EXIF SubIFD doesn't exist. // The EXIF SubIFD contains most of the interesting information that a // typical user might want. if (exif_sub_ifd_offset + 4 <= len) { offs = exif_sub_ifd_offset; int num_entries = parse_value<uint16_t>(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); readTag(result, this); offs += 12; } } // Jump to the GPS SubIFD if it exists and parse all the information // there. Note that it's possible that the GPS SubIFD doesn't exist. if (gps_sub_ifd_offset + 4 <= len) { offs = gps_sub_ifd_offset; int num_entries = parse_value<uint16_t>(buf + offs, alignIntel); if (offs + 6 + 12 * num_entries > len) return PARSE_EXIF_ERROR_CORRUPT; offs += 2; while (--num_entries >= 0) { unsigned short tag, format; unsigned length, data; parseIFEntryHeader(buf + offs, alignIntel, tag, format, length, data); switch (tag) { case 1: // GPS north or south this->GeoLocation.LatComponents.direction = *(buf + offs + 8); if (this->GeoLocation.LatComponents.direction == 0) { this->GeoLocation.LatComponents.direction = '?'; } if ('S' == this->GeoLocation.LatComponents.direction) { this->GeoLocation.Latitude = -this->GeoLocation.Latitude; } break; case 2: // GPS latitude if (format == 5 && length == 3) { this->GeoLocation.LatComponents.degrees = parse_value<Rational>( buf + data + tiff_header_start, alignIntel); this->GeoLocation.LatComponents.minutes = parse_value<Rational>( buf + data + tiff_header_start + 8, alignIntel); this->GeoLocation.LatComponents.seconds = parse_value<Rational>( 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 east or west this->GeoLocation.LonComponents.direction = *(buf + offs + 8); if (this->GeoLocation.LonComponents.direction == 0) { this->GeoLocation.LonComponents.direction = '?'; } if ('W' == this->GeoLocation.LonComponents.direction) { this->GeoLocation.Longitude = -this->GeoLocation.Longitude; } break; case 4: // GPS longitude if (format == 5 && length == 3) { this->GeoLocation.LonComponents.degrees = parse_value<Rational>( buf + data + tiff_header_start, alignIntel); this->GeoLocation.LonComponents.minutes = parse_value<Rational>( buf + data + tiff_header_start + 8, alignIntel); this->GeoLocation.LonComponents.seconds = parse_value<Rational>( 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: // GPS altitude reference (below or above sea level) this->GeoLocation.AltitudeRef = *(buf + offs + 8); if (1 == this->GeoLocation.AltitudeRef) { this->GeoLocation.Altitude = -this->GeoLocation.Altitude; } break; case 6: // GPS altitude if (format == 5) { this->GeoLocation.Altitude = parse_value<Rational>( buf + data + tiff_header_start, alignIntel); if (1 == this->GeoLocation.AltitudeRef) { this->GeoLocation.Altitude = -this->GeoLocation.Altitude; } } break; case 11: // GPS degree of precision (DOP) if (format == 5) { this->GeoLocation.DOP = parse_value<Rational>( buf + data + tiff_header_start, alignIntel); } break; } offs += 12; } } return PARSE_EXIF_SUCCESS; }
// // Main parsing function for an EXIF segment. // // PARAM: 'buf' start of the EXIF TIFF, which must be the bytes "Exif\0\0". // PARAM: 'len' length of buffer // int easyexif::EXIFInfo::parseFromEXIFSegment(const unsigned char *buf, unsigned len) { bool alignIntel = true; // byte alignment (defined in EXIF header) unsigned offs = 0; // current offset into buffer 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; // Now parsing the TIFF header. The first two bytes are either "II" or // "MM" for Intel or Motorola byte alignment. Sanity check by parsing // the unsigned short that follows, making sure it equals 0x2a. The // last 4 bytes are an offset into the first IFD, which are added to // the global offset counter. For this block, we expect the following // minimum size: // 2 bytes: 'II' or 'MM' // 2 bytes: 0x002a // 4 bytes: offset to first 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->ByteAlign = alignIntel; offs += 2; if (0x2a != parse_value<uint16_t>(buf+offs, alignIntel)) return PARSE_EXIF_ERROR_CORRUPT; offs += 2; unsigned first_ifd_offset = parse_value<uint32_t>(buf + offs, alignIntel); offs += first_ifd_offset - 4; if (offs >= len) return PARSE_EXIF_ERROR_CORRUPT; // Now parsing the first Image File Directory (IFD0, for the main image). // An IFD consists of a variable number of 12-byte directory entries. The // first two bytes of the IFD section contain the number of directory // entries in the section. The last 4 bytes of the IFD contain an offset // to the next IFD, which means this IFD must contain exactly 6 + 12 * num // bytes of data. if (offs + 2 > len) return PARSE_EXIF_ERROR_CORRUPT; int num_entries = parse_value<uint16_t>(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: // Bits per sample if (result.format() == 3) this->BitsPerSample = result.val_short().front(); break; case 0x10E: // Image description if (result.format() == 2) this->ImageDescription = result.val_string(); break; case 0x10F: // Digicam make if (result.format() == 2) this->Make = result.val_string(); break; case 0x110: // Digicam model if (result.format() == 2) this->Model = result.val_string(); break; case 0x112: // Orientation of image if (result.format() == 3) this->Orientation = result.val_short().front(); break; case 0x131: // Software used for image if (result.format() == 2) this->Software = result.val_string(); break; case 0x132: // EXIF/TIFF date/time of image modification if (result.format() == 2) this->DateTime = result.val_string(); break; case 0x8298: // Copyright information if (result.format() == 2) this->Copyright = result.val_string(); break; case 0x8825: // GPS IFS offset gps_sub_ifd_offset = tiff_header_start + result.data(); break; case 0x8769: // EXIF SubIFD offset exif_sub_ifd_offset = tiff_header_start + result.data(); break; } } // Jump to the EXIF SubIFD if it exists and parse all the information // there. Note that it's possible that the EXIF SubIFD doesn't exist. // The EXIF SubIFD contains most of the interesting information that a // typical user might want. if (exif_sub_ifd_offset + 4 <= len) { offs = exif_sub_ifd_offset; int num_entries = parse_value<uint16_t>(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: // Exposure time in seconds if (result.format() == 5) this->ExposureTime = result.val_rational().front(); break; case 0x829d: // FNumber if (result.format() == 5) this->FNumber = result.val_rational().front(); break; case 0x8827: // ISO Speed Rating if (result.format() == 3) this->ISOSpeedRatings = result.val_short().front(); break; case 0x9003: // Original date and time if (result.format() == 2) this->DateTimeOriginal = result.val_string(); break; case 0x9004: // Digitization date and time if (result.format() == 2) this->DateTimeDigitized = result.val_string(); break; case 0x9201: // Shutter speed value if (result.format() == 5) this->ShutterSpeedValue = result.val_rational().front(); break; case 0x9204: // Exposure bias value if (result.format() == 5) this->ExposureBiasValue = result.val_rational().front(); break; case 0x9206: // Subject distance if (result.format() == 5) this->SubjectDistance = result.val_rational().front(); break; case 0x9209: // Flash used if (result.format() == 3) this->Flash = result.data() ? 1 : 0; break; case 0x920a: // Focal length if (result.format() == 5) this->FocalLength = result.val_rational().front(); break; case 0x9207: // Metering mode if (result.format() == 3) this->MeteringMode = result.val_short().front(); break; case 0x9291: // Subsecond original time if (result.format() == 2) this->SubSecTimeOriginal = result.val_string(); break; case 0xa002: // EXIF Image width if (result.format() == 4) this->ImageWidth = result.val_long().front(); if (result.format() == 3) this->ImageWidth = result.val_short().front(); break; case 0xa003: // EXIF Image height if (result.format() == 4) this->ImageHeight = result.val_long().front(); if (result.format() == 3) this->ImageHeight = result.val_short().front(); break; case 0xa405: // Focal length in 35mm film if (result.format() == 3) this->FocalLengthIn35mm = result.val_short().front(); break; case 0xa432: if (result.format() == 5) { this->LensInfo.FocalLengthMin = result.val_rational()[0]; this->LensInfo.FocalLengthMax = result.val_rational()[1]; this->LensInfo.FStopMin = result.val_rational()[2]; this->LensInfo.FStopMax = result.val_rational()[3]; } break; case 0xa433: if (result.format() == 2) { this->LensInfo.Make = result.val_string(); } break; case 0xa434: if (result.format() == 2) { this->LensInfo.Model = result.val_string(); } break; } offs += 12; } } // Jump to the GPS SubIFD if it exists and parse all the information // there. Note that it's possible that the GPS SubIFD doesn't exist. if (gps_sub_ifd_offset + 4 <= len) { offs = gps_sub_ifd_offset; int num_entries = parse_value<uint16_t>(buf + offs, alignIntel); if (offs + 6 + 12 * num_entries > len) return PARSE_EXIF_ERROR_CORRUPT; offs += 2; while (--num_entries >= 0) { unsigned short tag, format; unsigned length, data; parseIFEntryHeader(buf + offs, alignIntel, tag, format, length, data); switch(tag) { case 1: // GPS north or south this->GeoLocation.LatComponents.direction = *(buf + offs + 8); if ('S' == this->GeoLocation.LatComponents.direction) this->GeoLocation.Latitude = -this->GeoLocation.Latitude; break; case 2: // GPS latitude if (format == 5 && length == 3) { this->GeoLocation.LatComponents.degrees = parse_value<Rational>(buf + data + tiff_header_start, alignIntel); this->GeoLocation.LatComponents.minutes = parse_value<Rational>(buf + data + tiff_header_start + 8, alignIntel); this->GeoLocation.LatComponents.seconds = parse_value<Rational>(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 east or west this->GeoLocation.LonComponents.direction = *(buf + offs + 8); if ('W' == this->GeoLocation.LonComponents.direction) this->GeoLocation.Longitude = -this->GeoLocation.Longitude; break; case 4: // GPS longitude if (format == 5 && length == 3) { this->GeoLocation.LonComponents.degrees = parse_value<Rational>(buf + data + tiff_header_start, alignIntel); this->GeoLocation.LonComponents.minutes = parse_value<Rational>(buf + data + tiff_header_start + 8, alignIntel); this->GeoLocation.LonComponents.seconds = parse_value<Rational>(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: // GPS altitude reference (below or above sea level) this->GeoLocation.AltitudeRef = *(buf + offs + 8); if (1 == this->GeoLocation.AltitudeRef) this->GeoLocation.Altitude = -this->GeoLocation.Altitude; break; case 6: // GPS altitude reference if (format == 5) { this->GeoLocation.Altitude = parse_value<Rational>(buf + data + tiff_header_start, alignIntel); if (1 == this->GeoLocation.AltitudeRef) this->GeoLocation.Altitude = -this->GeoLocation.Altitude; } break; } offs += 12; } } return PARSE_EXIF_SUCCESS; }
void readTag(IFEntry & result, easyexif::EXIFInfo * exif) { switch (result.tag()) { case 0x102: // Bits per sample if (result.format() == 3) exif->BitsPerSample = result.val_short().front(); break; case 0x10E: // Image description if (result.format() == 2) exif->ImageDescription = result.val_string(); break; case 0x10F: // Digicam make if (result.format() == 2) exif->Make = result.val_string(); break; case 0x110: // Digicam model if (result.format() == 2) exif->Model = result.val_string(); break; case 0x112: // Orientation of image if (result.format() == 3) exif->Orientation = result.val_short().front(); break; case 0x131: // Software used for image if (result.format() == 2) exif->Software = result.val_string(); break; case 0x132: // EXIF/TIFF date/time of image modification if (result.format() == 2) exif->DateTime = result.val_string(); break; case 0x8298: // Copyright information if (result.format() == 2) exif->Copyright = result.val_string(); break; case 0x829a: // Exposure time in seconds if (result.format() == 5) exif->ExposureTime = result.val_rational().front(); break; case 0x829d: // FNumber if (result.format() == 5) exif->FNumber = result.val_rational().front(); break; case 0x8827: // ISO Speed Rating if (result.format() == 3) exif->ISOSpeedRatings = result.val_short().front(); break; case 0x9003: // Original date and time if (result.format() == 2) exif->DateTimeOriginal = result.val_string(); break; case 0x9004: // Digitization date and time if (result.format() == 2) exif->DateTimeDigitized = result.val_string(); break; case 0x9201: // Shutter speed value if (result.format() == 5) exif->ShutterSpeedValue = result.val_rational().front(); break; case 0x9204: // Exposure bias value if (result.format() == 5) exif->ExposureBiasValue = result.val_rational().front(); break; case 0x9206: // Subject distance if (result.format() == 5) exif->SubjectDistance = result.val_rational().front(); break; case 0x9209: // Flash used if (result.format() == 3) exif->Flash = result.data() ? 1 : 0; break; case 0x920a: // Focal length if (result.format() == 5) exif->FocalLength = result.val_rational().front(); break; case 0x9207: // Metering mode if (result.format() == 3) exif->MeteringMode = result.val_short().front(); break; case 0x9291: // Subsecond original time if (result.format() == 2) exif->SubSecTimeOriginal = result.val_string(); break; case 0xa002: // EXIF Image width if (result.format() == 4) exif->ImageWidth = result.val_long().front(); if (result.format() == 3) exif->ImageWidth = result.val_short().front(); break; case 0xa003: // EXIF Image height if (result.format() == 4) exif->ImageHeight = result.val_long().front(); if (result.format() == 3) exif->ImageHeight = result.val_short().front(); break; case 0xa20e: // EXIF Focal plane X-resolution if (result.format() == 5) { exif->LensInfo.FocalPlaneXResolution = result.val_rational()[0]; } break; case 0xa20f: // EXIF Focal plane Y-resolution if (result.format() == 5) { exif->LensInfo.FocalPlaneYResolution = result.val_rational()[0]; } break; case 0xa405: // Focal length in 35mm film if (result.format() == 3) exif->FocalLengthIn35mm = result.val_short().front(); break; case 0xa420: if (result.format() == 2) exif->ImageUniqueID = result.val_string(); break; case 0xa432: // Focal length and FStop. if (result.format() == 5) { exif->LensInfo.FocalLengthMin = result.val_rational()[0]; exif->LensInfo.FocalLengthMax = result.val_rational()[1]; exif->LensInfo.FStopMin = result.val_rational()[2]; exif->LensInfo.FStopMax = result.val_rational()[3]; } break; case 0xa433: // Lens make. if (result.format() == 2) { exif->LensInfo.Make = result.val_string(); } break; case 0xa434: // Lens model. if (result.format() == 2) { exif->LensInfo.Model = result.val_string(); } break; } }