Commit 5897e715 for libheif

commit 5897e715c1545c37767e0d3fbebe463fae7d63da
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Fri Feb 27 14:51:58 2026 +0100

    unci: enable compression also for non-tiled images

diff --git a/examples/heif_enc.cc b/examples/heif_enc.cc
index ab2d9b8b..2642bf61 100644
--- a/examples/heif_enc.cc
+++ b/examples/heif_enc.cc
@@ -1853,6 +1853,7 @@ int main(int argc, char** argv)
     options->color_conversion_options.only_use_preferred_chroma_algorithm = true;
   }

+  options->unci_compression = unci_compression;

   // --- if no output filename was given, synthesize one from the first input image filename

diff --git a/libheif/CMakeLists.txt b/libheif/CMakeLists.txt
index ca0a0154..e7470838 100644
--- a/libheif/CMakeLists.txt
+++ b/libheif/CMakeLists.txt
@@ -25,6 +25,7 @@ set(libheif_headers
         api/libheif/heif_context.h
         api/libheif/heif_tiling.h
         api/libheif/heif_uncompressed.h
+        api/libheif/heif_uncompressed_types.h
         api/libheif/heif_text.h
         api/libheif/heif_cxx.h
         ${CMAKE_CURRENT_BINARY_DIR}/heif_version.h)
@@ -55,6 +56,7 @@ set(libheif_sources
         logging.h
         logging.cc
         compression.h
+        compression.cc
         compression_brotli.cc
         compression_zlib.cc
         common_utils.cc
diff --git a/libheif/api/libheif/heif_encoding.cc b/libheif/api/libheif/heif_encoding.cc
index 609406ef..48257057 100644
--- a/libheif/api/libheif/heif_encoding.cc
+++ b/libheif/api/libheif/heif_encoding.cc
@@ -570,7 +570,7 @@ int heif_encoder_has_default(heif_encoder* encoder,

 static void set_default_encoding_options(heif_encoding_options& options)
 {
-  options.version = 7;
+  options.version = 8;

   options.save_alpha_channel = true;
   options.macOS_compatibility_workaround = false;
@@ -585,6 +585,8 @@ static void set_default_encoding_options(heif_encoding_options& options)
   options.color_conversion_options.only_use_preferred_chroma_algorithm = false;

   options.prefer_uncC_short_form = true;
+
+  options.unci_compression = heif_unci_compression_off;
 }


@@ -607,6 +609,9 @@ void heif_encoding_options_copy(heif_encoding_options* dst, const heif_encoding_
   int min_version = std::min(dst->version, src->version);

   switch (min_version) {
+    case 8:
+      dst->unci_compression = src->unci_compression;
+      [[fallthrough]];
     case 7:
       dst->prefer_uncC_short_form = src->prefer_uncC_short_form;
       [[fallthrough]];
diff --git a/libheif/api/libheif/heif_encoding.h b/libheif/api/libheif/heif_encoding.h
index 5c91c416..711cf6c9 100644
--- a/libheif/api/libheif/heif_encoding.h
+++ b/libheif/api/libheif/heif_encoding.h
@@ -33,6 +33,7 @@ extern "C" {
 #include <libheif/heif_context.h>
 #include <libheif/heif_brands.h>
 #include <libheif/heif_color.h>
+#include <libheif/heif_uncompressed_types.h>


 // ----- encoder -----
@@ -308,6 +309,12 @@ typedef struct heif_encoding_options
   // Set this to true to use compressed form of uncC where possible.
   uint8_t prefer_uncC_short_form;

+  // version 8 options
+
+  // Set this to enable compression for 'unci' images encoded through heif_context_encode_image().
+  // Default: heif_unci_compression_off
+  heif_unci_compression unci_compression;
+
   // TODO: we should add a flag to force MIAF compatible outputs. E.g. this will put restrictions on grid tile sizes and
   //       might add a clap box when the grid output size does not match the color subsampling factors.
   //       Since some of these constraints have to be known before actually encoding the image, "forcing MIAF compatibility"
diff --git a/libheif/api/libheif/heif_uncompressed.h b/libheif/api/libheif/heif_uncompressed.h
index 9e299d7f..e8ac61b3 100644
--- a/libheif/api/libheif/heif_uncompressed.h
+++ b/libheif/api/libheif/heif_uncompressed.h
@@ -21,6 +21,7 @@
 #ifndef LIBHEIF_HEIF_UNCOMPRESSED_H
 #define LIBHEIF_HEIF_UNCOMPRESSED_H

+#include "libheif/heif_uncompressed_types.h"
 #include "libheif/heif.h"

 #ifdef __cplusplus
@@ -34,37 +35,7 @@ extern "C" {
  *        See heif_metadata_compression for more information.
  */

-// --- ISO 23001-17 component types (Table 1)
-
-typedef enum heif_uncompressed_component_type
-{
-  heif_uncompressed_component_type_monochrome = 0,
-  heif_uncompressed_component_type_Y = 1,
-  heif_uncompressed_component_type_Cb = 2,
-  heif_uncompressed_component_type_Cr = 3,
-  heif_uncompressed_component_type_red = 4,
-  heif_uncompressed_component_type_green = 5,
-  heif_uncompressed_component_type_blue = 6,
-  heif_uncompressed_component_type_alpha = 7,
-  heif_uncompressed_component_type_depth = 8,
-  heif_uncompressed_component_type_disparity = 9,
-  heif_uncompressed_component_type_palette = 10,
-  heif_uncompressed_component_type_filter_array = 11,
-  heif_uncompressed_component_type_padded = 12,
-  heif_uncompressed_component_type_cyan = 13,
-  heif_uncompressed_component_type_magenta = 14,
-  heif_uncompressed_component_type_yellow = 15,
-  heif_uncompressed_component_type_key_black = 16
-} heif_uncompressed_component_type;
-
-
-// --- Bayer / filter array pattern
-
-typedef struct heif_bayer_pattern_pixel
-{
-  uint16_t component_index;  // index into the component definition (cmpd)
-  float component_gain;
-} heif_bayer_pattern_pixel;
+// heif_uncompressed_component_type and heif_bayer_pattern_pixel are defined in heif_uncompressed_types.h.

 // Set a Bayer / filter array pattern on an image.
 // The pattern is a 2D array of component indices with dimensions pattern_width x pattern_height.
@@ -155,7 +126,7 @@ int heif_image_get_polarization_pattern_index_for_component(const heif_image*,

 // --- Sensor bad pixels map (ISO 23001-17, Section 6.1.7)

-struct heif_bad_pixel { uint32_t row; uint32_t column; };
+// struct heif_bad_pixel is defined in heif_uncompressed_types.h.

 // Add a sensor bad pixels map to an image.
 // component_indices: array of component indices this map applies to (may be NULL if num_component_indices == 0,
@@ -246,21 +217,7 @@ heif_error heif_image_get_sensor_nuc_data(const heif_image*,
                                            float* out_nuc_offsets);


-// --- Chroma sample location (ISO 23091-2 / ITU-T H.273 + ISO 23001-17)
-
-typedef enum heif_chroma420_sample_location {
-  // values 0-5 according to ISO 23091-2 / ITU-T H.273
-  heif_chroma420_sample_location_00_05 = 0,
-  heif_chroma420_sample_location_05_05 = 1,
-  heif_chroma420_sample_location_00_00 = 2,
-  heif_chroma420_sample_location_05_00 = 3,
-  heif_chroma420_sample_location_00_10 = 4,
-  heif_chroma420_sample_location_05_10 = 5,
-
-  // value 6 according to ISO 23001-17
-  heif_chroma420_sample_location_00_00_01_00 = 6
-} heif_chroma420_sample_location;
-
+// heif_chroma420_sample_location is defined in heif_uncompressed_types.h.

 // --- Chroma sample location (ISO 23001-17, Section 6.1.4)

@@ -280,35 +237,7 @@ uint8_t heif_image_get_chroma_location(const heif_image*);

 // --- 'unci' images

-// This is similar to heif_metadata_compression. We should try to keep the integers compatible, but each enum will just
-// contain the allowed values.
-typedef enum heif_unci_compression
-{
-  heif_unci_compression_off = 0,
-  //heif_unci_compression_auto = 1,
-  //heif_unci_compression_unknown = 2, // only used when reading unknown method from input file
-  heif_unci_compression_deflate = 3,
-  heif_unci_compression_zlib = 4,
-  heif_unci_compression_brotli = 5
-} heif_unci_compression;
-
-
-typedef struct heif_unci_image_parameters
-{
-  int version;
-
-  // --- version 1
-
-  uint32_t image_width;
-  uint32_t image_height;
-
-  uint32_t tile_width;
-  uint32_t tile_height;
-
-  enum heif_unci_compression compression;
-
-  // TODO: interleave type, padding
-} heif_unci_image_parameters;
+// heif_unci_compression and heif_unci_image_parameters are defined in heif_uncompressed_types.h.

 LIBHEIF_API
 heif_unci_image_parameters* heif_unci_image_parameters_alloc(void);
@@ -346,27 +275,7 @@ heif_error heif_context_add_empty_unci_image(heif_context* ctx,
                                              const heif_image* prototype,
                                              heif_image_handle** out_unci_image_handle);

-// --- pixel datatype support
-
-typedef enum heif_channel_datatype
-{
-  heif_channel_datatype_undefined = 0,
-  heif_channel_datatype_unsigned_integer = 1,
-  heif_channel_datatype_signed_integer = 2,
-  heif_channel_datatype_floating_point = 3,
-  heif_channel_datatype_complex_number = 4
-} heif_channel_datatype;
-
-typedef struct heif_complex32
-{
-  float real, imaginary;
-} heif_complex32;
-
-typedef struct heif_complex64
-{
-  double real, imaginary;
-} heif_complex64;
-
+// heif_channel_datatype, heif_complex32, heif_complex64 are defined in heif_uncompressed_types.h.

 // --- index-based component access (for ISO 23001-17 multi-component images)

diff --git a/libheif/api/libheif/heif_uncompressed_types.h b/libheif/api/libheif/heif_uncompressed_types.h
new file mode 100644
index 00000000..1a00dcc9
--- /dev/null
+++ b/libheif/api/libheif/heif_uncompressed_types.h
@@ -0,0 +1,143 @@
+/*6
+ * HEIF codec.
+ * Copyright (c) 2026 Dirk Farin <dirk.farin@gmail.com>
+ *
+ * This file is part of libheif.
+ *
+ * libheif is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * libheif is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libheif.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBHEIF_HEIF_UNCOMPRESSED_TYPES_H
+#define LIBHEIF_HEIF_UNCOMPRESSED_TYPES_H
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// --- ISO 23001-17 component types (Table 1)
+
+typedef enum heif_uncompressed_component_type
+{
+  heif_uncompressed_component_type_monochrome = 0,
+  heif_uncompressed_component_type_Y = 1,
+  heif_uncompressed_component_type_Cb = 2,
+  heif_uncompressed_component_type_Cr = 3,
+  heif_uncompressed_component_type_red = 4,
+  heif_uncompressed_component_type_green = 5,
+  heif_uncompressed_component_type_blue = 6,
+  heif_uncompressed_component_type_alpha = 7,
+  heif_uncompressed_component_type_depth = 8,
+  heif_uncompressed_component_type_disparity = 9,
+  heif_uncompressed_component_type_palette = 10,
+  heif_uncompressed_component_type_filter_array = 11,
+  heif_uncompressed_component_type_padded = 12,
+  heif_uncompressed_component_type_cyan = 13,
+  heif_uncompressed_component_type_magenta = 14,
+  heif_uncompressed_component_type_yellow = 15,
+  heif_uncompressed_component_type_key_black = 16
+} heif_uncompressed_component_type;
+
+
+// --- Bayer / filter array pattern
+
+typedef struct heif_bayer_pattern_pixel
+{
+  uint16_t component_index;  // index into the component definition (cmpd)
+  float component_gain;
+} heif_bayer_pattern_pixel;
+
+
+// --- Sensor bad pixels map (ISO 23001-17, Section 6.1.7)
+
+struct heif_bad_pixel { uint32_t row; uint32_t column; };
+
+
+// --- Chroma sample location (ISO 23091-2 / ITU-T H.273 + ISO 23001-17)
+
+typedef enum heif_chroma420_sample_location {
+  // values 0-5 according to ISO 23091-2 / ITU-T H.273
+  heif_chroma420_sample_location_00_05 = 0,
+  heif_chroma420_sample_location_05_05 = 1,
+  heif_chroma420_sample_location_00_00 = 2,
+  heif_chroma420_sample_location_05_00 = 3,
+  heif_chroma420_sample_location_00_10 = 4,
+  heif_chroma420_sample_location_05_10 = 5,
+
+  // value 6 according to ISO 23001-17
+  heif_chroma420_sample_location_00_00_01_00 = 6
+} heif_chroma420_sample_location;
+
+
+// Compression methods for 'unci' (ISO 23001-17) images.
+// This is similar to heif_metadata_compression. We should try to keep the integers compatible, but each enum will just
+// contain the allowed values.
+typedef enum heif_unci_compression
+{
+  heif_unci_compression_off = 0,
+  //heif_unci_compression_auto = 1,
+  //heif_unci_compression_unknown = 2, // only used when reading unknown method from input file
+  heif_unci_compression_deflate = 3,
+  heif_unci_compression_zlib = 4,
+  heif_unci_compression_brotli = 5
+} heif_unci_compression;
+
+
+// --- 'unci' image parameters
+
+typedef struct heif_unci_image_parameters
+{
+  int version;
+
+  // --- version 1
+
+  uint32_t image_width;
+  uint32_t image_height;
+
+  uint32_t tile_width;
+  uint32_t tile_height;
+
+  heif_unci_compression compression;
+
+  // TODO: interleave type, padding
+} heif_unci_image_parameters;
+
+
+// --- pixel datatype support
+
+typedef enum heif_channel_datatype
+{
+  heif_channel_datatype_undefined = 0,
+  heif_channel_datatype_unsigned_integer = 1,
+  heif_channel_datatype_signed_integer = 2,
+  heif_channel_datatype_floating_point = 3,
+  heif_channel_datatype_complex_number = 4
+} heif_channel_datatype;
+
+typedef struct heif_complex32
+{
+  float real, imaginary;
+} heif_complex32;
+
+typedef struct heif_complex64
+{
+  double real, imaginary;
+} heif_complex64;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/libheif/codecs/uncompressed/unc_enc.cc b/libheif/codecs/uncompressed/unc_enc.cc
index 4aa66f19..6a71121e 100644
--- a/libheif/codecs/uncompressed/unc_enc.cc
+++ b/libheif/codecs/uncompressed/unc_enc.cc
@@ -68,6 +68,9 @@ std::shared_ptr<class Box_VisualSampleEntry> Encoder_uncompressed::get_sample_de
       case fourcc("icef"):
       case fourcc("cpat"):
       case fourcc("splz"):
+      case fourcc("sbpm"):
+      case fourcc("snuc"):
+      case fourcc("cloc"):
         uncv->append_child_box(prop);
       break;
     }
diff --git a/libheif/codecs/uncompressed/unc_encoder.cc b/libheif/codecs/uncompressed/unc_encoder.cc
index 5b652fd6..a660322a 100644
--- a/libheif/codecs/uncompressed/unc_encoder.cc
+++ b/libheif/codecs/uncompressed/unc_encoder.cc
@@ -30,6 +30,7 @@
 #include "unc_encoder_rgb_pixel_interleave.h"
 #include "unc_encoder_rgb_bytealign_pixel_interleave.h"
 #include "libheif/heif_uncompressed.h"
+#include "compression.h"


 heif_uncompressed_component_type heif_channel_to_component_type(heif_channel channel)
@@ -188,7 +189,37 @@ Result<Encoder::CodedImageData> unc_encoder::encode_static(const std::shared_ptr
     return codedBitstreamResult.error();
   }

-  codedImageData.bitstream = *codedBitstreamResult;
+  // --- optionally compress
+
+  heif_unci_compression compression = (in_options.version >= 8) ? in_options.unci_compression : heif_unci_compression_off;
+
+  if (compression != heif_unci_compression_off) {
+    uint32_t compr_fourcc = unci_compression_to_fourcc(compression);
+
+    auto compressed = compress_unci_fourcc(compr_fourcc,
+                                            codedBitstreamResult->data(),
+                                            codedBitstreamResult->size());
+    if (!compressed) {
+      return compressed.error();
+    }
+
+    auto cmpC = std::make_shared<Box_cmpC>();
+    cmpC->set_compression_type(compr_fourcc);
+    cmpC->set_compressed_unit_type(heif_cmpC_compressed_unit_type_image_tile);
+    codedImageData.properties.push_back(cmpC);
+
+    auto icef = std::make_shared<Box_icef>();
+    Box_icef::CompressedUnitInfo info;
+    info.unit_offset = 0;
+    info.unit_size = compressed->size();
+    icef->add_component(info);
+    codedImageData.properties.push_back(icef);
+
+    codedImageData.bitstream = std::move(*compressed);
+  }
+  else {
+    codedImageData.bitstream = std::move(*codedBitstreamResult);
+  }

   return codedImageData;
 }
diff --git a/libheif/compression.cc b/libheif/compression.cc
new file mode 100644
index 00000000..037cf926
--- /dev/null
+++ b/libheif/compression.cc
@@ -0,0 +1,61 @@
+/*
+ * HEIF codec.
+ * Copyright (c) 2026 Dirk Farin <dirk.farin@gmail.com>
+ *
+ * This file is part of libheif.
+ *
+ * libheif is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * libheif is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libheif.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "compression.h"
+#include "common_utils.h"
+
+
+uint32_t unci_compression_to_fourcc(heif_unci_compression method)
+{
+  switch (method) {
+    case heif_unci_compression_off:
+      return 0;
+    case heif_unci_compression_deflate:
+      return fourcc("defl");
+    case heif_unci_compression_zlib:
+      return fourcc("zlib");
+    case heif_unci_compression_brotli:
+      return fourcc("brot");
+    default:
+      return 0;
+  }
+}
+
+
+Result<std::vector<uint8_t>> compress_unci_fourcc(uint32_t fourcc_code,
+                                                   const uint8_t* data, size_t size)
+{
+  switch (fourcc_code) {
+#if HAVE_ZLIB
+    case fourcc("defl"):
+      return {compress_deflate(data, size)};
+    case fourcc("zlib"):
+      return {compress_zlib(data, size)};
+#endif
+#if HAVE_BROTLI
+    case fourcc("brot"):
+      return {compress_brotli(data, size)};
+#endif
+    default:
+      return Error{heif_error_Unsupported_feature,
+                   heif_suberror_Unsupported_generic_compression_method,
+                   "Unsupported unci compression method."};
+  }
+}
diff --git a/libheif/compression.h b/libheif/compression.h
index 9b007672..95e45221 100644
--- a/libheif/compression.h
+++ b/libheif/compression.h
@@ -25,6 +25,26 @@
 #include <cstddef>

 #include <error.h>
+#include <libheif/heif_uncompressed_types.h>
+
+/**
+ * Convert heif_unci_compression enum to a fourcc code.
+ *
+ * @param method the compression method
+ * @return the corresponding fourcc code, or 0 for heif_unci_compression_off
+ */
+uint32_t unci_compression_to_fourcc(heif_unci_compression method);
+
+/**
+ * Compress data using the compression method identified by a fourcc code.
+ *
+ * @param fourcc_code the fourcc code for the compression method (e.g. "defl", "zlib", "brot")
+ * @param data pointer to the data to be compressed
+ * @param size the length of the input array in bytes
+ * @return the corresponding compressed data, or an error
+ */
+Result<std::vector<uint8_t>> compress_unci_fourcc(uint32_t fourcc_code,
+                                                   const uint8_t* data, size_t size);

 #if HAVE_ZLIB
 /**
diff --git a/libheif/image-items/unc_image.cc b/libheif/image-items/unc_image.cc
index 00ee7d7a..760c49ce 100644
--- a/libheif/image-items/unc_image.cc
+++ b/libheif/image-items/unc_image.cc
@@ -170,30 +170,13 @@ Result<std::shared_ptr<ImageItem_uncompressed>> ImageItem_uncompressed::add_unci
   unci_image->add_property(ispe, true);

   if (parameters->compression != heif_unci_compression_off) {
-    auto icef = std::make_shared<Box_icef>();
     auto cmpC = std::make_shared<Box_cmpC>();
+    cmpC->set_compression_type(unci_compression_to_fourcc(parameters->compression));
     cmpC->set_compressed_unit_type(heif_cmpC_compressed_unit_type_image_tile);

-    if (false) {
-    }
-#if HAVE_ZLIB
-    else if (parameters->compression == heif_unci_compression_deflate) {
-      cmpC->set_compression_type(fourcc("defl"));
-    }
-    else if (parameters->compression == heif_unci_compression_zlib) {
-      cmpC->set_compression_type(fourcc("zlib"));
-    }
-#endif
-#if HAVE_BROTLI
-    else if (parameters->compression == heif_unci_compression_brotli) {
-      cmpC->set_compression_type(fourcc("brot"));
-    }
-#endif
-    else {
-      assert(false);
-    }
-
     unci_image->add_property(cmpC, true);
+
+    auto icef = std::make_shared<Box_icef>();
     unci_image->add_property_without_deduplication(icef, true); // icef is empty. A normal add_property() would lead to a wrong deduplication.
   }

@@ -262,38 +245,22 @@ Error ImageItem_uncompressed::add_image_tile(uint32_t tile_x, uint32_t tile_y, c
     get_file()->replace_iloc_data(get_id(), tile_idx * tile_data_size, *codedBitstreamResult, 0);
   }
   else {
-    std::vector<uint8_t> compressed_data;
-    const std::vector<uint8_t>& raw_data = std::move(*codedBitstreamResult);
-    (void)raw_data;
-
-    uint32_t compr = cmpC->get_compression_type();
-    switch (compr) {
-#if HAVE_ZLIB
-      case fourcc("defl"):
-        compressed_data = compress_deflate(raw_data.data(), raw_data.size());
-        break;
-      case fourcc("zlib"):
-        compressed_data = compress_zlib(raw_data.data(), raw_data.size());
-        break;
-#endif
-#if HAVE_BROTLI
-      case fourcc("brot"):
-        compressed_data = compress_brotli(raw_data.data(), raw_data.size());
-        break;
-#endif
-      default:
-        assert(false);
-        break;
+    const std::vector<uint8_t>& raw_data = *codedBitstreamResult;
+
+    auto compressed = compress_unci_fourcc(cmpC->get_compression_type(),
+                                            raw_data.data(), raw_data.size());
+    if (!compressed) {
+      return compressed.error();
     }

-    get_file()->append_iloc_data(get_id(), compressed_data, 0);
+    get_file()->append_iloc_data(get_id(), *compressed, 0);

     Box_icef::CompressedUnitInfo unit_info;
     unit_info.unit_offset = m_next_tile_write_pos;
-    unit_info.unit_size = compressed_data.size();
+    unit_info.unit_size = compressed->size();
     icef->set_component(tile_idx, unit_info);

-    m_next_tile_write_pos += compressed_data.size();
+    m_next_tile_write_pos += compressed->size();
   }

   return Error::Ok;