Commit 604d8370 for libheif

commit 604d83704387d11834ca398f8e323de9a2c0b9ea
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Sat Sep 5 21:12:04 2026 +0200

    Use the shared HEVC SPS parser in the WebCodecs decoder plugin

    decoder_webcodecs.cc carried a verbatim copy of
    parse_sps_for_hvcC_configuration() and remove_start_code_emulation() from
    hevc_boxes.cc. The copy was made when the plugin was written (issue #1600), at
    a time when it was unclear whether the plugin would be able to link against
    libheif internals. It always was: the plugin is compiled into libheif (there is
    no WITH_WEBCODECS_PLUGIN option, and the emscripten build is a static archive),
    and it already used HEVCDecoderConfigurationRecord::write() and StreamWriter,
    which are just as internal. The copy had to be patched in lockstep with the
    original several times and never received the coded_size out-parameter that
    was added to the original in f91283ac.

    Call the shared parser instead and drop the copy.

diff --git a/libheif/plugins/decoder_webcodecs.cc b/libheif/plugins/decoder_webcodecs.cc
index e939325c..cacfca0c 100644
--- a/libheif/plugins/decoder_webcodecs.cc
+++ b/libheif/plugins/decoder_webcodecs.cc
@@ -21,6 +21,7 @@
 #include "decoder_webcodecs.h"
 #include "libheif/heif_plugin.h"
 #include "codecs/hevc_boxes.h"
+#include "codecs/decoder.h"
 #include "bitstream.h"
 #include "nalu_utils.h"

@@ -198,195 +199,6 @@ EM_JS(emscripten::EM_VAL, decode_with_browser_hevc, (const char *codec_ptr, uint
 });


-static std::vector<uint8_t> remove_start_code_emulation2(const uint8_t* sps, size_t size)
-{
-  std::vector<uint8_t> out_data;
-
-  for (size_t i = 0; i < size; i++) {
-    if (i + 2 < size &&
-        sps[i] == 0 &&
-        sps[i + 1] == 0 &&
-        sps[i + 2] == 3) {
-      out_data.push_back(0);
-      out_data.push_back(0);
-      i += 2;
-    }
-    else {
-      out_data.push_back(sps[i]);
-    }
-  }
-
-  return out_data;
-}
-
-
-// Parses the SPS and fills 'config'. On return, 'width' and 'height' hold the
-// size of the visible image (after applying the conformance window), while
-// 'coded_width' and 'coded_height' hold the size of the coded picture.
-Error parse_sps_for_hvcC_configuration2(const uint8_t* sps, size_t size,
-                                       HEVCDecoderConfigurationRecord* config,
-                                       uint32_t* width, uint32_t* height,
-                                       uint32_t* coded_width, uint32_t* coded_height)
-{
-  // remove start-code emulation bytes from SPS header stream
-
-  std::vector<uint8_t> sps_no_emul = remove_start_code_emulation2(sps, size);
-
-  sps = sps_no_emul.data();
-  size = sps_no_emul.size();
-
-
-  BitReader reader(sps, size);
-
-  // skip NAL header
-  reader.skip_bits(2 * 8);
-
-  // skip VPS ID
-  reader.skip_bits(4);
-
-  uint8_t nMaxSubLayersMinus1 = reader.get_bits8(3);
-
-  config->temporal_id_nested = reader.get_bits8(1);
-
-  // --- profile_tier_level ---
-
-  config->general_profile_space = reader.get_bits8(2);
-  config->general_tier_flag = reader.get_bits8(1);
-  config->general_profile_idc = reader.get_bits8(5);
-  config->general_profile_compatibility_flags = reader.get_bits32(32);
-
-  reader.skip_bits(16); // skip reserved bits
-  reader.skip_bits(16); // skip reserved bits
-  reader.skip_bits(16); // skip reserved bits
-
-  config->general_level_idc = reader.get_bits8(8);
-
-  std::vector<bool> layer_profile_present(nMaxSubLayersMinus1);
-  std::vector<bool> layer_level_present(nMaxSubLayersMinus1);
-
-  for (int i = 0; i < nMaxSubLayersMinus1; i++) {
-    layer_profile_present[i] = reader.get_bits(1);
-    layer_level_present[i] = reader.get_bits(1);
-  }
-
-  if (nMaxSubLayersMinus1 > 0) {
-    for (int i = nMaxSubLayersMinus1; i < 8; i++) {
-      reader.skip_bits(2);
-    }
-  }
-
-  for (int i = 0; i < nMaxSubLayersMinus1; i++) {
-    if (layer_profile_present[i]) {
-      reader.skip_bits(2 + 1 + 5);
-      reader.skip_bits(32);
-      reader.skip_bits(16);
-    }
-
-    if (layer_level_present[i]) {
-      reader.skip_bits(8);
-    }
-  }
-
-
-  // --- SPS continued ---
-
-  Error invalidUVLC{
-    heif_error_Invalid_input,
-    heif_suberror_Invalid_parameter_value,
-    "Invalid variable length code in HEVC SPS header"
-  };
-
-  uint32_t dummy, value;
-  if (!reader.get_uvlc(&dummy) || // skip seq_parameter_seq_id
-      !reader.get_uvlc(&value)) {
-    return invalidUVLC;
-  }
-  if (value > 3) {
-    // chroma_format_idc is in the range 0..3 (H.265 section 7.4.3.2.1). The
-    // value is later cast to heif_chroma, so it must not be left unchecked.
-    return Error{heif_error_Invalid_input,
-                 heif_suberror_Invalid_parameter_value,
-                 "SPS chroma_format_idc out of range"};
-  }
-  config->chroma_format = (uint8_t) value;
-
-  if (config->chroma_format == 3) {
-    reader.skip_bits(1);
-  }
-
-  if (!reader.get_uvlc(width) ||
-      !reader.get_uvlc(height)) {
-    return invalidUVLC;
-  }
-
-  *coded_width = *width;
-  *coded_height = *height;
-
-  bool conformance_window = reader.get_bits(1);
-  if (conformance_window) {
-    uint32_t left, right, top, bottom;
-    if (!reader.get_uvlc(&left) ||
-        !reader.get_uvlc(&right) ||
-        !reader.get_uvlc(&top) ||
-        !reader.get_uvlc(&bottom)) {
-      return invalidUVLC;
-    }
-
-    //printf("conformance borders: %u %u %u %u\n",left,right,top,bottom);
-
-    uint32_t subH = 1, subV = 1;
-    if (config->chroma_format == 1) {
-      subV = 2;
-      subH = 2;
-    }
-    if (config->chroma_format == 2) { subH = 2; }
-
-    const uint64_t crop_w = (uint64_t)subH * ((uint64_t)left + (uint64_t)right);
-    const uint64_t crop_h = (uint64_t)subV * ((uint64_t)top + (uint64_t)bottom);
-    if (crop_w > *width || crop_h > *height) {
-      return Error{heif_error_Invalid_input,
-                   heif_suberror_Invalid_parameter_value,
-                   "SPS conformance window exceeds image dimensions"};
-    }
-    *width  -= (uint32_t)crop_w;
-    *height -= (uint32_t)crop_h;
-  }
-
-  if (!reader.get_uvlc(&value)) {
-    return invalidUVLC;
-  }
-  if (value > 8) {
-    return Error{heif_error_Invalid_input,
-                 heif_suberror_Invalid_parameter_value,
-                 "SPS bit_depth_luma_minus8 out of range"};
-  }
-  config->bit_depth_luma = (uint8_t) (value + 8);
-
-  if (!reader.get_uvlc(&value)) {
-    return invalidUVLC;
-  }
-  if (value > 8) {
-    return Error{heif_error_Invalid_input,
-                 heif_suberror_Invalid_parameter_value,
-                 "SPS bit_depth_chroma_minus8 out of range"};
-  }
-  config->bit_depth_chroma = (uint8_t) (value + 8);
-
-
-
-  // --- init static configuration fields ---
-
-  config->configuration_version = 1;
-  config->min_spatial_segmentation_idc = 0; // TODO: get this value from the VUI, 0 should be safe
-  config->parallelism_type = 0; // TODO, 0 should be safe
-  config->avg_frame_rate = 0; // makes no sense for HEIF
-  config->constant_frame_rate = 0; // makes no sense for HEIF
-  config->num_temporal_layers = 1; // makes no sense for HEIF
-
-  return Error::Ok;
-}
-
-
 static const char* webcodecs_plugin_name()
 {
   strcpy(plugin_name, "Webcodecs HEVC decoder");
@@ -792,10 +604,10 @@ static struct heif_error webcodecs_decode_image_with_limits(void* decoder_raw,
   }

   HEVCDecoderConfigurationRecord config;
-  uint32_t w, h;              // visible size after applying the conformance window
-  uint32_t coded_w, coded_h;  // size of the coded picture
-  Error err = parse_sps_for_hvcC_configuration2(sps_nal_unit.data.data(), sps_nal_unit.data.size(),
-                                                &config, &w, &h, &coded_w, &coded_h);
+  uint32_t w, h;      // visible size after applying the conformance window
+  ImageSize coded{};  // size of the coded picture
+  Error err = parse_sps_for_hvcC_configuration(sps_nal_unit.data.data(), sps_nal_unit.data.size(),
+                                               &config, &w, &h, &coded);
   if (err != Error::Ok) {
     return {heif_error_Decoder_plugin_error,
             heif_suberror_Unspecified,
@@ -805,7 +617,7 @@ static struct heif_error webcodecs_decode_image_with_limits(void* decoder_raw,
   // Reject coded picture sizes beyond the security limits before handing the
   // bitstream to the browser. libheif checks the SPS stored in the hvcC box,
   // but this plugin honours the last SPS in the NAL stream, which may differ.
-  if (coded_w == 0 || coded_h == 0 || w == 0 || h == 0) {
+  if (coded.width == 0 || coded.height == 0 || w == 0 || h == 0) {
     return {heif_error_Decoder_plugin_error,
             heif_suberror_Invalid_image_size,
             "SPS declares a zero-sized image"};
@@ -813,8 +625,8 @@ static struct heif_error webcodecs_decode_image_with_limits(void* decoder_raw,

   if (limits && limits->max_image_size_pixels > 0) {
     const auto max_dim = static_cast<uint32_t>(std::numeric_limits<int>::max());
-    if (coded_w > max_dim || coded_h > max_dim ||
-        coded_w > limits->max_image_size_pixels / coded_h) {
+    if (coded.width > max_dim || coded.height > max_dim ||
+        coded.width > limits->max_image_size_pixels / coded.height) {
       return {heif_error_Memory_allocation_error,
               heif_suberror_Security_limit_exceeded,
               "SPS coded picture size exceeds the maximum image size"};