text: // Copyright 2018 The Chromium OS Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  //
  #include "camera3_test/camera3_exif_validator.h"
  
  #include <algorithm>
  #include <limits>
  #include <string>
  #include <vector>
  
  #include <gtest/gtest.h>
  #include <jpeglib.h>
  
  namespace camera3_test {
  
  enum {
    ORIENTATION_UNDEFINED,
    ORIENTATION_NORMAL,
    ORIENTATION_FLIP_HORIZONTAL,
    ORIENTATION_ROTATE_180,
    ORIENTATION_FLIP_VERTICAL,
    ORIENTATION_TRANSPOSE,
    ORIENTATION_ROTATE_90,
    ORIENTATION_TRANSVERSE,
    ORIENTATION_ROTATE_270,
  };
  
  static int32_t GetJpegInfo(uint8_t* data,
                             size_t size,
                             ResolutionInfo* resolution,
                             ExifData** exif_data) {
  #define JPEG_APP1 (JPEG_APP0 + 1)
    const unsigned int kMaxMarkerLength = 0xffff;
    struct jpeg_decompress_struct jpeg_info;
    struct jpeg_error_mgr jpeg_error;
    jpeg_info.err = jpeg_std_error(&jpeg_error);
    jpeg_create_decompress(&jpeg_info);
    jpeg_mem_src(&jpeg_info, data, size);
    jpeg_save_markers(&jpeg_info, JPEG_APP1, kMaxMarkerLength);
    if (jpeg_read_header(&jpeg_info, TRUE) != JPEG_HEADER_OK) {
      jpeg_destroy_decompress(&jpeg_info);
      return -EINVAL;
    }
    if (resolution) {
      *resolution = ResolutionInfo(jpeg_info.image_width, jpeg_info.image_height);
    }
    if (exif_data) {
      for (auto marker = jpeg_info.marker_list; marker != nullptr;
           marker = marker->next) {
        if (marker->marker == JPEG_APP1) {
          *exif_data = exif_data_new_from_data(marker->data, marker->data_length);
          break;
        }
      }
    }
    jpeg_destroy_decompress(&jpeg_info);
    return 0;
  }
  
  static int32_t GetExifOrientationInDegree(int32_t exif_orientation) {
    switch (exif_orientation) {
      case ORIENTATION_NORMAL:
        return 0;
      case ORIENTATION_ROTATE_90:
        return 90;
      case ORIENTATION_ROTATE_180:
        return 180;
      case ORIENTATION_ROTATE_270:
        return 270;
      default:
        ADD_FAILURE() << "It is impossible to get non 0, 90, 180, 270 degress "
                         "exif info based on the request orientation range";
        return 0;
    }
  }
  
  static int32_t GetExifTagInteger(const ExifData& exif_data,
                                   const ExifIfd& ifd,
                                   const ExifTag& tag,
                                   const ExifByteOrder& byte_order) {
    ExifEntry* entry = exif_data_get_entry((&exif_data), tag);
    if (!entry) {
      ADD_FAILURE() << "Failed to get EXIF tag "
                    << exif_tag_get_name_in_ifd(tag, ifd);
      return -EINVAL;
    }
    switch (entry->format) {
      case EXIF_FORMAT_SHORT:
        return exif_get_short(entry->data, byte_order);
      case EXIF_FORMAT_LONG:
        return exif_get_long(entry->data, byte_order);
      case EXIF_FORMAT_SLONG:
        return exif_get_slong(entry->data, byte_order);
      default:
        ADD_FAILURE() << "Invalid EXIF entry format " << entry->format;
        return -EINVAL;
    }
  }
  
  static const char* GetExifTagString(const ExifData& exif_data,
                                      const ExifIfd& ifd,
                                      const ExifTag& tag) {
    ExifEntry* entry = exif_data_get_entry((&exif_data), tag);
    if (!entry) {
      ADD_FAILURE() << "Failed to get EXIF tag "
                    << exif_tag_get_name_in_ifd(tag, ifd);
      return nullptr;
    }
    EXPECT_EQ(EXIF_FORMAT_ASCII, entry->format)
        << "Invalid EXIF entry string format " << entry->format;
    return reinterpret_cast<char*>(entry->data);
  }
  
  static float GetExifTagFloat(const ExifData& exif_data,
                               const ExifIfd& ifd,
                               const ExifTag& tag,
                               const ExifByteOrder& byte_order) {
    ExifEntry* entry = exif_data_get_entry((&exif_data), tag);
    if (!entry) {
      ADD_FAILURE() << "Failed to get EXIF tag "
                    << exif_tag_get_name_in_ifd(tag, ifd);
      return -EINVAL;
    }
    switch (entry->format) {
      case EXIF_FORMAT_RATIONAL: {
        ExifRational rational = exif_get_rational(entry->data, byte_order);
        return static_cast<float>(rational.numerator) /
               static_cast<float>(rational.denominator);
      }
      case EXIF_FORMAT_SRATIONAL: {
        ExifSRational srational = exif_get_srational(entry->data, byte_order);
        return static_cast<float>(srational.numerator) /
               static_cast<float>(srational.denominator);
      }
      default:
        ADD_FAILURE() << "Invalid EXIF entry format " << entry->format;
        return -EINVAL;
    }
  }
  
  Camera3ExifValidator::JpegExifInfo::JpegExifInfo(
      const ScopedBufferHandle& buffer, size_t size)
      : buffer_handle(buffer),
        buffer_size(size),
        buffer_addr(nullptr),
        jpeg_resolution(ResolutionInfo(0, 0)),
        exif_data(nullptr) {
    // Get JPEG image address and size
    if (Camera3TestGralloc::GetInstance()->Lock(*buffer_handle, 0, 0, 0, size, 1,
                                                &buffer_addr) != 0 ||
        !buffer_addr) {
      ADD_FAILURE() << "Failed to map buffer";
    }
  }
  
  Camera3ExifValidator::JpegExifInfo::~JpegExifInfo() {
    if (exif_data) {
      exif_data_unref(exif_data);
    }
    Camera3TestGralloc::GetInstance()->Unlock(*buffer_handle);
  }
  
  bool Camera3ExifValidator::JpegExifInfo::Initialize() {
    if (!buffer_addr) {
      return false;
    }
    auto jpeg_blob = reinterpret_cast<camera3_jpeg_blob_t*>(
        static_cast<uint8_t*>(buffer_addr) + buffer_size -
        sizeof(camera3_jpeg_blob_t));
    if (static_cast<void*>(jpeg_blob) < buffer_addr ||
        jpeg_blob->jpeg_blob_id != CAMERA3_JPEG_BLOB_ID) {
      ADD_FAILURE() << "Invalid JPEG BLOB ID";
      return false;
    }
    if (GetJpegInfo(static_cast<uint8_t*>(buffer_addr), jpeg_blob->jpeg_size,
                    &jpeg_resolution, &exif_data) != 0) {
      ADD_FAILURE() << "No valid JPEG image found in the buffer";
      return false;
    }
    return true;
  }
  
  static ResolutionInfo GetMetadataThumbnailSize(
      const camera_metadata_t& metadata) {
    const size_t kNumOfElementsInSizeEntry = 2;
    enum { SIZE_ENTRY_WIDTH_INDEX, SIZE_ENTRY_HEIGHT_INDEX };
    camera_metadata_ro_entry_t entry;
    if (find_camera_metadata_ro_entry(&metadata, ANDROID_JPEG_THUMBNAIL_SIZE,
                                      &entry)) {
      ADD_FAILURE() << "Cannot find the metadata ANDROID_JPEG_THUMBNAIL_SIZE";
      return ResolutionInfo(-1, -1);
    }
    if (entry.count != kNumOfElementsInSizeEntry) {
      ADD_FAILURE()
          << "Invalid entry count of metadata ANDROID_JPEG_THUMBNAIL_SIZE ("
          << entry.count << ")";
      return ResolutionInfo(-1, -1);
    }
    return ResolutionInfo(entry.data.i32[SIZE_ENTRY_WIDTH_INDEX],
                          entry.data.i32[SIZE_ENTRY_HEIGHT_INDEX]);
  }
  
  static int64_t GetMetadataInteger(const camera_metadata_t& metadata,
                                    int32_t key) {
    camera_metadata_ro_entry_t entry;
    if (find_camera_metadata_ro_entry(&metadata, key, &entry)) {
      ADD_FAILURE() << "Cannot find the metadata "
                    << get_camera_metadata_tag_name(key);
      return -EINVAL;
    }
    switch (entry.type) {
      case TYPE_BYTE:
        return entry.data.u8[0];
      case TYPE_INT32:
        return entry.data.i32[0];
      case TYPE_INT64:
        return entry.data.i64[0];
      default:
        ADD_FAILURE() << "Unexpected metadata entry type " << entry.type;
        return -EINVAL;
    }
  }
  
  static float GetMetadataKeyValueFloat(const camera_metadata_t& metadata,
                                        int32_t key) {
    camera_metadata_ro_entry_t entry;
    if (find_camera_metadata_ro_entry(&metadata, key, &entry)) {
      ADD_FAILURE() << "Cannot find the metadata "
                    << get_camera_metadata_tag_name(key);
      return -EINVAL;
    }
    if (entry.type != TYPE_FLOAT) {
      ADD_FAILURE() << "Unexpected metadata entry type " << entry.type;
      return -EINVAL;
    }
    return entry.data.f[0];
  }
  
  void Camera3ExifValidator::ValidateExifKeys(
      const ResolutionInfo& jpeg_resolution,
      const ExifTestData& exif_test_data,
      const ScopedBufferHandle& buffer,
      size_t buffer_size,
      const camera_metadata_t& metadata,
      const time_t& date_time) const {
    const int32_t kExifDateTimeStringLength = 19;
    const double kExifDateTimeErrorMarginSeconds = 60;
    const float kExifFocalLengthErrorMargin = 0.001f;
    const float kExifExposureTimeErrorMarginRation = 0.05f;
    const float kExifExposureTimeMinErrorMarginSeconds = 0.002f;
    const float kOneNanoSecond = 1e-9;
    const float kExifApertureErrorMargin = 0.001f;
    auto GetClosestValueInArray = [](std::vector<float> values, float target) {
      int min_idx = -1;
      float min_distance = std::numeric_limits<float>::max();
      for (size_t i = 0; i < values.size(); i++) {
        float distance = std::abs(values[i] - target);
        if (min_distance > distance) {
          min_distance = distance;
          min_idx = i;
        }
      }
      return (min_idx >= 0) ? values[min_idx]
                            : -std::numeric_limits<float>::max();
    };
    auto SwapWidthAndHeight = [](ResolutionInfo* resolution) {
      *resolution = ResolutionInfo(resolution->Height(), resolution->Width());
    };
  
    JpegExifInfo jpeg_exif_info(buffer, buffer_size);
    ASSERT_TRUE(jpeg_exif_info.Initialize());
    ExifByteOrder byte_order = exif_data_get_byte_order(jpeg_exif_info.exif_data);
    const ExifData& exif_data = *jpeg_exif_info.exif_data;
    int32_t exif_orientation = GetExifTagInteger(
        exif_data, EXIF_IFD_0, EXIF_TAG_ORIENTATION, byte_order);
    EXPECT_LE(ORIENTATION_UNDEFINED, exif_orientation)
        << "Invalid EXIF orientation value";
    EXPECT_GE(ORIENTATION_ROTATE_270, exif_orientation)
        << "Invalid EXIF orientation value";
    EXPECT_TRUE(exif_orientation == ORIENTATION_UNDEFINED ||
                exif_test_data.orientation ==
                    GetExifOrientationInDegree(exif_orientation))
        << "EXIF orientaiton should match requested orientation";
  
    if (exif_test_data.thumbnail_resolution == ResolutionInfo(0, 0)) {
      EXPECT_EQ(nullptr, exif_data.data)
          << "JPEG shouldn't have thumbnail when thumbnail size is (0, 0)";
    } else {
      EXPECT_NE(nullptr, exif_data.data) << "No thumbnail found in JPEG image";
      ResolutionInfo expected_thumbnail_resolution(
          exif_test_data.thumbnail_resolution);
      if (exif_test_data.orientation % 180 == 90 &&
          exif_orientation == ORIENTATION_UNDEFINED) {
        // Device physically rotated image+thumbnail data
        // Expect thumbnail size to be also rotated
        SwapWidthAndHeight(&expected_thumbnail_resolution);
      }
      EXPECT_EQ(expected_thumbnail_resolution, GetMetadataThumbnailSize(metadata))
          << "JPEG thumbnail size result and request should match";
      if (exif_data.data) {
        ResolutionInfo actual_thumbnail_resolution(0, 0);
        if (GetJpegInfo(exif_data.data, exif_data.size,
                        &actual_thumbnail_resolution, nullptr) != 0) {
          ADD_FAILURE() << "No valid thumbnail image found in the buffer";
        } else {
          EXPECT_EQ(expected_thumbnail_resolution, actual_thumbnail_resolution)
              << "EXIF thumbnail image size should match requested size";
        }
      }
    }
    EXPECT_EQ(exif_test_data.orientation,
              GetMetadataInteger(metadata, ANDROID_JPEG_ORIENTATION))
        << "JPEG orientation result and request should match";
    EXPECT_EQ(exif_test_data.jpeg_quality,
              GetMetadataInteger(metadata, ANDROID_JPEG_QUALITY))
        << "JPEG quality result and request should match";
    EXPECT_EQ(exif_test_data.thumbnail_quality,
              GetMetadataInteger(metadata, ANDROID_JPEG_THUMBNAIL_QUALITY))
        << "JPEG thumbnail quality result and request should match";
    // TAG_IMAGE_WIDTH and TAG_IMAGE_LENGTH and TAG_ORIENTATION.
    // Orientation and exif width/height need to be tested carefully, two cases:
    // 1. Device rotates the image buffer physically, then exif width/height may
    // not match the requested still capture size, we need swap them to check.
    // 2. Device uses the exif tag to record the image orientation, it doesn't
    // rotate the jpeg image buffer itself. In this case, the exif width/height
    // should always match the requested still capture size, and the exif
    // orientation should always match the requested orientation.
    ResolutionInfo expected_jpeg_size(jpeg_resolution);
    if (exif_test_data.orientation % 180 == 90 &&
        exif_orientation == ORIENTATION_UNDEFINED) {
      // Device captured image doesn't respect the requested orientation, which
      // means it rotates the image buffer physically. Then we should swap the
      // exif width/height accordingly to compare.
      SwapWidthAndHeight(&expected_jpeg_size);
    }
    EXPECT_EQ(
        expected_jpeg_size,
        ResolutionInfo(GetExifTagInteger(exif_data, EXIF_IFD_0,
                                         EXIF_TAG_IMAGE_WIDTH, byte_order),
                       GetExifTagInteger(exif_data, EXIF_IFD_0,
                                         EXIF_TAG_IMAGE_LENGTH, byte_order)))
        << "EXIF JPEG size should match requested size";
    EXPECT_EQ(
        expected_jpeg_size,
        ResolutionInfo(GetExifTagInteger(exif_data, EXIF_IFD_EXIF,
                                         EXIF_TAG_PIXEL_X_DIMENSION, byte_order),
                       GetExifTagInteger(exif_data, EXIF_IFD_EXIF,
                                         EXIF_TAG_PIXEL_Y_DIMENSION, byte_order)))
        << "EXIF JPEG pixel dimension should match requested size";
    EXPECT_EQ(expected_jpeg_size, jpeg_exif_info.jpeg_resolution)
        << "JPEG size result and request should match";
  
    // Validate date time between EXIF data and current time
    const char* exif_datetime_string =
        GetExifTagString(exif_data, EXIF_IFD_0, EXIF_TAG_DATE_TIME);
    if (exif_datetime_string) {
      EXPECT_EQ(kExifDateTimeStringLength, strlen(exif_datetime_string))
          << "EXIF dateTime is in wrong format";
      struct tm exif_tm = {};
      strptime(exif_datetime_string, "%Y:%m:%d %H:%M:%S", &exif_tm);
      time_t exif_time = mktime(&exif_tm);
      EXPECT_GT(kExifDateTimeErrorMarginSeconds, difftime(date_time, exif_time))
          << "Capture time deviates too much from the current time";
      const char* exif_datetime_digitized = GetExifTagString(
          exif_data, EXIF_IFD_EXIF, EXIF_TAG_DATE_TIME_DIGITIZED);
      if (exif_datetime_digitized) {
        EXPECT_EQ(kExifDateTimeStringLength, strlen(exif_datetime_digitized))
            << "EXIF digitizedTime is in wrong format";
        EXPECT_EQ(0, strncmp(exif_datetime_string, exif_datetime_digitized,
                             kExifDateTimeStringLength))
            << "EXIF dataTime should match digitizedTime";
      }
    }
  
    // Validate focal length between EXIF data and metadata
    std::vector<float> focal_lengths;
    if (cam_info_.GetAvailableFocalLengths(&focal_lengths) != 0 ||
        focal_lengths.empty()) {
      ADD_FAILURE() << "Failed to get available focal lengths";
    } else {
      float exif_focal_length = GetExifTagFloat(
          exif_data, EXIF_IFD_EXIF, EXIF_TAG_FOCAL_LENGTH, byte_order);
      EXPECT_GT(
          kExifFocalLengthErrorMargin,
          std::abs(GetClosestValueInArray(focal_lengths, exif_focal_length) -
                   exif_focal_length))
          << "EXIF focal length should be one of the available focal lengths";
      float result_focal_length =
          GetMetadataKeyValueFloat(metadata, ANDROID_LENS_FOCAL_LENGTH);
      EXPECT_LT(0.0f, result_focal_length)
          << "Result Focal length " << result_focal_length
          << " should be positive";
      EXPECT_NE(focal_lengths.end(),
                std::find(focal_lengths.begin(), focal_lengths.end(),
                          result_focal_length))
          << "Result Focal length should be one of the available focal lengths";
      EXPECT_GT(kExifFocalLengthErrorMargin,
                std::abs(result_focal_length - exif_focal_length))
          << "EXIF focal length should match capture result";
    }
  
    // Validate exposure time between EXIF data and metadata
    float exif_exposure_time = GetExifTagFloat(
        exif_data, EXIF_IFD_EXIF, EXIF_TAG_EXPOSURE_TIME, byte_order);
    if (exif_exposure_time > 0 &&
        cam_info_.IsKeyAvailable(ANDROID_SENSOR_EXPOSURE_TIME)) {
      float result_exposure_time = static_cast<float>(GetMetadataInteger(
                                       metadata, ANDROID_SENSOR_EXPOSURE_TIME)) *
                                   kOneNanoSecond;
      float tolerance =
          std::max(result_exposure_time * kExifExposureTimeErrorMarginRation,
                   kExifExposureTimeMinErrorMarginSeconds);
      EXPECT_GT(tolerance, std::abs(result_exposure_time - exif_exposure_time))
          << "Exif exposure time doesn't match";
    }
  
    // Validate aperture between EXIF data and metadata
    float exif_aperture =
        GetExifTagFloat(exif_data, EXIF_IFD_EXIF, EXIF_TAG_FNUMBER, byte_order);
    if (exif_aperture > 0 &&
        cam_info_.IsKeyAvailable(ANDROID_LENS_INFO_AVAILABLE_APERTURES)) {
      std::vector<float> apertures;
      if (cam_info_.GetAvailableApertures(&apertures) != 0 || apertures.empty()) {
        ADD_FAILURE() << "Failed to get available apertures";
      } else {
        float result_aperture =
            GetMetadataKeyValueFloat(metadata, ANDROID_LENS_APERTURE);
        EXPECT_GT(kExifApertureErrorMargin,
                  std::abs(GetClosestValueInArray(apertures, exif_aperture) -
                           exif_aperture))
            << "Aperture value should be one of the available apertures";
        EXPECT_GT(kExifApertureErrorMargin,
                  std::abs(result_aperture - exif_aperture))
            << "Exif aperture length should match capture result";
      }
    }
  
    // Verify EXIF TAG_FLASH is available
    GetExifTagInteger(exif_data, EXIF_IFD_EXIF, EXIF_TAG_FLASH, byte_order);
    // Verify EXIF TAG_WHITE_BALANCE is available
    GetExifTagInteger(exif_data, EXIF_IFD_EXIF, EXIF_TAG_WHITE_BALANCE,
                      byte_order);
    // TODO(hywu): For full devices, validate flash and AWB between EXIF and
    // metadata
  
    // Validate ISO between EXIF and metadata
    if (cam_info_.IsKeyAvailable(ANDROID_SENSOR_SENSITIVITY)) {
      int32_t iso = GetExifTagInteger(exif_data, EXIF_IFD_EXIF,
                                      EXIF_TAG_ISO_SPEED_RATINGS, byte_order);
      EXPECT_EQ(GetMetadataInteger(metadata, ANDROID_SENSOR_SENSITIVITY), iso)
          << "EXIF TAG_ISO is incorrect";
    }
  
    auto is_number = [&](const ExifIfd& ifd, const ExifTag& tag) {
      const char* c = GetExifTagString(exif_data, ifd, tag);
      const std::string s(c ? c : "");
      return !s.empty() && std::find_if(s.begin(), s.end(), [](char c) {
                             return !std::isdigit(c);
                           }) == s.end();
    };
    EXPECT_TRUE(is_number(EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME));
    EXPECT_TRUE(is_number(EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME_ORIGINAL));
    EXPECT_TRUE(is_number(EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME_DIGITIZED));
  }
  
  int Camera3ExifValidator::getExifOrientation(const ScopedBufferHandle& buffer,
                                               size_t buffer_size) {
    JpegExifInfo jpeg_exif_info(buffer, buffer_size);
    jpeg_exif_info.Initialize();
    ExifByteOrder byte_order = exif_data_get_byte_order(jpeg_exif_info.exif_data);
    const ExifData& exif_data = *jpeg_exif_info.exif_data;
    int32_t exif_orientation = GetExifTagInteger(
        exif_data, EXIF_IFD_0, EXIF_TAG_ORIENTATION, byte_order);
    return exif_orientation;
  }
  
  }  // namespace camera3_test
  