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