Commit c111c638 for libheif

commit c111c6387189457496b315f181584740205fd6c1
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Thu May 28 13:03:45 2026 +0200

    mini box: propagate HDR metadata in both directions

    The bitstream write() function already serializes clli/mdcv/cclv/amve/ndwt
    correctly, but the two conversions between Box_mini and HeifFile dropped them:

      - create_from_heif_file (write path) did not collect Box_clli/Box_mdcv/
        Box_cclv/Box_amve/Box_ndwt from the source item's properties and forced
        set_hdr_flag(false), so any HDR metadata was silently lost when emitting
        a mini box.

      - create_expanded_boxes (read path) parsed the mini bitstream's HDR block
        into m_clli/m_mdcv/m_cclv/m_amve/m_ndwt but never appended them to the
        expanded ipco/ipma, so the public has/get accessors on the primary item
        returned 0 even when the file actually carried the data.

    Fix both directions for the primary item; the tmap/gainmap HDR metadata is
    still TODO and the existing comments reflect that.

diff --git a/libheif/mini.cc b/libheif/mini.cc
index 38a394f7..2f86b36f 100644
--- a/libheif/mini.cc
+++ b/libheif/mini.cc
@@ -1478,6 +1478,33 @@ Error Box_mini::create_expanded_boxes(class HeifFile* file)
   // TODO: replace this placeholder with pixi box version 1 once that is supported
   ipco_box->append_child_box(std::make_shared<Box_free>()); // placeholder for entry 8

+  // HDR metadata: expand the parsed mini-bitstream fields into separate ipco
+  // property boxes so the standard property-walk machinery (and the public
+  // heif_image*_has/get_content_light_level / mastering_display_colour_volume
+  // / ambient_viewing_environment / nominal_diffuse_white APIs) sees them.
+  uint16_t hdr_clli_prop_index = 0;
+  uint16_t hdr_mdcv_prop_index = 0;
+  uint16_t hdr_cclv_prop_index = 0;
+  uint16_t hdr_amve_prop_index = 0;
+  uint16_t hdr_ndwt_prop_index = 0;
+  // append_child_box() returns the 0-based child index; ipma uses 1-based
+  // property indices.
+  if (m_clli) {
+    hdr_clli_prop_index = static_cast<uint16_t>(ipco_box->append_child_box(m_clli) + 1);
+  }
+  if (m_mdcv) {
+    hdr_mdcv_prop_index = static_cast<uint16_t>(ipco_box->append_child_box(m_mdcv) + 1);
+  }
+  if (m_cclv) {
+    hdr_cclv_prop_index = static_cast<uint16_t>(ipco_box->append_child_box(m_cclv) + 1);
+  }
+  if (m_amve) {
+    hdr_amve_prop_index = static_cast<uint16_t>(ipco_box->append_child_box(m_amve) + 1);
+  }
+  if (m_ndwt) {
+    hdr_ndwt_prop_index = static_cast<uint16_t>(ipco_box->append_child_box(m_ndwt) + 1);
+  }
+
   auto ipma_box = std::make_shared<Box_ipma>();
   file->set_ipma_box(ipma_box);
   ipma_box->add_property_for_item_ID(1, Box_ipma::PropertyAssociation{true, uint16_t(1)});
@@ -1492,7 +1519,26 @@ Error Box_mini::create_expanded_boxes(class HeifFile* file)
     ipma_box->add_property_for_item_ID(2, Box_ipma::PropertyAssociation{true, uint16_t(7)});
     ipma_box->add_property_for_item_ID(2, Box_ipma::PropertyAssociation{false, uint16_t(8)});
   }
-  // TODO: will need more once we support HDR / gainmap representation
+
+  // Associate HDR-metadata properties with the primary item (non-essential —
+  // matches what ImageItem::set_clli/set_mdcv/set_amve/set_ndwt emit on
+  // the write path).
+  if (hdr_clli_prop_index) {
+    ipma_box->add_property_for_item_ID(1, Box_ipma::PropertyAssociation{false, hdr_clli_prop_index});
+  }
+  if (hdr_mdcv_prop_index) {
+    ipma_box->add_property_for_item_ID(1, Box_ipma::PropertyAssociation{false, hdr_mdcv_prop_index});
+  }
+  if (hdr_cclv_prop_index) {
+    ipma_box->add_property_for_item_ID(1, Box_ipma::PropertyAssociation{false, hdr_cclv_prop_index});
+  }
+  if (hdr_amve_prop_index) {
+    ipma_box->add_property_for_item_ID(1, Box_ipma::PropertyAssociation{false, hdr_amve_prop_index});
+  }
+  if (hdr_ndwt_prop_index) {
+    ipma_box->add_property_for_item_ID(1, Box_ipma::PropertyAssociation{false, hdr_ndwt_prop_index});
+  }
+  // TODO: gainmap (tmap) HDR metadata expansion is still missing.

   // Append irot/imir (only as needed) and associate them with the items.
   // mini's orientation field uses standard EXIF orientation values 1..8,
@@ -1780,6 +1826,11 @@ std::shared_ptr<Box_mini> Box_mini::create_from_heif_file(HeifFile* file)
   std::shared_ptr<Box_colr> colr_nclx;
   std::shared_ptr<Box_colr> colr_icc;
   std::shared_ptr<Box> codec_config;
+  std::shared_ptr<Box_clli> clli_box;
+  std::shared_ptr<Box_mdcv> mdcv_box;
+  std::shared_ptr<Box_cclv> cclv_box;
+  std::shared_ptr<Box_amve> amve_box;
+  std::shared_ptr<Box_ndwt> ndwt_box;

   for (auto& prop : properties) {
     if (auto p = std::dynamic_pointer_cast<Box_ispe>(prop)) {
@@ -1799,6 +1850,21 @@ std::shared_ptr<Box_mini> Box_mini::create_from_heif_file(HeifFile* file)
     else if (std::dynamic_pointer_cast<Box_av1C>(prop) || std::dynamic_pointer_cast<Box_hvcC>(prop)) {
       codec_config = prop;
     }
+    else if (auto p = std::dynamic_pointer_cast<Box_clli>(prop)) {
+      clli_box = p;
+    }
+    else if (auto p = std::dynamic_pointer_cast<Box_mdcv>(prop)) {
+      mdcv_box = p;
+    }
+    else if (auto p = std::dynamic_pointer_cast<Box_cclv>(prop)) {
+      cclv_box = p;
+    }
+    else if (auto p = std::dynamic_pointer_cast<Box_amve>(prop)) {
+      amve_box = p;
+    }
+    else if (auto p = std::dynamic_pointer_cast<Box_ndwt>(prop)) {
+      ndwt_box = p;
+    }
   }

   heif_orientation orientation = compute_orientation_from_properties(properties);
@@ -2037,8 +2103,17 @@ std::shared_ptr<Box_mini> Box_mini::create_from_heif_file(HeifFile* file)
     }
   }

-  // HDR / gainmap: not yet implemented for conversion
-  mini->set_hdr_flag(false);
+  // HDR metadata on the primary item (clli/mdcv/cclv/amve/ndwt). The mini
+  // bitstream gates these via hdr_flag — set it whenever at least one of
+  // those property boxes is present.
+  // Gainmap (tmap) conversion is still TODO; m_gainmap_flag stays false here.
+  bool has_hdr_metadata = clli_box || mdcv_box || cclv_box || amve_box || ndwt_box;
+  mini->set_hdr_flag(has_hdr_metadata);
+  if (clli_box) mini->set_clli(clli_box);
+  if (mdcv_box) mini->set_mdcv(mdcv_box);
+  if (cclv_box) mini->set_cclv(cclv_box);
+  if (amve_box) mini->set_amve(amve_box);
+  if (ndwt_box) mini->set_ndwt(ndwt_box);

   return mini;
 }