Commit 3a1a8be7 for libheif
commit 3a1a8be758b266692b35b51d6f8ec9ab42b4c3fd
Author: Dirk Farin <dirk.farin@gmail.com>
Date: Sat Dec 13 23:57:40 2025 +0100
[BSD3] write alpha channel track
diff --git a/examples/heif_enc.cc b/examples/heif_enc.cc
index 8b158f72..a25c9b33 100644
--- a/examples/heif_enc.cc
+++ b/examples/heif_enc.cc
@@ -2348,6 +2348,7 @@ int do_encode_sequence(heif_context* context, heif_encoder* encoder, heif_encodi
encoding_options->gop_structure = sequence_gop_structure;
encoding_options->keyframe_distance_min = sequence_keyframe_distance_min;
encoding_options->keyframe_distance_max = sequence_keyframe_distance_max;
+ encoding_options->save_alpha_channel = master_alpha;
image_width = static_cast<uint16_t>(w);
image_height = static_cast<uint16_t>(h);
diff --git a/libheif/api/libheif/heif_sequences.cc b/libheif/api/libheif/heif_sequences.cc
index 433593ed..eb16d291 100644
--- a/libheif/api/libheif/heif_sequences.cc
+++ b/libheif/api/libheif/heif_sequences.cc
@@ -411,6 +411,7 @@ heif_sequence_encoding_options* heif_sequence_encoding_options_alloc()
options->gop_structure = heif_sequence_gop_structure_lowdelay;
options->keyframe_distance_min = 0;
options->keyframe_distance_max = 0;
+ options->save_alpha_channel = 1;
return options;
}
diff --git a/libheif/api/libheif/heif_sequences.h b/libheif/api/libheif/heif_sequences.h
index 431893bf..84fa3583 100644
--- a/libheif/api/libheif/heif_sequences.h
+++ b/libheif/api/libheif/heif_sequences.h
@@ -312,6 +312,8 @@ void heif_track_options_set_timescale(heif_track_options*, uint32_t timescale);
*
* If 'false', all aux_info will be written as one block after the compressed image data.
* This has the advantage that no aux_info offsets have to be written.
+ *
+ * Note: currently ignored. Interleaved writing is disabled.
*/
LIBHEIF_API
void heif_track_options_set_interleaved_sample_aux_infos(heif_track_options*, int interleaved_flag);
@@ -367,6 +369,8 @@ typedef struct heif_sequence_encoding_options
enum heif_sequence_gop_structure gop_structure;
int keyframe_distance_min; // 0 - undefined
int keyframe_distance_max; // 0 - undefined
+
+ int save_alpha_channel;
} heif_sequence_encoding_options;
diff --git a/libheif/context.cc b/libheif/context.cc
index 65b26f46..958abef8 100644
--- a/libheif/context.cc
+++ b/libheif/context.cc
@@ -1404,7 +1404,7 @@ Result<std::shared_ptr<HeifPixelImage>> HeifContext::convert_to_output_colorspac
}
-static Result<std::shared_ptr<HeifPixelImage>>
+Result<std::shared_ptr<HeifPixelImage>>
create_alpha_image_from_image_alpha_channel(const std::shared_ptr<HeifPixelImage>& image,
const heif_security_limits* limits)
{
diff --git a/libheif/context.h b/libheif/context.h
index 8c19b0dd..ae3c8148 100644
--- a/libheif/context.h
+++ b/libheif/context.h
@@ -54,6 +54,11 @@ class Track;
struct TrackOptions;
+Result<std::shared_ptr<HeifPixelImage>>
+create_alpha_image_from_image_alpha_channel(const std::shared_ptr<HeifPixelImage>& image,
+ const heif_security_limits* limits);
+
+
// This is a higher-level view than HeifFile.
// Images are grouped logically into main images and their thumbnails.
// The class also handles automatic color-space conversion.
diff --git a/libheif/sequences/track.cc b/libheif/sequences/track.cc
index 57f59945..f415d0b8 100644
--- a/libheif/sequences/track.cc
+++ b/libheif/sequences/track.cc
@@ -92,6 +92,7 @@ void SampleAuxInfoHelper::add_nonpresent_sample()
void SampleAuxInfoHelper::write_interleaved(const std::shared_ptr<HeifFile>& file)
{
if (m_interleaved && !m_data.empty()) {
+ // TODO: I think this does not work because the image data does not know that there is SAI in-between
uint64_t pos = file->append_mdat_data(m_data);
m_saio->add_sample_offset(pos);
@@ -601,7 +602,21 @@ heif_auxiliary_track_info_type Track::get_auxiliary_info_type() const
}
}
+const char* get_track_auxiliary_info_type(heif_compression_format format)
+{
+ switch (format) {
+ case heif_compression_HEVC:
+ return cAuxType_alpha_hevc;
+ case heif_compression_AVC:
+ return cAuxType_alpha_avc;
+ case heif_compression_AV1:
+ return cAuxType_alpha_miaf;
+ default:
+ return cAuxType_alpha_miaf; // TODO: is this correct for all remaining compression types ?
+ }
+}
+// TODO: is this correct or should we set the aux_info_type depending on the compression format?
void Track::set_auxiliary_info_type(heif_auxiliary_track_info_type type)
{
switch (type) {
@@ -661,6 +676,28 @@ bool Track::end_of_sequence_reached() const
Error Track::finalize_track()
{
+ // --- write active chunk data
+
+ size_t data_start = m_heif_context->get_heif_file()->append_mdat_data(m_chunk_data);
+ m_chunk_data.clear();
+
+ // first sample in chunk? -> write chunk offset
+
+ if (true) { // m_stsc->last_chunk_empty()) {
+ // TODO: we will have to call this at the end of a chunk to dump the current SAI queue
+
+ // if auxiliary data is interleaved, write it between the chunks
+ if (m_aux_helper_tai_timestamps) m_aux_helper_tai_timestamps->write_interleaved(get_file());
+ if (m_aux_helper_content_ids) m_aux_helper_content_ids->write_interleaved(get_file());
+
+ // TODO
+ assert(data_start < 0xFF000000); // add some headroom for header data
+ m_stco->add_chunk_offset(static_cast<uint32_t>(data_start));
+ }
+
+
+ // --- write rest of data
+
if (m_aux_helper_tai_timestamps) m_aux_helper_tai_timestamps->write_all(m_stbl, get_file());
if (m_aux_helper_content_ids) m_aux_helper_content_ids->write_all(m_stbl, get_file());
@@ -742,19 +779,7 @@ void Track::set_sample_description_box(std::shared_ptr<Box> sample_description_b
Error Track::write_sample_data(const std::vector<uint8_t>& raw_data, uint32_t sample_duration, bool is_sync_sample,
const heif_tai_timestamp_packet* tai, const std::optional<std::string>& gimi_contentID)
{
- size_t data_start = m_heif_context->get_heif_file()->append_mdat_data(raw_data);
-
- // first sample in chunk? -> write chunk offset
-
- if (m_stsc->last_chunk_empty()) {
- // if auxiliary data is interleaved, write it between the chunks
- if (m_aux_helper_tai_timestamps) m_aux_helper_tai_timestamps->write_interleaved(get_file());
- if (m_aux_helper_content_ids) m_aux_helper_content_ids->write_interleaved(get_file());
-
- // TODO
- assert(data_start < 0xFF000000); // add some headroom for header data
- m_stco->add_chunk_offset(static_cast<uint32_t>(data_start));
- }
+ m_chunk_data.insert(m_chunk_data.end(), raw_data.begin(), raw_data.end());
m_stsc->increase_samples_in_chunk(1);
@@ -849,7 +874,8 @@ void Track::init_sample_timing_table()
timing.sample_duration_media_time = m_stts->get_sample_duration(i);
current_decoding_time += timing.sample_duration_media_time;
- while (i > m_chunks[current_chunk]->last_sample_number()) {
+ while (current_chunk < m_chunks.size() &&
+ i > m_chunks[current_chunk]->last_sample_number()) {
current_chunk++;
if (current_chunk > m_chunks.size()) {
diff --git a/libheif/sequences/track.h b/libheif/sequences/track.h
index 081aa8af..b445e66a 100644
--- a/libheif/sequences/track.h
+++ b/libheif/sequences/track.h
@@ -119,6 +119,9 @@ struct TrackOptions
};
+const char* get_track_auxiliary_info_type(heif_compression_format format);
+
+
class Track : public ErrorBuffer {
public:
//Track(HeifContext* ctx);
@@ -220,6 +223,7 @@ protected:
void init_sample_timing_table();
std::vector<std::shared_ptr<Chunk>> m_chunks;
+ std::vector<uint8_t> m_chunk_data;
std::shared_ptr<Box_moov> m_moov;
std::shared_ptr<Box_trak> m_trak;
diff --git a/libheif/sequences/track_visual.cc b/libheif/sequences/track_visual.cc
index 3134228d..53b056a2 100644
--- a/libheif/sequences/track_visual.cc
+++ b/libheif/sequences/track_visual.cc
@@ -19,6 +19,8 @@
*/
#include "track_visual.h"
+
+#include <memory>
#include "codecs/decoder.h"
#include "codecs/encoder.h"
#include "chunk.h"
@@ -321,6 +323,16 @@ Error Track_Visual::encode_end_of_sequence(heif_encoder* h_encoder)
}
}
+
+ // --- also end alpha track
+
+ if (m_aux_alpha_track) {
+ auto err = m_aux_alpha_track->encode_end_of_sequence(m_alpha_track_encoder.get());
+ if (err) {
+ return err;
+ }
+ }
+
return {};
}
@@ -339,6 +351,50 @@ Error Track_Visual::encode_image(std::shared_ptr<HeifPixelImage> image,
};
}
+ m_image_class = input_class;
+
+
+ // --- If input has an alpha channel, add an alpha auxiliary track.
+
+ if (in_options->save_alpha_channel && image->has_alpha() && !m_aux_alpha_track) {
+ if (m_active_encoder) {
+ return {
+ heif_error_Usage_error,
+ heif_suberror_Unspecified,
+ "Input images must all either have an alpha channel or none of them."
+ };
+ }
+ else {
+ // alpha track uses default options, same timescale as color track
+
+ TrackOptions alphaOptions;
+ alphaOptions.track_timescale = m_track_info.track_timescale;
+
+ auto newAlphaTrackResult = m_heif_context->add_visual_sequence_track(&alphaOptions,
+ heif_track_type_auxiliary,
+ static_cast<uint16_t>(image->get_width()),
+ static_cast<uint16_t>(image->get_height()));
+
+ if (auto err = newAlphaTrackResult.error()) {
+ return err;
+ }
+
+ // add a reference to the color track
+
+ m_aux_alpha_track = *newAlphaTrackResult;
+ m_aux_alpha_track->add_reference_to_track(fourcc("auxl"), m_id);
+
+ // make a copy of the encoder from the color track for encoding the alpha track
+
+ m_alpha_track_encoder = std::make_unique<heif_encoder>(h_encoder->plugin);
+ heif_error err = m_alpha_track_encoder->alloc();
+ if (err.code) {
+ return {err.code, err.subcode, err.message};
+ }
+ }
+ }
+
+
if (!m_active_encoder) {
m_active_encoder = h_encoder;
}
@@ -462,7 +518,32 @@ Error Track_Visual::encode_image(std::shared_ptr<HeifPixelImage> image,
// --- get compressed data from encoder
Result<bool> processingResult = process_encoded_data(h_encoder);
- return processingResult.error();
+ if (auto err = processingResult.error()) {
+ return err;
+ }
+
+
+ // --- encode alpha channel into auxiliary track
+
+ if (m_aux_alpha_track) {
+ auto alphaImageResult = create_alpha_image_from_image_alpha_channel(colorConvertedImage,
+ m_heif_context->get_security_limits());
+ if (auto err = alphaImageResult.error()) {
+ return err;
+ }
+
+ (*alphaImageResult)->set_sample_duration(colorConvertedImage->get_sample_duration());
+
+ auto err = m_aux_alpha_track->encode_image(*alphaImageResult,
+ m_alpha_track_encoder.get(),
+ in_options,
+ heif_image_input_class_alpha);
+ if (err) {
+ return err;
+ }
+ }
+
+ return {};
}
@@ -494,12 +575,19 @@ Result<bool> Track_Visual::process_encoded_data(heif_encoder* h_encoder)
// add Coding-Constraints box (ccst) only if we are generating an image sequence
+ // TODO: does the alpha track also need a ccst box?
if (m_hdlr->get_handler_type() == heif_track_type_image_sequence) {
auto ccst = std::make_shared<Box_ccst>();
ccst->set_coding_constraints(data.codingConstraints);
sample_description_box->append_child_box(ccst);
}
+ if (m_image_class == heif_image_input_class_alpha) {
+ auto auxi_box = std::make_shared<Box_auxi>();
+ auxi_box->set_aux_track_type_urn(get_track_auxiliary_info_type(h_encoder->plugin->compression_format));
+ sample_description_box->append_child_box(auxi_box);
+ }
+
set_sample_description_box(sample_description_box);
m_generated_sample_description_box = true;
}
diff --git a/libheif/sequences/track_visual.h b/libheif/sequences/track_visual.h
index 0c40c32c..4eeab87f 100644
--- a/libheif/sequences/track_visual.h
+++ b/libheif/sequences/track_visual.h
@@ -65,6 +65,7 @@ public:
private:
uint16_t m_width = 0;
uint16_t m_height = 0;
+ heif_image_input_class m_image_class;
uintptr_t m_current_frame_nr = 0;
bool m_generated_sample_description_box = false;
@@ -92,6 +93,7 @@ private:
std::shared_ptr<Track_Visual> m_aux_alpha_track;
heif_encoder* m_active_encoder = nullptr;
+ std::unique_ptr<heif_encoder> m_alpha_track_encoder;
Result<bool> process_encoded_data(heif_encoder* encoder);
};