Commit adbddce1 for libheif

commit adbddce16fce1e46ed13adb31aa25ed6dab2ad99
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Thu Aug 27 02:53:12 2026 +0200

    seed IDCreator from the item, track and entity group IDs of a parsed file

    Since db3b3e84, get_unused_item_id() returns a bare counter starting at 1 that
    was never advanced past the IDs read from the input. Adding any item to a context
    read from a file (heif_context_add_*item, add_exif/XMP, regions, encoded images)
    allocated an ID that already existed and `m_infe_boxes[id] = infe` silently
    replaced that item (reproduced: hvc1 item 1 turned into a mime item).

    HeifFile::read() now calls seed_id_creator(), which marks every parsed item ID,
    entity group ID and track ID as used (new IDCreator::mark_id_used()). A 'unif'
    brand in the input switches the creator to the global ID space so that the file
    stays unif-conformant when items are added.

    The global counter is also kept ahead of the namespace counters, so enabling
    unif mode after IDs were handed out cannot reuse one of them.

diff --git a/libheif/file.cc b/libheif/file.cc
index a0a10dc2..d1f7fdd0 100644
--- a/libheif/file.cc
+++ b/libheif/file.cc
@@ -143,7 +143,13 @@ Error HeifFile::read(const std::shared_ptr<StreamReader>& reader)
   }

   Error error = parse_heif_file();
-  return error;
+  if (error) {
+    return error;
+  }
+
+  seed_id_creator();
+
+  return Error::Ok;
 }


@@ -1032,6 +1038,36 @@ Result<heif_item_id> HeifFile::get_unused_item_id()
 }


+void HeifFile::seed_id_creator()
+{
+  // A file with the 'unif' brand uses one ID space for items, tracks and entity groups.
+  // Keep it that way for everything we add to the file.
+  if (m_ftyp_box && m_ftyp_box->has_compatible_brand(heif_brand2_unif)) {
+    m_id_creator.set_unif(true);
+  }
+
+  for (const auto& infe : m_infe_boxes) {
+    m_id_creator.mark_id_used(IDCreator::Namespace::item, infe.first);
+  }
+
+  if (m_grpl_box) {
+    for (const auto& box : m_grpl_box->get_all_child_boxes()) {
+      if (auto group = std::dynamic_pointer_cast<Box_EntityToGroup>(box)) {
+        m_id_creator.mark_id_used(IDCreator::Namespace::entity_group, group->get_group_id());
+      }
+    }
+  }
+
+  if (m_moov_box) {
+    for (const auto& trak : m_moov_box->get_child_boxes<Box_trak>()) {
+      if (auto tkhd = trak->get_child_box<Box_tkhd>()) {
+        m_id_creator.mark_id_used(IDCreator::Namespace::track, tkhd->get_track_id());
+      }
+    }
+  }
+}
+
+
 Result<heif_item_id> HeifFile::add_new_image(uint32_t item_type)
 {
   auto result = add_new_infe_box(item_type);
diff --git a/libheif/file.h b/libheif/file.h
index 06de7253..fe4388cd 100644
--- a/libheif/file.h
+++ b/libheif/file.h
@@ -304,6 +304,10 @@ private:

   Error parse_heif_file();

+  // Advance the ID creator past all item, track and entity-group IDs found in the parsed
+  // file, so that IDs allocated for new items cannot collide with existing ones.
+  void seed_id_creator();
+
   Error parse_heif_images();

   Error parse_heif_sequences();
diff --git a/libheif/id_creator.cc b/libheif/id_creator.cc
index 38f532e8..0f3e03fc 100644
--- a/libheif/id_creator.cc
+++ b/libheif/id_creator.cc
@@ -50,5 +50,40 @@ Result<uint32_t> IDCreator::get_new_id(Namespace ns)
                  "ID namespace overflow");
   }

-  return (*counter)++;
+  uint32_t id = (*counter)++;
+
+  // Keep the global counter ahead of all namespace counters so that switching to
+  // unif mode later does not reuse an ID that was already handed out.
+  if (m_next_id_global != 0 && id >= m_next_id_global) {
+    m_next_id_global = id + 1;
+  }
+
+  return id;
+}
+
+
+void IDCreator::mark_id_used(Namespace ns, uint32_t id)
+{
+  uint32_t* counter = nullptr;
+  switch (ns) {
+    case Namespace::item:
+      counter = &m_next_id_item;
+      break;
+    case Namespace::track:
+      counter = &m_next_id_track;
+      break;
+    case Namespace::entity_group:
+      counter = &m_next_id_entity_group;
+      break;
+  }
+
+  // A counter value of 0 means "exhausted" (see get_new_id). id + 1 wraps to 0 exactly
+  // when id == 0xFFFFFFFF, which is the correct exhausted state.
+  if (*counter != 0 && id >= *counter) {
+    *counter = id + 1;
+  }
+
+  if (m_next_id_global != 0 && id >= m_next_id_global) {
+    m_next_id_global = id + 1;
+  }
 }
diff --git a/libheif/id_creator.h b/libheif/id_creator.h
index fff8c97e..51b4d082 100644
--- a/libheif/id_creator.h
+++ b/libheif/id_creator.h
@@ -39,6 +39,11 @@ public:
   // Returns error on overflow (counter would exceed 0xFFFFFFFF).
   Result<uint32_t> get_new_id(Namespace ns);

+  // Declare an ID as already taken (e.g. because it was read from an existing file),
+  // so that get_new_id() will never hand it out again. Advances the namespace counter
+  // and the global counter past 'id'.
+  void mark_id_used(Namespace ns, uint32_t id);
+
 private:
   bool m_unif = false;
   uint32_t m_next_id_item = 1;