Commit d05d6dff for libheif

commit d05d6dff922d64af9d5f48cc0818eacbfcbb0b6e
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Sun Dec 14 12:06:40 2025 +0100

    support writing uncv with dropped alpha channel

diff --git a/libheif/api/libheif/heif_tiling.cc b/libheif/api/libheif/heif_tiling.cc
index 4a90bb53..d322dbc7 100644
--- a/libheif/api/libheif/heif_tiling.cc
+++ b/libheif/api/libheif/heif_tiling.cc
@@ -260,7 +260,7 @@ heif_error heif_context_add_image_tile(heif_context* ctx,
   }
 #if WITH_UNCOMPRESSED_CODEC
   else if (auto unci = std::dynamic_pointer_cast<ImageItem_uncompressed>(tiled_image->image)) {
-    Error err = unci->add_image_tile(tile_x, tile_y, image->image);
+    Error err = unci->add_image_tile(tile_x, tile_y, image->image, true); // TODO: how do we handle 'save_alpha=false' ?
     return err.error_struct(ctx->context.get());
   }
 #endif
diff --git a/libheif/codecs/uncompressed/unc_codec.cc b/libheif/codecs/uncompressed/unc_codec.cc
index cd52bbb7..d4d10fa4 100644
--- a/libheif/codecs/uncompressed/unc_codec.cc
+++ b/libheif/codecs/uncompressed/unc_codec.cc
@@ -792,7 +792,8 @@ UncompressedImageCodec::decode_uncompressed_image(const UncompressedImageCodec::
 Error fill_cmpd_and_uncC(std::shared_ptr<Box_cmpd>& cmpd,
                          std::shared_ptr<Box_uncC>& uncC,
                          const std::shared_ptr<const HeifPixelImage>& image,
-                         const heif_unci_image_parameters* parameters)
+                         const heif_unci_image_parameters* parameters,
+                         bool save_alpha_channel)
 {
   uint32_t nTileColumns = parameters->image_width / parameters->tile_width;
   uint32_t nTileRows = parameters->image_height / parameters->tile_height;
@@ -858,19 +859,23 @@ Error fill_cmpd_and_uncC(std::shared_ptr<Box_cmpd>& cmpd,
                    heif_suberror_Unsupported_data_version,
                    "Unsupported colourspace / chroma combination - RGB");
     }
+
     Box_cmpd::Component rComponent = {component_type_red};
     cmpd->add_component(rComponent);
     Box_cmpd::Component gComponent = {component_type_green};
     cmpd->add_component(gComponent);
     Box_cmpd::Component bComponent = {component_type_blue};
     cmpd->add_component(bComponent);
-    if ((image->get_chroma_format() == heif_chroma_interleaved_RGBA) ||
-        (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE) ||
-        (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE) ||
-        (image->has_channel(heif_channel_Alpha))) {
+
+    if (save_alpha_channel &&
+        image->get_chroma_format() == heif_chroma_interleaved_RGBA ||
+        image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE ||
+        image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE ||
+        image->has_channel(heif_channel_Alpha)) {
       Box_cmpd::Component alphaComponent = {component_type_alpha};
       cmpd->add_component(alphaComponent);
     }
+
     if ((image->get_chroma_format() == heif_chroma_interleaved_RGB) ||
         (image->get_chroma_format() == heif_chroma_interleaved_RGBA) ||
         (image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_BE) ||
@@ -886,15 +891,18 @@ Error fill_cmpd_and_uncC(std::shared_ptr<Box_cmpd>& cmpd,
       else if (bpp > 8) {
         component_align = 2;
       }
+
       Box_uncC::Component component0 = {0, (uint8_t) (bpp), component_format_unsigned, component_align};
       uncC->add_component(component0);
       Box_uncC::Component component1 = {1, (uint8_t) (bpp), component_format_unsigned, component_align};
       uncC->add_component(component1);
       Box_uncC::Component component2 = {2, (uint8_t) (bpp), component_format_unsigned, component_align};
       uncC->add_component(component2);
-      if ((image->get_chroma_format() == heif_chroma_interleaved_RGBA) ||
-          (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE) ||
-          (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE)) {
+
+      if (save_alpha_channel &&
+          (image->get_chroma_format() == heif_chroma_interleaved_RGBA ||
+           image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE ||
+           image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE)) {
         Box_uncC::Component component3 = {
             3, (uint8_t) (bpp), component_format_unsigned, component_align};
         uncC->add_component(component3);
@@ -902,23 +910,29 @@ Error fill_cmpd_and_uncC(std::shared_ptr<Box_cmpd>& cmpd,
     }
     else {
       uncC->set_interleave_type(interleave_mode_component);
+
       int bpp_red = image->get_bits_per_pixel(heif_channel_R);
       Box_uncC::Component component0 = {0, (uint8_t) (bpp_red), component_format_unsigned, 0};
       uncC->add_component(component0);
+
       int bpp_green = image->get_bits_per_pixel(heif_channel_G);
       Box_uncC::Component component1 = {1, (uint8_t) (bpp_green), component_format_unsigned, 0};
       uncC->add_component(component1);
+
       int bpp_blue = image->get_bits_per_pixel(heif_channel_B);
       Box_uncC::Component component2 = {2, (uint8_t) (bpp_blue), component_format_unsigned, 0};
       uncC->add_component(component2);
-      if (image->has_channel(heif_channel_Alpha)) {
+
+      if (save_alpha_channel && image->has_channel(heif_channel_Alpha)) {
         int bpp_alpha = image->get_bits_per_pixel(heif_channel_Alpha);
         Box_uncC::Component component3 = {3, (uint8_t) (bpp_alpha), component_format_unsigned, 0};
         uncC->add_component(component3);
       }
     }
+
     uncC->set_sampling_type(sampling_mode_no_subsampling);
     uncC->set_block_size(0);
+
     if ((image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_LE) ||
         (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE)) {
       uncC->set_components_little_endian(true);
@@ -926,6 +940,7 @@ Error fill_cmpd_and_uncC(std::shared_ptr<Box_cmpd>& cmpd,
     else {
       uncC->set_components_little_endian(false);
     }
+
     uncC->set_block_pad_lsb(false);
     uncC->set_block_little_endian(false);
     uncC->set_block_reversed(false);
@@ -939,18 +954,22 @@ Error fill_cmpd_and_uncC(std::shared_ptr<Box_cmpd>& cmpd,
   else if (colourspace == heif_colorspace_monochrome) {
     Box_cmpd::Component monoComponent = {component_type_monochrome};
     cmpd->add_component(monoComponent);
-    if (image->has_channel(heif_channel_Alpha)) {
+
+    if (save_alpha_channel && image->has_channel(heif_channel_Alpha)) {
       Box_cmpd::Component alphaComponent = {component_type_alpha};
       cmpd->add_component(alphaComponent);
     }
+
     int bpp = image->get_bits_per_pixel(heif_channel_Y);
     Box_uncC::Component component0 = {0, (uint8_t) (bpp), component_format_unsigned, 0};
     uncC->add_component(component0);
-    if (image->has_channel(heif_channel_Alpha)) {
+
+    if (save_alpha_channel && image->has_channel(heif_channel_Alpha)) {
       bpp = image->get_bits_per_pixel(heif_channel_Alpha);
       Box_uncC::Component component1 = {1, (uint8_t) (bpp), component_format_unsigned, 0};
       uncC->add_component(component1);
     }
+
     uncC->set_sampling_type(sampling_mode_no_subsampling);
     uncC->set_interleave_type(interleave_mode_component);
     uncC->set_block_size(0);
diff --git a/libheif/codecs/uncompressed/unc_codec.h b/libheif/codecs/uncompressed/unc_codec.h
index 507ec3b9..c31f061a 100644
--- a/libheif/codecs/uncompressed/unc_codec.h
+++ b/libheif/codecs/uncompressed/unc_codec.h
@@ -45,7 +45,8 @@ bool isKnownUncompressedFrameConfigurationBoxProfile(const std::shared_ptr<const
 Error fill_cmpd_and_uncC(std::shared_ptr<Box_cmpd>& cmpd,
                          std::shared_ptr<Box_uncC>& uncC,
                          const std::shared_ptr<const HeifPixelImage>& image,
-                         const heif_unci_image_parameters* parameters);
+                         const heif_unci_image_parameters* parameters,
+                         bool save_alpha_channel);

 bool map_uncompressed_component_to_channel(const std::shared_ptr<const Box_cmpd> &cmpd,
                                            const std::shared_ptr<const Box_uncC> &uncC,
diff --git a/libheif/codecs/uncompressed/unc_enc.h b/libheif/codecs/uncompressed/unc_enc.h
index d8cfb0d1..c61285a3 100644
--- a/libheif/codecs/uncompressed/unc_enc.h
+++ b/libheif/codecs/uncompressed/unc_enc.h
@@ -52,6 +52,8 @@ public:
   {
     heif_encoding_options dummy_options{};

+    dummy_options.save_alpha_channel = static_cast<uint8_t>(options.save_alpha_channel);
+
     auto encodeResult = encode(image, encoder, dummy_options, input_class);
     if (encodeResult.error()) {
       return encodeResult.error();
diff --git a/libheif/image-items/unc_image.cc b/libheif/image-items/unc_image.cc
index 2b8af08f..c9a221b5 100644
--- a/libheif/image-items/unc_image.cc
+++ b/libheif/image-items/unc_image.cc
@@ -131,7 +131,7 @@ static Result<unciHeaders> generate_headers(const std::shared_ptr<const HeifPixe
   } else {
     std::shared_ptr<Box_cmpd> cmpd = std::make_shared<Box_cmpd>();

-    Error error = fill_cmpd_and_uncC(cmpd, uncC, src_image, parameters);
+    Error error = fill_cmpd_and_uncC(cmpd, uncC, src_image, parameters, options->save_alpha_channel);
     if (error) {
       return error;
     }
@@ -144,7 +144,7 @@ static Result<unciHeaders> generate_headers(const std::shared_ptr<const HeifPixe
 }


-Result<std::vector<uint8_t>> encode_image_tile(const std::shared_ptr<const HeifPixelImage>& src_image)
+Result<std::vector<uint8_t>> encode_image_tile(const std::shared_ptr<const HeifPixelImage>& src_image, bool save_alpha)
 {
   std::vector<uint8_t> data;

@@ -193,12 +193,16 @@ Result<std::vector<uint8_t>> encode_image_tile(const std::shared_ptr<const HeifP

       return data;
     }
-    else if ((src_image->get_chroma_format() == heif_chroma_interleaved_RGB) ||
-             (src_image->get_chroma_format() == heif_chroma_interleaved_RGBA) ||
-             (src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_BE) ||
-             (src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_LE) ||
-             (src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE) ||
-             (src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE))
+    else if ((save_alpha && (src_image->get_chroma_format() == heif_chroma_interleaved_RGB ||
+                             src_image->get_chroma_format() == heif_chroma_interleaved_RGBA ||
+                             src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_BE ||
+                             src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_LE ||
+                             src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE ||
+                             src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE))
+             ||
+             (!save_alpha && (src_image->get_chroma_format() == heif_chroma_interleaved_RGB ||
+                              src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_BE ||
+                              src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_LE)))
     {
       int bytes_per_pixel = 0;
       switch (src_image->get_chroma_format()) {
@@ -231,7 +235,49 @@ Result<std::vector<uint8_t>> encode_image_tile(const std::shared_ptr<const HeifP
       return data;
     }
     else
-    {
+    if (!save_alpha && (src_image->get_chroma_format() == heif_chroma_interleaved_RGBA ||
+                        src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE ||
+                        src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE)) {
+      int bytes_per_pixel = 0;
+      switch (src_image->get_chroma_format()) {
+        case heif_chroma_interleaved_RGBA:
+          bytes_per_pixel = 3;
+          break;
+        case heif_chroma_interleaved_RRGGBBAA_BE:
+        case heif_chroma_interleaved_RRGGBBAA_LE:
+          bytes_per_pixel = 6;
+          break;
+        default:
+          assert(false);
+      }
+
+      size_t src_stride;
+      const uint8_t* src_data = src_image->get_plane(heif_channel_interleaved, &src_stride);
+      uint64_t out_size = static_cast<uint64_t>(src_image->get_height()) * src_image->get_width() * bytes_per_pixel;
+      data.resize(out_size);
+
+      if (src_image->get_chroma_format() == heif_chroma_interleaved_RGBA) {
+        for (uint32_t y = 0; y < src_image->get_height(); y++) {
+          for (uint32_t x = 0; x < src_image->get_width(); x++) {
+            data[y * src_image->get_width() * bytes_per_pixel + 3 * x + 0] = src_data[src_stride * y + 4 * x + 0];
+            data[y * src_image->get_width() * bytes_per_pixel + 3 * x + 1] = src_data[src_stride * y + 4 * x + 1];
+            data[y * src_image->get_width() * bytes_per_pixel + 3 * x + 2] = src_data[src_stride * y + 4 * x + 2];
+          }
+        }
+      }
+      else {
+        for (uint32_t y = 0; y < src_image->get_height(); y++) {
+          for (uint32_t x = 0; x < src_image->get_width(); x++) {
+            for (int i = 0; i < 6; i++) {
+              data[y * src_image->get_width() * bytes_per_pixel + 6 * x + i] = src_data[src_stride * y + 8 * x + i];
+            }
+          }
+        }
+      }
+
+      return data;
+    }
+    else {
       return Error(heif_error_Unsupported_feature,
                    heif_suberror_Unsupported_data_version,
                    "Unsupported RGB chroma");
@@ -267,7 +313,6 @@ Result<std::vector<uint8_t>> encode_image_tile(const std::shared_ptr<const HeifP
                  heif_suberror_Unsupported_data_version,
                  "Unsupported colourspace");
   }
-
 }


@@ -313,7 +358,7 @@ Result<Encoder::CodedImageData> ImageItem_uncompressed::encode_static(const std:

   // --- encode image

-  Result<std::vector<uint8_t>> codedBitstreamResult = encode_image_tile(src_image);
+  Result<std::vector<uint8_t>> codedBitstreamResult = encode_image_tile(src_image, options.save_alpha_channel);
   if (!codedBitstreamResult) {
     return codedBitstreamResult.error();
   }
@@ -426,7 +471,7 @@ Result<std::shared_ptr<ImageItem_uncompressed>> ImageItem_uncompressed::add_unci
 }


-Error ImageItem_uncompressed::add_image_tile(uint32_t tile_x, uint32_t tile_y, const std::shared_ptr<const HeifPixelImage>& image)
+Error ImageItem_uncompressed::add_image_tile(uint32_t tile_x, uint32_t tile_y, const std::shared_ptr<const HeifPixelImage>& image, bool save_alpha)
 {
   std::shared_ptr<Box_uncC> uncC = get_property<Box_uncC>();
   assert(uncC);
@@ -436,7 +481,7 @@ Error ImageItem_uncompressed::add_image_tile(uint32_t tile_x, uint32_t tile_y, c

   uint32_t tile_idx = tile_y * uncC->get_number_of_tile_columns() + tile_x;

-  Result<std::vector<uint8_t>> codedBitstreamResult = encode_image_tile(image);
+  Result<std::vector<uint8_t>> codedBitstreamResult = encode_image_tile(image, save_alpha);
   if (!codedBitstreamResult) {
     return codedBitstreamResult.error();
   }
diff --git a/libheif/image-items/unc_image.h b/libheif/image-items/unc_image.h
index 1af85c92..7d02b7fd 100644
--- a/libheif/image-items/unc_image.h
+++ b/libheif/image-items/unc_image.h
@@ -87,7 +87,7 @@ public:
                                                                 const heif_encoding_options* encoding_options,
                                                                 const std::shared_ptr<const HeifPixelImage>& prototype);

-  Error add_image_tile(uint32_t tile_x, uint32_t tile_y, const std::shared_ptr<const HeifPixelImage>& image);
+  Error add_image_tile(uint32_t tile_x, uint32_t tile_y, const std::shared_ptr<const HeifPixelImage>& image, bool save_alpha);

 protected:
   Result<std::shared_ptr<Decoder>> get_decoder() const override;