Commit 6eee0dd9 for libheif

commit 6eee0dd930bbd19e5dc5b1f0d8e24c9afad56054
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Fri Feb 27 10:26:28 2026 +0100

    heif-gen-bayer: add HDR support

diff --git a/examples/heif_gen_bayer.cc b/examples/heif_gen_bayer.cc
index 7238f28c..9a6e69dc 100644
--- a/examples/heif_gen_bayer.cc
+++ b/examples/heif_gen_bayer.cc
@@ -217,6 +217,7 @@ static void print_usage()
             << "       heif-gen-bayer -S [options] <frame_NNN.png> <output.mp4>\n\n"
             << "Options:\n"
             << "  -h, --help              show this help\n"
+            << "  -b, --bit-depth #       output bit depth (default: 8, range: 8-16)\n"
             << "  -p, --pattern <name>    filter array pattern (default: rggb)\n"
             << "  -S, --sequence          sequence mode (expand numbered PNGs)\n"
             << "  -V, --video             use video track handler (vide) instead of pict\n"
@@ -232,11 +233,12 @@ static void print_usage()


 static struct option long_options[] = {
-    {(char* const) "help",     no_argument,       nullptr, 'h'},
-    {(char* const) "pattern",  required_argument, nullptr, 'p'},
-    {(char* const) "sequence", no_argument,       nullptr, 'S'},
-    {(char* const) "video",    no_argument,       nullptr, 'V'},
-    {(char* const) "fps",      required_argument, nullptr, 'f'},
+    {(char* const) "help",      no_argument,       nullptr, 'h'},
+    {(char* const) "bit-depth", required_argument, nullptr, 'b'},
+    {(char* const) "pattern",   required_argument, nullptr, 'p'},
+    {(char* const) "sequence",  no_argument,       nullptr, 'S'},
+    {(char* const) "video",     no_argument,       nullptr, 'V'},
+    {(char* const) "fps",       required_argument, nullptr, 'f'},
     {nullptr, 0, nullptr, 0}
 };

@@ -245,11 +247,12 @@ static struct option long_options[] = {
 // If expected_width/expected_height are non-zero, the PNG must match those dimensions.
 static heif_image* create_bayer_image_from_png(const char* png_filename,
                                                const PatternDefinition* pat,
+                                               int output_bit_depth,
                                                int expected_width,
                                                int expected_height)
 {
   InputImage input_image;
-  heif_error err = loadPNG(png_filename, 8, &input_image);
+  heif_error err = loadPNG(png_filename, output_bit_depth, &input_image);
   if (err.code != heif_error_Ok) {
     std::cerr << "Cannot load PNG '" << png_filename << "': " << err.message << "\n";
     return nullptr;
@@ -260,12 +263,6 @@ static heif_image* create_bayer_image_from_png(const char* png_filename,
   int width = heif_image_get_primary_width(src_img);
   int height = heif_image_get_primary_height(src_img);

-  int bpp = heif_image_get_bits_per_pixel_range(src_img, heif_channel_interleaved);
-  if (bpp != 8) {
-    std::cerr << "Only 8-bit PNG input is supported. Got " << bpp << " bits per pixel.\n";
-    return nullptr;
-  }
-
   if (expected_width != 0 && (width != expected_width || height != expected_height)) {
     std::cerr << "Frame '" << png_filename << "' has dimensions " << width << "x" << height
               << " but expected " << expected_width << "x" << expected_height << "\n";
@@ -298,37 +295,70 @@ static heif_image* create_bayer_image_from_png(const char* png_filename,
     return nullptr;
   }

-  err = heif_image_add_plane(bayer_img, heif_channel_filter_array, width, height, 8);
+  err = heif_image_add_plane(bayer_img, heif_channel_filter_array, width, height, output_bit_depth);
   if (err.code != heif_error_Ok) {
     std::cerr << "Cannot add plane: " << err.message << "\n";
     heif_image_release(bayer_img);
     return nullptr;
   }

-  int dst_stride;
-  uint8_t* dst_data = heif_image_get_plane(bayer_img, heif_channel_filter_array, &dst_stride);
-
   // Convert RGB to filter array using the selected pattern
-  for (int y = 0; y < height; y++) {
-    const uint8_t* src_row = src_data + y * src_stride;
-    uint8_t* dst_row = dst_data + y * dst_stride;
-
-    for (int x = 0; x < width; x++) {
-      uint8_t r = src_row[x * 3 + 0];
-      uint8_t g = src_row[x * 3 + 1];
-      uint8_t b = src_row[x * 3 + 2];
-
-      int px = x % pat->width;
-      int py = y % pat->height;
-      uint16_t comp_type = pat->cpat[py * pat->width + px].component_type;
-
-      switch (comp_type) {
-        case heif_uncompressed_component_type_red:   dst_row[x] = r; break;
-        case heif_uncompressed_component_type_green: dst_row[x] = g; break;
-        case heif_uncompressed_component_type_blue:  dst_row[x] = b; break;
-        case heif_uncompressed_component_type_Y: dst_row[x] = static_cast<uint8_t>((r + g + b) / 3); break;
-        default:
-          assert(false);
+  if (output_bit_depth == 8) {
+    int dst_stride;
+    uint8_t* dst_data = heif_image_get_plane(bayer_img, heif_channel_filter_array, &dst_stride);
+
+    for (int y = 0; y < height; y++) {
+      const uint8_t* src_row = src_data + y * src_stride;
+      uint8_t* dst_row = dst_data + y * dst_stride;
+
+      for (int x = 0; x < width; x++) {
+        uint8_t r = src_row[x * 3 + 0];
+        uint8_t g = src_row[x * 3 + 1];
+        uint8_t b = src_row[x * 3 + 2];
+
+        int px = x % pat->width;
+        int py = y % pat->height;
+        uint16_t comp_type = pat->cpat[py * pat->width + px].component_type;
+
+        switch (comp_type) {
+          case heif_uncompressed_component_type_red:   dst_row[x] = r; break;
+          case heif_uncompressed_component_type_green: dst_row[x] = g; break;
+          case heif_uncompressed_component_type_blue:  dst_row[x] = b; break;
+          case heif_uncompressed_component_type_Y: dst_row[x] = static_cast<uint8_t>((r + g + b) / 3); break;
+          default:
+            assert(false);
+        }
+      }
+    }
+  }
+  else {
+    int dst_stride;
+    uint8_t* dst_raw = heif_image_get_plane(bayer_img, heif_channel_filter_array, &dst_stride);
+    auto* dst_data = reinterpret_cast<uint16_t*>(dst_raw);
+    int dst_stride16 = dst_stride / 2;
+
+    for (int y = 0; y < height; y++) {
+      const uint8_t* src_row = src_data + y * src_stride;
+      uint16_t* dst_row = dst_data + y * dst_stride16;
+
+      for (int x = 0; x < width; x++) {
+        // Source is little-endian uint16_t per component, 3 components per pixel
+        uint16_t r = src_row[x * 6 + 0] | (src_row[x * 6 + 1] << 8);
+        uint16_t g = src_row[x * 6 + 2] | (src_row[x * 6 + 3] << 8);
+        uint16_t b = src_row[x * 6 + 4] | (src_row[x * 6 + 5] << 8);
+
+        int px = x % pat->width;
+        int py = y % pat->height;
+        uint16_t comp_type = pat->cpat[py * pat->width + px].component_type;
+
+        switch (comp_type) {
+          case heif_uncompressed_component_type_red:   dst_row[x] = r; break;
+          case heif_uncompressed_component_type_green: dst_row[x] = g; break;
+          case heif_uncompressed_component_type_blue:  dst_row[x] = b; break;
+          case heif_uncompressed_component_type_Y: dst_row[x] = static_cast<uint16_t>((r + g + b) / 3); break;
+          default:
+            assert(false);
+        }
       }
     }
   }
@@ -349,6 +379,7 @@ static heif_image* create_bayer_image_from_png(const char* png_filename,

 static int encode_sequence(const std::vector<std::string>& filenames,
                            const PatternDefinition* pat,
+                           int output_bit_depth,
                            int fps,
                            bool use_video_handler,
                            const char* output_filename)
@@ -371,6 +402,7 @@ static int encode_sequence(const std::vector<std::string>& filenames,

   for (size_t i = 0; i < filenames.size(); i++) {
     heif_image* bayer_img = create_bayer_image_from_png(filenames[i].c_str(), pat,
+                                                        output_bit_depth,
                                                         first_width, first_height);
     if (!bayer_img) {
       heif_sequence_encoding_options_release(enc_options);
@@ -453,13 +485,14 @@ static int encode_sequence(const std::vector<std::string>& filenames,
 int main(int argc, char* argv[])
 {
   const PatternDefinition* pat = &patterns[0]; // default: RGGB
+  int output_bit_depth = 8;
   bool sequence_mode = false;
   bool use_video_handler = false;
   int fps = 30;

   while (true) {
     int option_index = 0;
-    int c = getopt_long(argc, argv, "hp:SV", long_options, &option_index);
+    int c = getopt_long(argc, argv, "hb:p:SV", long_options, &option_index);
     if (c == -1)
       break;

@@ -468,6 +501,14 @@ int main(int argc, char* argv[])
         print_usage();
         return 0;

+      case 'b':
+        output_bit_depth = std::atoi(optarg);
+        if (output_bit_depth < 8 || output_bit_depth > 16) {
+          std::cerr << "Invalid bit depth: " << optarg << " (must be 8-16)\n";
+          return 1;
+        }
+        break;
+
       case 'p':
         pat = find_pattern(optarg);
         if (!pat) {
@@ -517,12 +558,12 @@ int main(int argc, char* argv[])
     }

     std::cout << "Found " << filenames.size() << " frame(s), encoding at " << fps << " fps\n";
-    return encode_sequence(filenames, pat, fps, use_video_handler, output_filename);
+    return encode_sequence(filenames, pat, output_bit_depth, fps, use_video_handler, output_filename);
   }

   // --- Single image mode

-  heif_image* bayer_img = create_bayer_image_from_png(input_filename, pat, 0, 0);
+  heif_image* bayer_img = create_bayer_image_from_png(input_filename, pat, output_bit_depth, 0, 0);
   if (!bayer_img) {
     return 1;
   }