Commit ec450bc4 for libheif

commit ec450bc481f8009c3fac6d3e0a9dc702361f013d
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Mon Feb 16 15:33:22 2026 +0100

    read overview images from GeoTIFF files

diff --git a/examples/heif_enc.cc b/examples/heif_enc.cc
index 2f77f3b4..3dfd2404 100644
--- a/examples/heif_enc.cc
+++ b/examples/heif_enc.cc
@@ -1041,7 +1041,7 @@ private:
 class input_tiles_generator_tiff : public input_tiles_generator
 {
 public:
-  input_tiles_generator_tiff(std::unique_ptr<TiledTiffReader> reader)
+  input_tiles_generator_tiff(std::shared_ptr<TiledTiffReader> reader)
     : m_reader(std::move(reader))
   {
   }
@@ -1072,7 +1072,7 @@ public:
   void readExif(InputImage* input_image) { m_reader->readExif(input_image); }

 private:
-  std::unique_ptr<TiledTiffReader> m_reader;
+  std::shared_ptr<TiledTiffReader> m_reader;
 };
 #endif

@@ -1873,6 +1873,7 @@ int do_encode_images(heif_context* context, heif_encoder* encoder, heif_encoding
     InputImage input_image;
     heif_image_tiling tiling{};
     std::shared_ptr<input_tiles_generator> tile_generator;
+    std::shared_ptr<TiledTiffReader> tiff_reader_for_pyramid;

 #if HAVE_LIBTIFF
     // Auto-detect tiled TIFFs when not using explicit tiling options
@@ -1893,7 +1894,8 @@ int do_encode_images(heif_context* context, heif_encoder* encoder, heif_encoding
         }

         if (tiff_reader) {
-          auto tiff_gen = std::make_shared<input_tiles_generator_tiff>(std::move(tiff_reader));
+          auto shared_tiff_reader = std::shared_ptr<TiledTiffReader>(std::move(tiff_reader));
+          auto tiff_gen = std::make_shared<input_tiles_generator_tiff>(shared_tiff_reader);

           // Read tile (0,0) as representative for nclx profile
           input_image = tiff_gen->get_image(0, 0, output_bit_depth);
@@ -1909,6 +1911,7 @@ int do_encode_images(heif_context* context, heif_encoder* encoder, heif_encoding
           tiling.number_of_extra_dimensions = 0;

           tile_generator = tiff_gen;
+          tiff_reader_for_pyramid = shared_tiff_reader;
         }
       }
     }
@@ -2020,6 +2023,52 @@ int do_encode_images(heif_context* context, heif_encoder* encoder, heif_encoding

     encoded_image_ids.push_back(heif_image_handle_get_item_id(handle));

+#if HEIF_ENABLE_EXPERIMENTAL_FEATURES && HAVE_LIBTIFF
+    // Auto-encode TIFF pyramid overviews
+    if (tiff_reader_for_pyramid && !tiff_reader_for_pyramid->overviews().empty()) {
+      heif_item_id fullres_id = heif_image_handle_get_item_id(handle);
+      std::vector<heif_item_id> pyramid_ids;
+      pyramid_ids.push_back(fullres_id);
+
+      for (const auto& ov : tiff_reader_for_pyramid->overviews()) {
+        if (!tiff_reader_for_pyramid->setDirectory(ov.dir_index)) {
+          std::cerr << "Warning: could not switch to TIFF overview directory " << ov.dir_index << "\n";
+          continue;
+        }
+
+        auto ov_gen = std::make_shared<input_tiles_generator_tiff>(tiff_reader_for_pyramid);
+
+        heif_image_tiling ov_tiling{};
+        ov_tiling.version = 1;
+        ov_tiling.num_columns = ov_gen->nColumns();
+        ov_tiling.num_rows = ov_gen->nRows();
+        ov_tiling.tile_width = ov_gen->tileWidth();
+        ov_tiling.tile_height = ov_gen->tileHeight();
+        ov_tiling.image_width = ov_gen->imageWidth();
+        ov_tiling.image_height = ov_gen->imageHeight();
+        ov_tiling.number_of_extra_dimensions = 0;
+
+        heif_image_handle* ov_handle = encode_tiled(context, encoder, options, output_bit_depth, ov_gen, ov_tiling);
+        if (ov_handle) {
+          pyramid_ids.push_back(heif_image_handle_get_item_id(ov_handle));
+          heif_image_handle_release(ov_handle);
+        }
+      }
+
+      // Restore directory 0 for any subsequent use
+      tiff_reader_for_pyramid->setDirectory(0);
+
+      if (pyramid_ids.size() > 1) {
+        error = heif_context_add_pyramid_entity_group(context, pyramid_ids.data(), pyramid_ids.size(), nullptr);
+        if (error.code) {
+          std::cerr << "Cannot create pyramid entity group: " << error.message << "\n";
+          return 5;
+        }
+        std::cout << "Created pyramid entity group with " << pyramid_ids.size() << " layers\n";
+      }
+    }
+#endif
+
     // write EXIF to HEIC
     if (!input_image.exif.empty()) {
       // Note: we do not modify the EXIF Orientation here because we want it to match the HEIF transforms.
diff --git a/heifio/decoder_tiff.cc b/heifio/decoder_tiff.cc
index b73379b3..238bcf8a 100644
--- a/heifio/decoder_tiff.cc
+++ b/heifio/decoder_tiff.cc
@@ -897,6 +897,46 @@ std::unique_ptr<TiledTiffReader> TiledTiffReader::open(const char* filename, hei
   reader->m_n_columns = (reader->m_image_width + reader->m_tile_width - 1) / reader->m_tile_width;
   reader->m_n_rows = (reader->m_image_height + reader->m_tile_height - 1) / reader->m_tile_height;

+  // Detect overview directories (reduced-resolution images)
+  tdir_t n_dirs = TIFFNumberOfDirectories(tif);
+  for (uint16_t d = 1; d < n_dirs; d++) {
+    if (!TIFFSetDirectory(tif, d)) {
+      continue;
+    }
+
+    uint32_t subfiletype = 0;
+    TIFFGetField(tif, TIFFTAG_SUBFILETYPE, &subfiletype);
+    if (!(subfiletype & FILETYPE_REDUCEDIMAGE)) {
+      continue;
+    }
+
+    if (!TIFFIsTiled(tif)) {
+      continue;
+    }
+
+    uint16_t spp, bps_ov, config_ov;
+    bool hasAlpha_ov;
+    heif_error valErr = validateTiffFormat(tif, spp, bps_ov, config_ov, hasAlpha_ov);
+    if (valErr.code != heif_error_Ok) {
+      continue;
+    }
+
+    uint32_t ov_width, ov_height, ov_tw, ov_th;
+    if (!TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &ov_width) ||
+        !TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &ov_height)) {
+      continue;
+    }
+    if (!TIFFGetField(tif, TIFFTAG_TILEWIDTH, &ov_tw) ||
+        !TIFFGetField(tif, TIFFTAG_TILELENGTH, &ov_th)) {
+      continue;
+    }
+
+    reader->m_overviews.push_back({d, ov_width, ov_height, ov_tw, ov_th});
+  }
+
+  // Switch back to directory 0
+  TIFFSetDirectory(tif, 0);
+
   *out_err = heif_error_ok;
   return reader;
 }
@@ -905,6 +945,38 @@ std::unique_ptr<TiledTiffReader> TiledTiffReader::open(const char* filename, hei
 TiledTiffReader::~TiledTiffReader() = default;


+bool TiledTiffReader::setDirectory(uint32_t dir_index)
+{
+  TIFF* tif = static_cast<TIFF*>(m_tif.get());
+
+  if (!TIFFSetDirectory(tif, static_cast<uint16_t>(dir_index))) {
+    return false;
+  }
+
+  if (!TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &m_image_width) ||
+      !TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &m_image_height)) {
+    return false;
+  }
+
+  if (!TIFFGetField(tif, TIFFTAG_TILEWIDTH, &m_tile_width) ||
+      !TIFFGetField(tif, TIFFTAG_TILELENGTH, &m_tile_height)) {
+    return false;
+  }
+
+  uint16_t bps;
+  heif_error err = validateTiffFormat(tif, m_samples_per_pixel, bps, m_planar_config, m_has_alpha);
+  if (err.code != heif_error_Ok) {
+    return false;
+  }
+  m_bits_per_sample = bps;
+
+  m_n_columns = (m_image_width + m_tile_width - 1) / m_tile_width;
+  m_n_rows = (m_image_height + m_tile_height - 1) / m_tile_height;
+
+  return true;
+}
+
+
 heif_error TiledTiffReader::readTile(uint32_t tx, uint32_t ty, int output_bit_depth, heif_image** out_image)
 {
   TIFF* tif = static_cast<TIFF*>(m_tif.get());
diff --git a/heifio/decoder_tiff.h b/heifio/decoder_tiff.h
index b77aa8e5..71598267 100644
--- a/heifio/decoder_tiff.h
+++ b/heifio/decoder_tiff.h
@@ -31,6 +31,7 @@
 #include "libheif/heif.h"
 #include <memory>
 #include <cstdint>
+#include <vector>

 LIBHEIF_API
 heif_error loadTIFF(const char *filename, int output_bit_depth, InputImage *input_image);
@@ -39,6 +40,14 @@ class LIBHEIF_API TiledTiffReader {
 public:
   ~TiledTiffReader();

+  struct OverviewInfo {
+    uint32_t dir_index;
+    uint32_t image_width;
+    uint32_t image_height;
+    uint32_t tile_width;
+    uint32_t tile_height;
+  };
+
   // Returns a reader if the file is a tiled TIFF. If the TIFF is not tiled,
   // returns nullptr with heif_error_Ok (caller should fall back to loadTIFF).
   static std::unique_ptr<TiledTiffReader> open(const char* filename, heif_error* out_err);
@@ -50,6 +59,9 @@ public:
   uint32_t nColumns() const { return m_n_columns; }
   uint32_t nRows() const { return m_n_rows; }

+  const std::vector<OverviewInfo>& overviews() const { return m_overviews; }
+  bool setDirectory(uint32_t dir_index);
+
   uint16_t bitsPerSample() const { return m_bits_per_sample; }

   heif_error readTile(uint32_t tx, uint32_t ty, int output_bit_depth, heif_image** out_image);
@@ -68,6 +80,8 @@ private:
   uint16_t m_bits_per_sample = 0;
   uint16_t m_planar_config = 0;
   bool m_has_alpha = false;
+
+  std::vector<OverviewInfo> m_overviews;
 };

 #endif // LIBHEIF_DECODER_TIFF_H