A bunch of improvements to recoverymap calculations.

* Add proper color space conversions for map generation
* Add proper luminance calculation for map generation
* Add PQ encode/decode support
* Add structs for handling JPEG/R metadata, including HDR10 stuff
* Fill in HDR10 metadata, except for ST2086 info
* Update decode output from P010 to RGBA 1010102
* Update "hdr ratio" to "range scaling factor"

Bug: 252835416
Test: builds
No-Typo-Check: incorrectly interpretting code as a comment
Change-Id: I67d7be6d2a7821108d5f7ed26dd65285684e80f4
diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp
index 86eb8a8..64021b7 100644
--- a/libs/jpegrecoverymap/recoverymap.cpp
+++ b/libs/jpegrecoverymap/recoverymap.cpp
@@ -14,11 +14,6 @@
  * limitations under the License.
  */
 
-// TODO: need to clean up handling around hdr_ratio and passing it around
-// TODO: need to handle color space information; currently we assume everything
-// is srgb in.
-// TODO: handle PQ encode/decode (currently only HLG)
-
 #include <jpegrecoverymap/recoverymap.h>
 #include <jpegrecoverymap/jpegencoder.h>
 #include <jpegrecoverymap/jpegdecoder.h>
@@ -43,9 +38,21 @@
     }                           \
   }
 
+// The current JPEGR version that we encode to
+static const uint32_t kJpegrVersion = 1;
+
 // Map is quarter res / sixteenth size
 static const size_t kMapDimensionScaleFactor = 4;
 
+// TODO: fill in st2086 metadata
+static const st2086_metadata kSt2086Metadata = {
+  {0.0f, 0.0f},
+  {0.0f, 0.0f},
+  {0.0f, 0.0f},
+  {0.0f, 0.0f},
+  0,
+  1.0f,
+};
 
 /*
  * Helper function used for generating XMP metadata.
@@ -81,6 +88,7 @@
 
 status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
                                   jr_uncompressed_ptr uncompressed_yuv_420_image,
+                                  jpegr_transfer_function hdr_tf,
                                   jr_compressed_ptr dest,
                                   int quality,
                                   jr_exif_ptr /* exif */) {
@@ -99,10 +107,16 @@
     return ERROR_JPEGR_RESOLUTION_MISMATCH;
   }
 
+  jpegr_metadata metadata;
+  metadata.version = kJpegrVersion;
+  metadata.transferFunction = hdr_tf;
+  if (hdr_tf == JPEGR_TF_PQ) {
+    metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
+  }
+
   jpegr_uncompressed_struct map;
-  float hdr_ratio = 0.0f;
   JPEGR_CHECK(generateRecoveryMap(
-      uncompressed_yuv_420_image, uncompressed_p010_image, &map, hdr_ratio));
+      uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
   std::unique_ptr<uint8_t[]> map_data;
   map_data.reset(reinterpret_cast<uint8_t*>(map.data));
 
@@ -113,7 +127,7 @@
   JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
 
   JpegEncoder jpeg_encoder;
-  // TODO: ICC data - need color space information
+  // TODO: determine ICC data based on color gamut information
   if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data,
                                   uncompressed_yuv_420_image->width,
                                   uncompressed_yuv_420_image->height, quality, nullptr, 0)) {
@@ -123,7 +137,7 @@
   jpeg.data = jpeg_encoder.getCompressedImagePtr();
   jpeg.length = jpeg_encoder.getCompressedImageSize();
 
-  JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, hdr_ratio, dest));
+  JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &metadata, dest));
 
   return NO_ERROR;
 }
@@ -131,6 +145,7 @@
 status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
                                   jr_uncompressed_ptr uncompressed_yuv_420_image,
                                   jr_compressed_ptr compressed_jpeg_image,
+                                  jpegr_transfer_function hdr_tf,
                                   jr_compressed_ptr dest) {
   if (uncompressed_p010_image == nullptr
    || uncompressed_yuv_420_image == nullptr
@@ -144,10 +159,16 @@
     return ERROR_JPEGR_RESOLUTION_MISMATCH;
   }
 
+  jpegr_metadata metadata;
+  metadata.version = kJpegrVersion;
+  metadata.transferFunction = hdr_tf;
+  if (hdr_tf == JPEGR_TF_PQ) {
+    metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
+  }
+
   jpegr_uncompressed_struct map;
-  float hdr_ratio = 0.0f;
   JPEGR_CHECK(generateRecoveryMap(
-      uncompressed_yuv_420_image, uncompressed_p010_image, &map, hdr_ratio));
+      uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
   std::unique_ptr<uint8_t[]> map_data;
   map_data.reset(reinterpret_cast<uint8_t*>(map.data));
 
@@ -157,13 +178,14 @@
   compressed_map.data = compressed_map_data.get();
   JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
 
-  JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, hdr_ratio, dest));
+  JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest));
 
   return NO_ERROR;
 }
 
 status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
                                   jr_compressed_ptr compressed_jpeg_image,
+                                  jpegr_transfer_function hdr_tf,
                                   jr_compressed_ptr dest) {
   if (uncompressed_p010_image == nullptr
    || compressed_jpeg_image == nullptr
@@ -179,16 +201,23 @@
   uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
   uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
   uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
+  uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut;
 
   if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
    || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
     return ERROR_JPEGR_RESOLUTION_MISMATCH;
   }
 
+  jpegr_metadata metadata;
+  metadata.version = kJpegrVersion;
+  metadata.transferFunction = hdr_tf;
+  if (hdr_tf == JPEGR_TF_PQ) {
+    metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
+  }
+
   jpegr_uncompressed_struct map;
-  float hdr_ratio = 0.0f;
   JPEGR_CHECK(generateRecoveryMap(
-      &uncompressed_yuv_420_image, uncompressed_p010_image, &map, hdr_ratio));
+      &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
   std::unique_ptr<uint8_t[]> map_data;
   map_data.reset(reinterpret_cast<uint8_t*>(map.data));
 
@@ -198,7 +227,7 @@
   compressed_map.data = compressed_map_data.get();
   JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
 
-  JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, hdr_ratio, dest));
+  JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest));
 
   return NO_ERROR;
 }
@@ -212,7 +241,8 @@
   }
 
   jpegr_compressed_struct compressed_map;
-  JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
+  jpegr_metadata metadata;
+  JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map, &metadata));
 
   jpegr_uncompressed_struct map;
   JPEGR_CHECK(decompressRecoveryMap(&compressed_map, &map));
@@ -227,7 +257,7 @@
   uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
   uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
 
-  JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, dest));
+  JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, dest));
 
   return NO_ERROR;
 }
@@ -257,7 +287,7 @@
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
-  // TODO: should we have ICC data?
+  // TODO: should we have ICC data for the map?
   JpegEncoder jpeg_encoder;
   if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data, uncompressed_recovery_map->width,
                                   uncompressed_recovery_map->height, 85, nullptr, 0,
@@ -271,16 +301,18 @@
 
   memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize());
   dest->length = jpeg_encoder.getCompressedImageSize();
+  dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
 
   return NO_ERROR;
 }
 
 status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
                                           jr_uncompressed_ptr uncompressed_p010_image,
-                                          jr_uncompressed_ptr dest,
-                                          float &hdr_ratio) {
+                                          jr_metadata_ptr metadata,
+                                          jr_uncompressed_ptr dest) {
   if (uncompressed_yuv_420_image == nullptr
    || uncompressed_p010_image == nullptr
+   || metadata == nullptr
    || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
@@ -290,6 +322,11 @@
     return ERROR_JPEGR_RESOLUTION_MISMATCH;
   }
 
+  if (uncompressed_yuv_420_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED
+   || uncompressed_p010_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED) {
+    return ERROR_JPEGR_INVALID_COLORGAMUT;
+  }
+
   size_t image_width = uncompressed_yuv_420_image->width;
   size_t image_height = uncompressed_yuv_420_image->height;
   size_t map_width = image_width / kMapDimensionScaleFactor;
@@ -297,25 +334,63 @@
 
   dest->width = map_width;
   dest->height = map_height;
+  dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
   dest->data = new uint8_t[map_width * map_height];
   std::unique_ptr<uint8_t[]> map_data;
   map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
 
+  ColorTransformFn hdrInvOetf = nullptr;
+  switch (metadata->transferFunction) {
+    case JPEGR_TF_HLG:
+      hdrInvOetf = hlgInvOetf;
+      break;
+    case JPEGR_TF_PQ:
+      hdrInvOetf = pqInvOetf;
+      break;
+  }
+
+  ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(
+      uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut);
+
+  ColorCalculationFn luminanceFn = nullptr;
+  switch (uncompressed_yuv_420_image->colorGamut) {
+    case JPEGR_COLORGAMUT_BT709:
+      luminanceFn = srgbLuminance;
+      break;
+    case JPEGR_COLORGAMUT_P3:
+      luminanceFn = p3Luminance;
+      break;
+    case JPEGR_COLORGAMUT_BT2100:
+      luminanceFn = bt2100Luminance;
+      break;
+    case JPEGR_COLORGAMUT_UNSPECIFIED:
+      // Should be impossible to hit after input validation.
+      return ERROR_JPEGR_INVALID_COLORGAMUT;
+  }
+
   float hdr_y_nits_max = 0.0f;
+  double hdr_y_nits_avg = 0.0f;
   for (size_t y = 0; y < image_height; ++y) {
     for (size_t x = 0; x < image_width; ++x) {
       Color hdr_yuv_gamma = getP010Pixel(uncompressed_p010_image, x, y);
       Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
-      Color hdr_rgb = hlgInvOetf(hdr_rgb_gamma);
-      float hdr_y_nits = bt2100Luminance(hdr_rgb);
+      Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
+      hdr_rgb = hdrGamutConversionFn(hdr_rgb);
+      float hdr_y_nits = luminanceFn(hdr_rgb);
 
+      hdr_y_nits_avg += hdr_y_nits;
       if (hdr_y_nits > hdr_y_nits_max) {
         hdr_y_nits_max = hdr_y_nits;
       }
     }
   }
+  hdr_y_nits_avg /= image_width * image_height;
 
-  hdr_ratio = hdr_y_nits_max / kSdrWhiteNits;
+  metadata->rangeScalingFactor = hdr_y_nits_max / kSdrWhiteNits;
+  if (metadata->transferFunction == JPEGR_TF_PQ) {
+    metadata->hdr10Metadata.maxFALL = hdr_y_nits_avg;
+    metadata->hdr10Metadata.maxCLL = hdr_y_nits_max;
+  }
 
   for (size_t y = 0; y < map_height; ++y) {
     for (size_t x = 0; x < map_width; ++x) {
@@ -323,16 +398,17 @@
                                          kMapDimensionScaleFactor, x, y);
       Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
       Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
-      float sdr_y_nits = srgbLuminance(sdr_rgb);
+      float sdr_y_nits = luminanceFn(sdr_rgb);
 
       Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
       Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
-      Color hdr_rgb = hlgInvOetf(hdr_rgb_gamma);
-      float hdr_y_nits = bt2100Luminance(hdr_rgb);
+      Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
+      hdr_rgb = hdrGamutConversionFn(hdr_rgb);
+      float hdr_y_nits = luminanceFn(hdr_rgb);
 
       size_t pixel_idx =  x + y * map_width;
       reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
-          encodeRecovery(sdr_y_nits, hdr_y_nits, hdr_ratio);
+          encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->rangeScalingFactor);
     }
   }
 
@@ -342,17 +418,15 @@
 
 status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
                                        jr_uncompressed_ptr uncompressed_recovery_map,
+                                       jr_metadata_ptr metadata,
                                        jr_uncompressed_ptr dest) {
   if (uncompressed_yuv_420_image == nullptr
    || uncompressed_recovery_map == nullptr
+   || metadata == nullptr
    || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
-  // TODO: need to get this from the XMP; should probably be a function
-  // parameter
-  float hdr_ratio = 4.0f;
-
   size_t width = uncompressed_yuv_420_image->width;
   size_t height = uncompressed_yuv_420_image->height;
 
@@ -360,26 +434,31 @@
   dest->height = height;
   size_t pixel_count = width * height;
 
+  ColorTransformFn hdrOetf = nullptr;
+  switch (metadata->transferFunction) {
+    case JPEGR_TF_HLG:
+      hdrOetf = hlgOetf;
+      break;
+    case JPEGR_TF_PQ:
+      hdrOetf = pqOetf;
+      break;
+  }
+
   for (size_t y = 0; y < height; ++y) {
     for (size_t x = 0; x < width; ++x) {
-      size_t pixel_y_idx =  x + y * width;
+      Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
+      Color rgb_gamma_sdr = srgbYuvToRgb(yuv_gamma_sdr);
+      Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
 
-      size_t pixel_uv_idx = x / 2 + (y / 2) * (width / 2);
-
-      Color ypuv_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
-      Color rgbp_sdr = srgbYuvToRgb(ypuv_sdr);
-      Color rgb_sdr = srgbInvOetf(rgbp_sdr);
-
+      // TODO: determine map scaling factor based on actual map dims
       float recovery = sampleMap(uncompressed_recovery_map, kMapDimensionScaleFactor, x, y);
-      Color rgb_hdr = applyRecovery(rgb_sdr, recovery, hdr_ratio);
+      Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata->rangeScalingFactor);
 
-      Color rgbp_hdr = hlgOetf(rgb_hdr);
-      // TODO: actually just leave in RGB and convert to RGBA1010102 instead.
-      Color ypuv_hdr = srgbRgbToYuv(rgbp_hdr);
+      Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
+      uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr);
 
-      reinterpret_cast<uint16_t*>(dest->data)[pixel_y_idx] = ypuv_hdr.r;
-      reinterpret_cast<uint16_t*>(dest->data)[pixel_count + pixel_uv_idx] = ypuv_hdr.g;
-      reinterpret_cast<uint16_t*>(dest->data)[pixel_count + pixel_uv_idx + 1] = ypuv_hdr.b;
+      size_t pixel_idx =  x + y * width;
+      reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba1010102;
     }
   }
 
@@ -387,8 +466,9 @@
 }
 
 status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
-                                         jr_compressed_ptr dest) {
-  if (compressed_jpegr_image == nullptr || dest == nullptr) {
+                                         jr_compressed_ptr dest,
+                                         jr_metadata_ptr metadata) {
+  if (compressed_jpegr_image == nullptr || dest == nullptr || metadata == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
@@ -398,15 +478,16 @@
 
 status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
                                         jr_compressed_ptr compressed_recovery_map,
-                                        float hdr_ratio,
+                                        jr_metadata_ptr metadata,
                                         jr_compressed_ptr dest) {
   if (compressed_jpeg_image == nullptr
    || compressed_recovery_map == nullptr
+   || metadata == nullptr
    || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
-  string xmp = generateXmp(compressed_recovery_map->length, hdr_ratio);
+  string xmp = generateXmp(compressed_recovery_map->length, *metadata);
   string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
 
   // 2 bytes: APP1 sign (ff e1)
@@ -442,7 +523,7 @@
   return NO_ERROR;
 }
 
-string RecoveryMap::generateXmp(int secondary_image_length, float hdr_ratio) {
+string RecoveryMap::generateXmp(int secondary_image_length, jpegr_metadata& metadata) {
   const string kContainerPrefix = "GContainer";
   const string kContainerUri    = "http://ns.google.com/photos/1.0/container/";
   const string kItemPrefix      = "Item";
@@ -455,7 +536,6 @@
   const string kPrimary         = "Primary";
   const string kSemantic        = "Semantic";
   const string kVersion         = "Version";
-  const int    kVersionValue    = 1;
 
   const string kConDir          = Name(kContainerPrefix, kDirectory);
   const string kContainerItem   = Name(kContainerPrefix, kItem);
@@ -475,8 +555,11 @@
   writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
   writer.StartWritingElement("rdf:Description");
   writer.WriteXmlns(kContainerPrefix, kContainerUri);
-  writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), kVersionValue);
-  writer.WriteElementAndContent(Name(kContainerPrefix, "HdrRatio"), hdr_ratio);
+  writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), metadata.version);
+  writer.WriteElementAndContent(Name(kContainerPrefix, "rangeScalingFactor"),
+                                metadata.rangeScalingFactor);
+  // TODO: determine structure for hdr10 metadata
+  // TODO: write rest of metadata
   writer.StartWritingElements(kConDirSeq);
   size_t item_depth = writer.StartWritingElements(kLiItem);
   writer.WriteAttributeNameAndValue(kItemSemantic, kPrimary);