Commit 39fe7e86 for libheif
commit 39fe7e865d0c321fc88258b6bca46d1e9dd72c00
Author: Dirk Farin <dirk.farin@gmail.com>
Date: Tue Feb 10 09:11:14 2026 +0100
unci: new encoder for packed component interleaving
diff --git a/libheif/CMakeLists.txt b/libheif/CMakeLists.txt
index efa15282..5021d7a7 100644
--- a/libheif/CMakeLists.txt
+++ b/libheif/CMakeLists.txt
@@ -317,6 +317,8 @@ if (WITH_UNCOMPRESSED_CODEC)
codecs/uncompressed/unc_encoder_rgb3_rgba.h
codecs/uncompressed/unc_encoder_rrggbb.cc
codecs/uncompressed/unc_encoder_rrggbb.h
+ codecs/uncompressed/unc_encoder_packed_planar.cc
+ codecs/uncompressed/unc_encoder_packed_planar.h
codecs/uncompressed/unc_encoder_planar.cc
codecs/uncompressed/unc_encoder_planar.h
codecs/uncompressed/unc_encoder_rgb_hdr_packed_interleave.cc
diff --git a/libheif/codecs/uncompressed/unc_decoder.cc b/libheif/codecs/uncompressed/unc_decoder.cc
index 7d74d59a..4ea7db02 100644
--- a/libheif/codecs/uncompressed/unc_decoder.cc
+++ b/libheif/codecs/uncompressed/unc_decoder.cc
@@ -357,7 +357,7 @@ Result<std::unique_ptr<unc_decoder>> unc_decoder_factory::get_unc_decoder(
}
std::stringstream sstr;
- sstr << "Uncompressed interleave_type of " << ((int) uncC->get_interleave_type()) << " is not implemented yet";
+ sstr << "No decoder found for uncompressed format (interleave_type of " << ((int) uncC->get_interleave_type()) << ")";
return Error{heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, sstr.str()};
}
diff --git a/libheif/codecs/uncompressed/unc_encoder.cc b/libheif/codecs/uncompressed/unc_encoder.cc
index a0c1d1f3..cb1b3112 100644
--- a/libheif/codecs/uncompressed/unc_encoder.cc
+++ b/libheif/codecs/uncompressed/unc_encoder.cc
@@ -20,10 +20,12 @@
#include "unc_encoder.h"
+#include <cassert>
#include <cstring>
#include "pixelimage.h"
#include "unc_boxes.h"
+#include "unc_encoder_packed_planar.h"
#include "unc_encoder_planar.h"
#include "unc_encoder_rgb_hdr_packed_interleave.h"
#include "unc_encoder_rgb3_rgba.h"
@@ -31,6 +33,27 @@
#include "libheif/heif_uncompressed.h"
+heif_uncompressed_component_type heif_channel_to_component_type(heif_channel channel)
+{
+ switch (channel) {
+ case heif_channel_Y: return heif_uncompressed_component_type::component_type_Y;
+ case heif_channel_Cb: return heif_uncompressed_component_type::component_type_Cb;
+ case heif_channel_Cr: return heif_uncompressed_component_type::component_type_Cr;
+ case heif_channel_R: return heif_uncompressed_component_type::component_type_red;
+ case heif_channel_G: return heif_uncompressed_component_type::component_type_green;
+ case heif_channel_B: return heif_uncompressed_component_type::component_type_blue;
+ case heif_channel_Alpha: return heif_uncompressed_component_type::component_type_alpha;
+ case heif_channel_interleaved: assert(false);
+ break;
+ case heif_channel_filter_array: return heif_uncompressed_component_type::component_type_filter_array;
+ case heif_channel_depth: return heif_uncompressed_component_type::component_type_depth;
+ case heif_channel_disparity: return heif_uncompressed_component_type::component_type_disparity;
+ }
+
+ return heif_uncompressed_component_type::component_type_padded;
+}
+
+
heif_uncompressed_component_format to_unc_component_format(heif_channel_datatype channel_datatype)
{
switch (channel_datatype) {
@@ -64,12 +87,14 @@ Result<std::unique_ptr<const unc_encoder> > unc_encoder_factory::get_unc_encoder
static unc_encoder_factory_rgb3_rgba enc_rgb3_rgba;
static unc_encoder_factory_rgb_hdr_packed_interleave enc_rgb10_12;
static unc_encoder_factory_rrggbb enc_rrggbb;
+ static unc_encoder_factory_packed_planar enc_packed_planar;
static unc_encoder_factory_planar enc_planar;
static const unc_encoder_factory* encoders[]{
&enc_rgb3_rgba,
&enc_rgb10_12,
&enc_rrggbb,
+ &enc_packed_planar,
&enc_planar
};
diff --git a/libheif/codecs/uncompressed/unc_encoder.h b/libheif/codecs/uncompressed/unc_encoder.h
index 9b11f588..e7484186 100644
--- a/libheif/codecs/uncompressed/unc_encoder.h
+++ b/libheif/codecs/uncompressed/unc_encoder.h
@@ -25,11 +25,14 @@
#include "error.h"
#include "codecs/encoder.h"
#include "libheif/heif_encoding.h"
+#include "unc_types.h"
class Box_uncC;
class Box_cmpd;
class HeifPixelImage;
+heif_uncompressed_component_type heif_channel_to_component_type(heif_channel channel);
+
class unc_encoder
{
diff --git a/libheif/codecs/uncompressed/unc_encoder_packed_planar.cc b/libheif/codecs/uncompressed/unc_encoder_packed_planar.cc
new file mode 100644
index 00000000..a32a4983
--- /dev/null
+++ b/libheif/codecs/uncompressed/unc_encoder_packed_planar.cc
@@ -0,0 +1,196 @@
+/*
+ * 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 "unc_encoder_packed_planar.h"
+
+#include <cstring>
+
+#include "pixelimage.h"
+#include "unc_boxes.h"
+
+
+bool unc_encoder_factory_packed_planar::can_encode(const std::shared_ptr<const HeifPixelImage>& image,
+ const heif_encoding_options& options) const
+{
+ if (image->has_channel(heif_channel_interleaved)) {
+ return false;
+ }
+
+ // Check if any component has non-byte-aligned bpp
+ for (auto channel : {heif_channel_Y, heif_channel_Cb, heif_channel_Cr,
+ heif_channel_R, heif_channel_G, heif_channel_B,
+ heif_channel_Alpha, heif_channel_filter_array,
+ heif_channel_depth, heif_channel_disparity}) {
+ if (image->has_channel(channel)) {
+ int bpp = image->get_bits_per_pixel(channel);
+ if (bpp % 8 != 0) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+
+std::unique_ptr<const unc_encoder> unc_encoder_factory_packed_planar::create(const std::shared_ptr<const HeifPixelImage>& image,
+ const heif_encoding_options& options) const
+{
+ return std::make_unique<unc_encoder_packed_planar>(image, options);
+}
+
+
+void unc_encoder_packed_planar::add_channel_if_exists(const std::shared_ptr<const HeifPixelImage>& image, heif_channel channel)
+{
+ if (image->has_channel(channel)) {
+ uint8_t bpp = image->get_bits_per_pixel(channel);
+ m_components.push_back({channel, heif_channel_to_component_type(channel), bpp});
+ }
+}
+
+
+unc_encoder_packed_planar::unc_encoder_packed_planar(const std::shared_ptr<const HeifPixelImage>& image,
+ const heif_encoding_options& options)
+{
+ // Special case for heif_channel_Y:
+ // - if this is a YCbCr image, use component_type_Y,
+ // - otherwise, use component_type_monochrome
+
+ if (image->has_channel(heif_channel_Y)) {
+ uint8_t bpp = image->get_bits_per_pixel(heif_channel_Y);
+ if (image->has_channel(heif_channel_Cb) && image->has_channel(heif_channel_Cr)) {
+ m_components.push_back({heif_channel_Y, heif_uncompressed_component_type::component_type_Y, bpp});
+ }
+ else {
+ m_components.push_back({heif_channel_Y, heif_uncompressed_component_type::component_type_monochrome, bpp});
+ }
+ }
+
+ add_channel_if_exists(image, heif_channel_Cb);
+ add_channel_if_exists(image, heif_channel_Cr);
+ add_channel_if_exists(image, heif_channel_R);
+ add_channel_if_exists(image, heif_channel_G);
+ add_channel_if_exists(image, heif_channel_B);
+ add_channel_if_exists(image, heif_channel_Alpha);
+ add_channel_if_exists(image, heif_channel_filter_array);
+ add_channel_if_exists(image, heif_channel_depth);
+ add_channel_if_exists(image, heif_channel_disparity);
+
+
+ uint16_t index = 0;
+ for (const auto& comp : m_components) {
+ m_cmpd->add_component({comp.component_type});
+ m_uncC->add_component({index, comp.bpp, component_format_unsigned, 0});
+ index++;
+ }
+
+ m_uncC->set_interleave_type(interleave_mode_component);
+ m_uncC->set_components_little_endian(false);
+ m_uncC->set_block_size(0);
+
+ if (image->get_chroma_format() == heif_chroma_420) {
+ m_uncC->set_sampling_type(sampling_mode_420);
+ }
+ else if (image->get_chroma_format() == heif_chroma_422) {
+ m_uncC->set_sampling_type(sampling_mode_422);
+ }
+ else {
+ m_uncC->set_sampling_type(sampling_mode_no_subsampling);
+ }
+}
+
+
+uint64_t unc_encoder_packed_planar::compute_tile_data_size_bytes(uint32_t tile_width, uint32_t tile_height) const
+{
+ uint64_t total = 0;
+ for (const auto& comp : m_components) {
+ uint32_t plane_width = tile_width;
+ uint32_t plane_height = tile_height;
+
+ if (comp.channel == heif_channel_Cb || comp.channel == heif_channel_Cr) {
+ // Adjust for chroma subsampling
+ if (m_uncC->get_sampling_type() == sampling_mode_420) {
+ plane_width = (plane_width + 1) / 2;
+ plane_height = (plane_height + 1) / 2;
+ }
+ else if (m_uncC->get_sampling_type() == sampling_mode_422) {
+ plane_width = (plane_width + 1) / 2;
+ }
+ }
+
+ uint64_t row_bytes = (static_cast<uint64_t>(plane_width) * comp.bpp + 7) / 8;
+ total += row_bytes * plane_height;
+ }
+ return total;
+}
+
+
+std::vector<uint8_t> unc_encoder_packed_planar::encode_tile(const std::shared_ptr<const HeifPixelImage>& src_image) const
+{
+ uint64_t total_size = compute_tile_data_size_bytes(src_image->get_width(), src_image->get_height());
+ std::vector<uint8_t> data;
+ data.reserve(total_size);
+
+ for (const auto& comp : m_components) {
+ uint32_t plane_width = src_image->get_width(comp.channel);
+ uint32_t plane_height = src_image->get_height(comp.channel);
+ uint8_t bpp = comp.bpp;
+
+ size_t src_stride;
+ const uint8_t* src_data = src_image->get_plane(comp.channel, &src_stride);
+
+ for (uint32_t y = 0; y < plane_height; y++) {
+ const uint8_t* row = src_data + src_stride * y;
+
+ uint64_t accumulator = 0;
+ int accumulated_bits = 0;
+
+ for (uint32_t x = 0; x < plane_width; x++) {
+ uint32_t sample;
+
+ if (bpp <= 8) {
+ sample = row[x];
+ }
+ else if (bpp <= 16) {
+ sample = reinterpret_cast<const uint16_t*>(row)[x];
+ }
+ else {
+ sample = reinterpret_cast<const uint32_t*>(row)[x];
+ }
+
+ accumulator = (accumulator << bpp) | sample;
+ accumulated_bits += bpp;
+
+ while (accumulated_bits >= 8) {
+ accumulated_bits -= 8;
+ data.push_back(static_cast<uint8_t>(accumulator >> accumulated_bits));
+ accumulator &= (uint64_t{1} << accumulated_bits) - 1;
+ }
+ }
+
+ // Flush partial byte at row end (pad with zeros in LSBs)
+ if (accumulated_bits > 0) {
+ data.push_back(static_cast<uint8_t>(accumulator << (8 - accumulated_bits)));
+ }
+ }
+ }
+
+ return data;
+}
diff --git a/libheif/codecs/uncompressed/unc_encoder_packed_planar.h b/libheif/codecs/uncompressed/unc_encoder_packed_planar.h
new file mode 100644
index 00000000..9d5ad39a
--- /dev/null
+++ b/libheif/codecs/uncompressed/unc_encoder_packed_planar.h
@@ -0,0 +1,63 @@
+/*
+ * 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_UNC_ENCODER_PACKED_PLANAR_H
+#define LIBHEIF_UNC_ENCODER_PACKED_PLANAR_H
+
+#include "unc_encoder.h"
+#include "unc_types.h"
+
+class unc_encoder_packed_planar : public unc_encoder
+{
+public:
+ unc_encoder_packed_planar(const std::shared_ptr<const HeifPixelImage>& image,
+ const heif_encoding_options& options);
+
+ uint64_t compute_tile_data_size_bytes(uint32_t tile_width, uint32_t tile_height) const override;
+
+ [[nodiscard]] std::vector<uint8_t> encode_tile(const std::shared_ptr<const HeifPixelImage>& image) const override;
+
+private:
+ struct channel_component
+ {
+ heif_channel channel;
+ heif_uncompressed_component_type component_type;
+ uint8_t bpp;
+ };
+
+ std::vector<channel_component> m_components;
+
+ void add_channel_if_exists(const std::shared_ptr<const HeifPixelImage>& image, heif_channel channel);
+};
+
+
+class unc_encoder_factory_packed_planar : public unc_encoder_factory
+{
+public:
+
+private:
+ [[nodiscard]] bool can_encode(const std::shared_ptr<const HeifPixelImage>& image,
+ const heif_encoding_options& options) const override;
+
+ std::unique_ptr<const unc_encoder> create(const std::shared_ptr<const HeifPixelImage>& image,
+ const heif_encoding_options& options) const override;
+};
+
+#endif //LIBHEIF_UNC_ENCODER_PACKED_PLANAR_H
diff --git a/libheif/codecs/uncompressed/unc_encoder_planar.cc b/libheif/codecs/uncompressed/unc_encoder_planar.cc
index 6e65eb03..6bd34c83 100644
--- a/libheif/codecs/uncompressed/unc_encoder_planar.cc
+++ b/libheif/codecs/uncompressed/unc_encoder_planar.cc
@@ -39,27 +39,6 @@ std::unique_ptr<const unc_encoder> unc_encoder_factory_planar::create(const std:
}
-heif_uncompressed_component_type heif_channel_to_component_type(heif_channel channel)
-{
- switch (channel) {
- case heif_channel_Y: return heif_uncompressed_component_type::component_type_Y;
- case heif_channel_Cb: return heif_uncompressed_component_type::component_type_Cb;
- case heif_channel_Cr: return heif_uncompressed_component_type::component_type_Cr;
- case heif_channel_R: return heif_uncompressed_component_type::component_type_red;
- case heif_channel_G: return heif_uncompressed_component_type::component_type_green;
- case heif_channel_B: return heif_uncompressed_component_type::component_type_blue;
- case heif_channel_Alpha: return heif_uncompressed_component_type::component_type_alpha;
- case heif_channel_interleaved: assert(false);
- break;
- case heif_channel_filter_array: return heif_uncompressed_component_type::component_type_filter_array;
- case heif_channel_depth: return heif_uncompressed_component_type::component_type_depth;
- case heif_channel_disparity: return heif_uncompressed_component_type::component_type_disparity;
- }
-
- return heif_uncompressed_component_type::component_type_padded;
-}
-
-
void unc_encoder_planar::add_channel_if_exists(const std::shared_ptr<const HeifPixelImage>& image, heif_channel channel)
{
if (image->has_channel(channel)) {