Commit f91283ac for libheif
commit f91283aca524c33c349132fcde615563f245cfe9
Author: Dirk Farin <dirk.farin@gmail.com>
Date: Wed Apr 29 19:33:46 2026 +0200
H.264, H.265, H.266: check SPS image size in configuration boxes against libheif security limits (#1774)
diff --git a/libheif/bitstream.cc b/libheif/bitstream.cc
index f7b6a630..be29548e 100644
--- a/libheif/bitstream.cc
+++ b/libheif/bitstream.cc
@@ -696,7 +696,7 @@ void BitReader::skip_to_byte_boundary()
nextbits_cnt -= nskip;
}
-bool BitReader::get_uvlc(int* value)
+bool BitReader::get_uvlc(uint32_t* value)
{
int num_zeros = 0;
@@ -706,10 +706,9 @@ bool BitReader::get_uvlc(int* value)
if (num_zeros > MAX_UVLC_LEADING_ZEROS) { return false; }
}
- int offset = 0;
if (num_zeros != 0) {
- offset = (int) get_bits(num_zeros);
- *value = offset + (1 << num_zeros) - 1;
+ uint32_t offset = get_bits(num_zeros);
+ *value = offset + (1u << num_zeros) - 1u;
assert(*value > 0);
return true;
}
@@ -719,19 +718,19 @@ bool BitReader::get_uvlc(int* value)
}
}
-bool BitReader::get_svlc(int* value)
+bool BitReader::get_svlc(int32_t* value)
{
- int v;
+ uint32_t v;
if (!get_uvlc(&v)) {
return false;
}
else if (v == 0) {
- *value = v;
+ *value = 0;
return true;
}
- bool negative = ((v & 1) == 0);
- *value = negative ? -v / 2 : (v + 1) / 2;
+ bool negative = ((v & 1u) == 0);
+ *value = negative ? -static_cast<int32_t>(v / 2) : static_cast<int32_t>((v + 1) / 2);
return true;
}
diff --git a/libheif/bitstream.h b/libheif/bitstream.h
index a532ad74..9cd3928f 100644
--- a/libheif/bitstream.h
+++ b/libheif/bitstream.h
@@ -443,9 +443,9 @@ public:
void skip_to_byte_boundary();
- bool get_uvlc(int* value);
+ bool get_uvlc(uint32_t* value);
- bool get_svlc(int* value);
+ bool get_svlc(int32_t* value);
int get_current_byte_index() const
{
diff --git a/libheif/codecs/avc_boxes.cc b/libheif/codecs/avc_boxes.cc
index ddd945f2..16033fb6 100644
--- a/libheif/codecs/avc_boxes.cc
+++ b/libheif/codecs/avc_boxes.cc
@@ -324,7 +324,7 @@ void skip_scaling_list(BitReader& reader, int sizeOfScalingList)
#else
// fast version
for (int j = 0; j < sizeOfScalingList; j++) {
- int delta_scale;
+ int32_t delta_scale;
reader.get_svlc(&delta_scale);
nextScale = (lastScale + delta_scale + 256) % 256;
@@ -340,7 +340,8 @@ void skip_scaling_list(BitReader& reader, int sizeOfScalingList)
Error parse_sps_for_avcC_configuration(const uint8_t* sps, size_t size,
Box_avcC::configuration* config,
- int* width, int* height)
+ uint32_t* width, uint32_t* height,
+ ImageSize* coded_size)
{
// remove start-code emulation bytes from SPS header stream
@@ -361,7 +362,7 @@ Error parse_sps_for_avcC_configuration(const uint8_t* sps, size_t size,
config->AVCLevelIndication = reader.get_bits8(8);
config->lengthSize = 4;
- int value;
+ uint32_t value;
reader.get_uvlc(&value); // SPS ID
Error invalidUVLC{
@@ -413,18 +414,19 @@ Error parse_sps_for_avcC_configuration(const uint8_t* sps, size_t size,
}
reader.get_uvlc(&value); // log2_max_frame_num_minus4
- int pic_order_cnt_type;
+ uint32_t pic_order_cnt_type;
reader.get_uvlc(&pic_order_cnt_type);
if (pic_order_cnt_type == 0) {
reader.get_uvlc(&value);
}
else if (pic_order_cnt_type == 1) {
reader.get_bits(1);
- reader.get_svlc(&value);
- reader.get_svlc(&value);
- int num_ref_franes_in_pic_order_cnt_cycle;
+ int32_t svalue;
+ reader.get_svlc(&svalue);
+ reader.get_svlc(&svalue);
+ uint32_t num_ref_franes_in_pic_order_cnt_cycle;
reader.get_uvlc(&num_ref_franes_in_pic_order_cnt_cycle);
- for (int i = 0; i < num_ref_franes_in_pic_order_cnt_cycle; i++) {
+ for (uint32_t i = 0; i < num_ref_franes_in_pic_order_cnt_cycle; i++) {
reader.get_uvlc(&value);
}
}
@@ -432,14 +434,19 @@ Error parse_sps_for_avcC_configuration(const uint8_t* sps, size_t size,
reader.get_uvlc(&value); // num_ref_frames
reader.skip_bits(1);
- int pic_width_in_mbs_minus1;
- int pic_height_in_mbs_minus1;
+ uint32_t pic_width_in_mbs_minus1;
+ uint32_t pic_height_in_mbs_minus1;
reader.get_uvlc(&pic_width_in_mbs_minus1);
reader.get_uvlc(&pic_height_in_mbs_minus1);
*width = (pic_width_in_mbs_minus1 + 1) * 16;
*height = (pic_height_in_mbs_minus1 + 1) * 16;
+ if (coded_size) {
+ coded_size->width = *width;
+ coded_size->height = *height;
+ }
+
uint32_t frame_mbs_only_flag = reader.get_bits(1);
if (!frame_mbs_only_flag) {
reader.skip_bits(1);
@@ -447,7 +454,7 @@ Error parse_sps_for_avcC_configuration(const uint8_t* sps, size_t size,
reader.skip_bits(1);
uint32_t frame_cropping_flag = reader.get_bits(1);
if (frame_cropping_flag) {
- int left, right, top, bottom;
+ uint32_t left, right, top, bottom;
reader.get_uvlc(&left);
reader.get_uvlc(&right);
reader.get_uvlc(&top);
diff --git a/libheif/codecs/avc_boxes.h b/libheif/codecs/avc_boxes.h
index 22c60177..6d043604 100644
--- a/libheif/codecs/avc_boxes.h
+++ b/libheif/codecs/avc_boxes.h
@@ -113,8 +113,14 @@ public:
}
};
+struct ImageSize;
+
+// Parses an AVC/H.264 SPS NAL unit. *width / *height return the post-frame-
+// cropping (display) dimensions. If non-null, *coded_size receives the
+// pre-cropping dimensions actually allocated by the decoder.
Error parse_sps_for_avcC_configuration(const uint8_t* sps, size_t size,
Box_avcC::configuration* inout_config,
- int* width, int* height);
+ uint32_t* width, uint32_t* height,
+ ImageSize* coded_size = nullptr);
#endif
diff --git a/libheif/codecs/avc_dec.cc b/libheif/codecs/avc_dec.cc
index 8af79a4c..3a0a2ac7 100644
--- a/libheif/codecs/avc_dec.cc
+++ b/libheif/codecs/avc_dec.cc
@@ -47,6 +47,29 @@ int Decoder_AVC::get_chroma_bits_per_pixel() const
}
+Result<std::optional<ImageSize>> Decoder_AVC::get_coded_image_size_from_config() const
+{
+ const auto& sps_set = m_avcC->getSequenceParameterSets();
+
+ for (const auto& sps : sps_set) {
+ if (sps.empty()) continue;
+ Box_avcC::configuration scratch = m_avcC->get_configuration();
+ uint32_t cropped_w = 0, cropped_h = 0;
+ ImageSize coded{};
+
+ Error e = parse_sps_for_avcC_configuration(sps.data(), sps.size(), &scratch,
+ &cropped_w, &cropped_h, &coded);
+ if (e) {
+ return e;
+ }
+
+ return std::optional<ImageSize>{coded};
+ }
+
+ return std::optional<ImageSize>{};
+}
+
+
Error Decoder_AVC::get_coded_image_colorspace(heif_colorspace* out_colorspace, heif_chroma* out_chroma) const
{
*out_chroma = m_avcC->get_configuration().chroma_format;
diff --git a/libheif/codecs/avc_dec.h b/libheif/codecs/avc_dec.h
index 3d08653b..5613e533 100644
--- a/libheif/codecs/avc_dec.h
+++ b/libheif/codecs/avc_dec.h
@@ -46,6 +46,8 @@ public:
Result<std::vector<uint8_t>> read_bitstream_configuration_data() const override;
+ Result<std::optional<ImageSize>> get_coded_image_size_from_config() const override;
+
private:
const std::shared_ptr<const Box_avcC> m_avcC;
};
diff --git a/libheif/codecs/avc_enc.cc b/libheif/codecs/avc_enc.cc
index 1e6973fe..e9fed1a3 100644
--- a/libheif/codecs/avc_enc.cc
+++ b/libheif/codecs/avc_enc.cc
@@ -49,8 +49,8 @@ Result<Encoder::CodedImageData> Encoder_AVC::encode(const std::shared_ptr<HeifPi
err.message);
}
- int encoded_width = 0;
- int encoded_height = 0;
+ uint32_t encoded_width = 0;
+ uint32_t encoded_height = 0;
for (;;) {
uint8_t* data;
@@ -109,8 +109,8 @@ Result<Encoder::CodedImageData> Encoder_AVC::encode(const std::shared_ptr<HeifPi
&check_encoded_width,
&check_encoded_height);
- assert((int)check_encoded_width == encoded_width);
- assert((int)check_encoded_height == encoded_height);
+ assert(check_encoded_width == encoded_width);
+ assert(check_encoded_height == encoded_height);
}
codedImage.codingConstraints.intra_pred_used = true;
diff --git a/libheif/codecs/avc_enc.h b/libheif/codecs/avc_enc.h
index c63d406d..c879be8e 100644
--- a/libheif/codecs/avc_enc.h
+++ b/libheif/codecs/avc_enc.h
@@ -65,8 +65,8 @@ private:
std::shared_ptr<class Box_avcC> m_avcC;
bool m_avcC_sent = false;
- int m_encoded_image_width = 0;
- int m_encoded_image_height = 0;
+ uint32_t m_encoded_image_width = 0;
+ uint32_t m_encoded_image_height = 0;
std::optional<CodedImageData> m_current_output_data;
bool m_output_image_complete = false;
diff --git a/libheif/codecs/avif_boxes.cc b/libheif/codecs/avif_boxes.cc
index 3a79d791..db3e75d4 100644
--- a/libheif/codecs/avif_boxes.cc
+++ b/libheif/codecs/avif_boxes.cc
@@ -361,7 +361,7 @@ bool fill_av1C_configuration_from_stream(Box_av1C::configuration* out_config, co
// --- read sequence header
- int dummy; // throw away value
+ uint32_t dummy; // throw away value
bool decoder_model_info_present = false;
int buffer_delay_length_minus1 = 0;
diff --git a/libheif/codecs/decoder.cc b/libheif/codecs/decoder.cc
index 33690fec..5176e732 100644
--- a/libheif/codecs/decoder.cc
+++ b/libheif/codecs/decoder.cc
@@ -25,6 +25,7 @@
#include "context.h"
#include "plugin_registry.h"
#include "api_structs.h"
+#include "security_limits.h"
#include "codecs/hevc_dec.h"
#include "codecs/avif_dec.h"
@@ -322,6 +323,25 @@ Error Decoder::decode_sequence_frame_from_compressed_data(bool upload_configurat
return pluginErr;
}
+ // Reject memory-bomb inputs whose codec configuration record (SPS) declares
+ // a coded picture size beyond libheif's security limits, before handing any
+ // bytes to the decoder plugin. Codecs whose configuration record does not
+ // carry dimensions (e.g. AV1's av1C) return nullopt and skip the check.
+ //
+ // TODO: check this also in the decoder plugin since SPS packets may be
+ // found within the actual image bitstream.
+ auto codedSize = get_coded_image_size_from_config();
+ if (codedSize.is_error()) {
+ return codedSize.error();
+ }
+
+ if (codedSize->has_value()) {
+ Error sizeErr = check_for_valid_image_size(limits, (*codedSize)->width, (*codedSize)->height);
+ if (sizeErr) {
+ return sizeErr;
+ }
+ }
+
// --- decode image with the plugin
heif_error err;
diff --git a/libheif/codecs/decoder.h b/libheif/codecs/decoder.h
index e76958b8..16dbf25a 100644
--- a/libheif/codecs/decoder.h
+++ b/libheif/codecs/decoder.h
@@ -27,12 +27,23 @@
#include "file.h"
#include <memory>
+#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "image-items/hevc.h"
+// Image dimensions in luma samples. Reused wherever libheif needs to pass
+// a (width, height) pair around — initially for SPS-derived coded sizes,
+// but designed to fit other uses (ispe, image-item dimensions) over time.
+struct ImageSize
+{
+ uint32_t width;
+ uint32_t height;
+};
+
+
// Specifies the input data for decoding.
// For images, this points to the iloc extents.
// For sequences, this points to the track data.
@@ -90,6 +101,23 @@ public:
// Returns a stream of packets. Each packet is starts with a 4-byte size (MSB first).
[[nodiscard]] virtual Result<std::vector<uint8_t>> read_bitstream_configuration_data() const = 0;
+ // Returns the *coded* picture size from the codec configuration record (the
+ // SPS for HEVC/AVC/VVC) — i.e. the buffer dimensions the decoder will
+ // actually allocate, BEFORE conformance-window cropping. The cropped output
+ // size is unsuitable for security checks: a malicious file can declare a
+ // huge SPS picture size with a near-equal-sized conformance window, so the
+ // displayed image looks small while the decoder still allocates the full
+ // uncropped buffer.
+ //
+ // Returns nullopt when the codec does not store dimensions in its
+ // configuration record (e.g. AV1's av1C) or when no SPS NAL is present.
+ // Returns Error only on a structurally invalid configuration record.
+ [[nodiscard]] virtual Result<std::optional<ImageSize>>
+ get_coded_image_size_from_config() const
+ {
+ return std::optional<ImageSize>{};
+ }
+
Result<std::vector<uint8_t>> get_compressed_data(bool with_configuration_NALs) const;
// --- decoding
diff --git a/libheif/codecs/hevc_boxes.cc b/libheif/codecs/hevc_boxes.cc
index 4c7a3108..c00237f0 100644
--- a/libheif/codecs/hevc_boxes.cc
+++ b/libheif/codecs/hevc_boxes.cc
@@ -451,12 +451,12 @@ static Result<std::shared_ptr<SEIMessage>> read_depth_representation_info(BitRea
msg->has_d_min = (uint8_t) reader.get_bits(1);
msg->has_d_max = (uint8_t) reader.get_bits(1);
- int rep_type;
+ uint32_t rep_type;
if (!reader.get_uvlc(&rep_type)) {
return Error{heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, "invalid depth representation type in input"};
}
- if (rep_type < 0 || rep_type > 3) {
+ if (rep_type > 3) {
return Error{heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, "input depth representation type out of range"};
}
@@ -466,7 +466,7 @@ static Result<std::shared_ptr<SEIMessage>> read_depth_representation_info(BitRea
//printf("type: %d\n",rep_type);
if (msg->has_d_min || msg->has_d_max) {
- int ref_view;
+ uint32_t ref_view;
if (!reader.get_uvlc(&ref_view)) {
return Error{heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, "invalid disparity_reference_view in input"};
}
@@ -593,7 +593,8 @@ std::vector<uint8_t> remove_start_code_emulation(const uint8_t* sps, size_t size
Error parse_sps_for_hvcC_configuration(const uint8_t* sps, size_t size,
HEVCDecoderConfigurationRecord* config,
- int* width, int* height)
+ uint32_t* width, uint32_t* height,
+ ImageSize* coded_size)
{
// remove start-code emulation bytes from SPS header stream
@@ -657,7 +658,7 @@ Error parse_sps_for_hvcC_configuration(const uint8_t* sps, size_t size,
// --- SPS continued ---
- int dummy, value;
+ uint32_t dummy, value;
reader.get_uvlc(&dummy); // skip seq_parameter_seq_id
reader.get_uvlc(&value);
@@ -670,17 +671,22 @@ Error parse_sps_for_hvcC_configuration(const uint8_t* sps, size_t size,
reader.get_uvlc(width);
reader.get_uvlc(height);
+ if (coded_size) {
+ coded_size->width = *width;
+ coded_size->height = *height;
+ }
+
bool conformance_window = reader.get_bits(1);
if (conformance_window) {
- int left, right, top, bottom;
+ uint32_t left, right, top, bottom;
reader.get_uvlc(&left);
reader.get_uvlc(&right);
reader.get_uvlc(&top);
reader.get_uvlc(&bottom);
- //printf("conformance borders: %d %d %d %d\n",left,right,top,bottom);
+ //printf("conformance borders: %u %u %u %u\n",left,right,top,bottom);
- int subH = 1, subV = 1;
+ uint32_t subH = 1, subV = 1;
if (config->chroma_format == 1) {
subV = 2;
subH = 2;
diff --git a/libheif/codecs/hevc_boxes.h b/libheif/codecs/hevc_boxes.h
index 34e997ed..b77e30fb 100644
--- a/libheif/codecs/hevc_boxes.h
+++ b/libheif/codecs/hevc_boxes.h
@@ -162,8 +162,14 @@ Error decode_hevc_aux_sei_messages(const std::vector<uint8_t>& data,
// Used for AVC, HEVC, and VVC.
std::vector<uint8_t> remove_start_code_emulation(const uint8_t* sps, size_t size);
+struct ImageSize;
+
+// Parses an HEVC SPS NAL unit. *width / *height return the post-conformance-
+// window cropping (display) dimensions. If non-null, *coded_size receives the
+// pre-cropping dimensions actually allocated by the decoder.
Error parse_sps_for_hvcC_configuration(const uint8_t* sps, size_t size,
HEVCDecoderConfigurationRecord* inout_config,
- int* width, int* height);
+ uint32_t* width, uint32_t* height,
+ ImageSize* coded_size = nullptr);
#endif
diff --git a/libheif/codecs/hevc_dec.cc b/libheif/codecs/hevc_dec.cc
index a8a24bb7..5d2bb324 100644
--- a/libheif/codecs/hevc_dec.cc
+++ b/libheif/codecs/hevc_dec.cc
@@ -22,6 +22,7 @@
#include "hevc_boxes.h"
#include "error.h"
#include "context.h"
+#include "plugins/nalu_utils.h"
#include <string>
@@ -50,6 +51,32 @@ int Decoder_HEVC::get_chroma_bits_per_pixel() const
}
+Result<std::optional<ImageSize>> Decoder_HEVC::get_coded_image_size_from_config() const
+{
+ const auto& nal_arrays = m_hvcC->get_configuration().m_nal_array;
+
+ for (const auto& arr : nal_arrays) {
+ if (arr.m_NAL_unit_type != HEVC_NAL_UNIT_SPS_NUT || arr.m_nal_units.empty()) {
+ continue;
+ }
+
+ const std::vector<uint8_t>& sps = arr.m_nal_units[0];
+ HEVCDecoderConfigurationRecord scratch = m_hvcC->get_configuration();
+ uint32_t cropped_w = 0, cropped_h = 0;
+ ImageSize coded{};
+ Error e = parse_sps_for_hvcC_configuration(sps.data(), sps.size(), &scratch,
+ &cropped_w, &cropped_h, &coded);
+ if (e) {
+ return e;
+ }
+
+ return std::optional<ImageSize>{coded};
+ }
+
+ return std::optional<ImageSize>{};
+}
+
+
Error Decoder_HEVC::get_coded_image_colorspace(heif_colorspace* out_colorspace, heif_chroma* out_chroma) const
{
*out_chroma = (heif_chroma) (m_hvcC->get_configuration().chroma_format);
diff --git a/libheif/codecs/hevc_dec.h b/libheif/codecs/hevc_dec.h
index ac4e67e0..7c0a9a86 100644
--- a/libheif/codecs/hevc_dec.h
+++ b/libheif/codecs/hevc_dec.h
@@ -47,6 +47,8 @@ public:
Result<std::vector<uint8_t>> read_bitstream_configuration_data() const override;
+ Result<std::optional<ImageSize>> get_coded_image_size_from_config() const override;
+
private:
const std::shared_ptr<const Box_hvcC> m_hvcC;
};
diff --git a/libheif/codecs/hevc_enc.cc b/libheif/codecs/hevc_enc.cc
index e06c282a..293b27c6 100644
--- a/libheif/codecs/hevc_enc.cc
+++ b/libheif/codecs/hevc_enc.cc
@@ -49,8 +49,8 @@ Result<Encoder::CodedImageData> Encoder_HEVC::encode(const std::shared_ptr<HeifP
err.message);
}
- int encoded_width = 0;
- int encoded_height = 0;
+ uint32_t encoded_width = 0;
+ uint32_t encoded_height = 0;
for (;;) {
uint8_t* data;
@@ -104,8 +104,8 @@ Result<Encoder::CodedImageData> Encoder_HEVC::encode(const std::shared_ptr<HeifP
&check_encoded_width,
&check_encoded_height);
- assert((int)check_encoded_width == encoded_width);
- assert((int)check_encoded_height == encoded_height);
+ assert(check_encoded_width == encoded_width);
+ assert(check_encoded_height == encoded_height);
}
codedImage.codingConstraints.intra_pred_used = true;
diff --git a/libheif/codecs/hevc_enc.h b/libheif/codecs/hevc_enc.h
index 20c7cfaa..dc57fcc9 100644
--- a/libheif/codecs/hevc_enc.h
+++ b/libheif/codecs/hevc_enc.h
@@ -66,8 +66,8 @@ private:
std::shared_ptr<class Box_hvcC> m_hvcC;
bool m_hvcC_sent = false;
- int m_encoded_image_width = 0;
- int m_encoded_image_height = 0;
+ uint32_t m_encoded_image_width = 0;
+ uint32_t m_encoded_image_height = 0;
std::optional<CodedImageData> m_current_output_data;
diff --git a/libheif/codecs/vvc_boxes.cc b/libheif/codecs/vvc_boxes.cc
index 44b6b5ec..104c1a78 100644
--- a/libheif/codecs/vvc_boxes.cc
+++ b/libheif/codecs/vvc_boxes.cc
@@ -20,6 +20,7 @@
#include "vvc_boxes.h"
#include "hevc_boxes.h"
+#include "codecs/decoder.h"
#include "file.h"
#include <cstring>
#include <string>
@@ -213,6 +214,18 @@ void Box_vvcC::append_nal_data(const std::vector<uint8_t>& nal)
}
+const std::vector<uint8_t>* Box_vvcC::get_first_nal_of_type(uint8_t nal_type) const
+{
+ for (const auto& arr : m_nal_array) {
+ if (arr.m_NAL_unit_type == nal_type && !arr.m_nal_units.empty()) {
+ return &arr.m_nal_units[0];
+ }
+ }
+
+ return nullptr;
+}
+
+
void Box_vvcC::append_nal_data(const uint8_t* data, size_t size)
{
std::vector<uint8_t> nal;
@@ -402,7 +415,8 @@ std::string Box_vvcC::dump(Indent& indent) const
Error parse_sps_for_vvcC_configuration(const uint8_t* sps, size_t size,
Box_vvcC::configuration* config,
- int* width, int* height)
+ uint32_t* width, uint32_t* height,
+ ImageSize* coded_size)
{
// remove start-code emulation bytes from SPS header stream
@@ -486,8 +500,8 @@ Error parse_sps_for_vvcC_configuration(const uint8_t* sps, size_t size,
reader.skip_bits(1); // sps_res_change_in_clvs_allowed_flag
}
- int sps_pic_width_max_in_luma_samples;
- int sps_pic_height_max_in_luma_samples;
+ uint32_t sps_pic_width_max_in_luma_samples;
+ uint32_t sps_pic_height_max_in_luma_samples;
bool success;
success = reader.get_uvlc(&sps_pic_width_max_in_luma_samples);
@@ -498,6 +512,11 @@ Error parse_sps_for_vvcC_configuration(const uint8_t* sps, size_t size,
*width = sps_pic_width_max_in_luma_samples;
*height = sps_pic_height_max_in_luma_samples;
+ if (coded_size) {
+ coded_size->width = *width;
+ coded_size->height = *height;
+ }
+
if (sps_pic_width_max_in_luma_samples > 0xFFFF ||
sps_pic_height_max_in_luma_samples > 0xFFFF) {
return {heif_error_Encoding_error,
@@ -510,11 +529,22 @@ Error parse_sps_for_vvcC_configuration(const uint8_t* sps, size_t size,
int sps_conformance_window_flag = reader.get_bits(1);
if (sps_conformance_window_flag) {
- int left,right,top,bottom;
+ uint32_t left, right, top, bottom;
reader.get_uvlc(&left);
reader.get_uvlc(&right);
reader.get_uvlc(&top);
reader.get_uvlc(&bottom);
+
+ // SubWidthC / SubHeightC per ITU-T H.266 Table 5-1, indexed by chroma_format_idc.
+ uint32_t subWidthC = 1, subHeightC = 1;
+ switch (config->chroma_format_idc) {
+ case 1: subWidthC = 2; subHeightC = 2; break; // 4:2:0
+ case 2: subWidthC = 2; subHeightC = 1; break; // 4:2:2
+ default: break; // mono / 4:4:4
+ }
+
+ *width -= subWidthC * (left + right);
+ *height -= subHeightC * (top + bottom);
}
bool sps_subpic_info_present_flag = reader.get_bits(1);
@@ -522,7 +552,7 @@ Error parse_sps_for_vvcC_configuration(const uint8_t* sps, size_t size,
assert(false); // TODO
}
- int bitDepth_minus8;
+ uint32_t bitDepth_minus8;
success = reader.get_uvlc(&bitDepth_minus8);
(void)success;
diff --git a/libheif/codecs/vvc_boxes.h b/libheif/codecs/vvc_boxes.h
index 6a4a9b1a..5dd7f689 100644
--- a/libheif/codecs/vvc_boxes.h
+++ b/libheif/codecs/vvc_boxes.h
@@ -85,6 +85,10 @@ public:
void append_nal_data(const std::vector<uint8_t>& nal);
void append_nal_data(const uint8_t* data, size_t size);
+ // Returns the bytes of the first NAL unit of the requested type stored in
+ // the configuration record, or nullptr if none is present.
+ const std::vector<uint8_t>* get_first_nal_of_type(uint8_t nal_type) const;
+
Error write(StreamWriter& writer) const override;
protected:
@@ -114,8 +118,14 @@ public:
};
+struct ImageSize;
+
+// Parses a VVC SPS NAL unit. *width / *height return the post-conformance-
+// window cropping (display) dimensions. If non-null, *coded_size receives the
+// pre-cropping dimensions actually allocated by the decoder.
Error parse_sps_for_vvcC_configuration(const uint8_t* sps, size_t size,
Box_vvcC::configuration* inout_config,
- int* width, int* height);
+ uint32_t* width, uint32_t* height,
+ ImageSize* coded_size = nullptr);
#endif // LIBHEIF_VVC_BOXES_H
diff --git a/libheif/codecs/vvc_dec.cc b/libheif/codecs/vvc_dec.cc
index 3c929713..d363cf6a 100644
--- a/libheif/codecs/vvc_dec.cc
+++ b/libheif/codecs/vvc_dec.cc
@@ -22,6 +22,7 @@
#include "vvc_boxes.h"
#include "error.h"
#include "context.h"
+#include "plugins/nalu_utils.h"
#include <string>
@@ -56,6 +57,26 @@ int Decoder_VVC::get_chroma_bits_per_pixel() const
}
+Result<std::optional<ImageSize>> Decoder_VVC::get_coded_image_size_from_config() const
+{
+ const std::vector<uint8_t>* sps = m_vvcC->get_first_nal_of_type(VVC_NAL_UNIT_SPS_NUT);
+ if (!sps || sps->empty()) {
+ return std::optional<ImageSize>{};
+ }
+
+ Box_vvcC::configuration scratch = m_vvcC->get_configuration();
+ uint32_t cropped_w = 0, cropped_h = 0;
+ ImageSize coded{};
+ Error e = parse_sps_for_vvcC_configuration(sps->data(), sps->size(), &scratch,
+ &cropped_w, &cropped_h, &coded);
+ if (e) {
+ return e;
+ }
+
+ return std::optional<ImageSize>{coded};
+}
+
+
Error Decoder_VVC::get_coded_image_colorspace(heif_colorspace* out_colorspace, heif_chroma* out_chroma) const
{
*out_chroma = (heif_chroma) (m_vvcC->get_configuration().chroma_format_idc);
diff --git a/libheif/codecs/vvc_dec.h b/libheif/codecs/vvc_dec.h
index 0d55b1c3..2ad19344 100644
--- a/libheif/codecs/vvc_dec.h
+++ b/libheif/codecs/vvc_dec.h
@@ -47,6 +47,8 @@ public:
Result<std::vector<uint8_t>> read_bitstream_configuration_data() const override;
+ Result<std::optional<ImageSize>> get_coded_image_size_from_config() const override;
+
private:
const std::shared_ptr<const Box_vvcC> m_vvcC;
};
diff --git a/libheif/codecs/vvc_enc.cc b/libheif/codecs/vvc_enc.cc
index 96930f22..32495386 100644
--- a/libheif/codecs/vvc_enc.cc
+++ b/libheif/codecs/vvc_enc.cc
@@ -50,8 +50,8 @@ Result<Encoder::CodedImageData> Encoder_VVC::encode(const std::shared_ptr<HeifPi
err.message);
}
- int encoded_width = 0;
- int encoded_height = 0;
+ uint32_t encoded_width = 0;
+ uint32_t encoded_height = 0;
for (;;) {
uint8_t* data;
diff --git a/libheif/codecs/vvc_enc.h b/libheif/codecs/vvc_enc.h
index 92a2950c..b629816d 100644
--- a/libheif/codecs/vvc_enc.h
+++ b/libheif/codecs/vvc_enc.h
@@ -67,8 +67,8 @@ private:
std::shared_ptr<class Box_vvcC> m_vvcC;
bool m_vvcC_sent = false;
- int m_encoded_image_width = 0;
- int m_encoded_image_height = 0;
+ uint32_t m_encoded_image_width = 0;
+ uint32_t m_encoded_image_height = 0;
uint16_t m_avg_frame_rate = 0;
std::optional<CodedImageData> m_current_output_data;
diff --git a/libheif/plugins/decoder_webcodecs.cc b/libheif/plugins/decoder_webcodecs.cc
index 21f2ae93..44d3489d 100644
--- a/libheif/plugins/decoder_webcodecs.cc
+++ b/libheif/plugins/decoder_webcodecs.cc
@@ -205,7 +205,7 @@ static std::vector<uint8_t> remove_start_code_emulation2(const uint8_t* sps, siz
Error parse_sps_for_hvcC_configuration2(const uint8_t* sps, size_t size,
HEVCDecoderConfigurationRecord* config,
- int* width, int* height)
+ uint32_t* width, uint32_t* height)
{
// remove start-code emulation bytes from SPS header stream
@@ -269,7 +269,7 @@ Error parse_sps_for_hvcC_configuration2(const uint8_t* sps, size_t size,
// --- SPS continued ---
- int dummy, value;
+ uint32_t dummy, value;
reader.get_uvlc(&dummy); // skip seq_parameter_seq_id
reader.get_uvlc(&value);
@@ -284,15 +284,15 @@ Error parse_sps_for_hvcC_configuration2(const uint8_t* sps, size_t size,
bool conformance_window = reader.get_bits(1);
if (conformance_window) {
- int left, right, top, bottom;
+ uint32_t left, right, top, bottom;
reader.get_uvlc(&left);
reader.get_uvlc(&right);
reader.get_uvlc(&top);
reader.get_uvlc(&bottom);
- //printf("conformance borders: %d %d %d %d\n",left,right,top,bottom);
+ //printf("conformance borders: %u %u %u %u\n",left,right,top,bottom);
- int subH = 1, subV = 1;
+ uint32_t subH = 1, subV = 1;
if (config->chroma_format == 1) {
subV = 2;
subH = 2;
@@ -671,7 +671,7 @@ static struct heif_error webcodecs_decode_image(void* decoder_raw,
}
HEVCDecoderConfigurationRecord config;
- int w, h;
+ uint32_t w, h;
Error err = parse_sps_for_hvcC_configuration2(sps_nal_unit.data.data(), sps_nal_unit.data.size(), &config, &w, &h);
if (err != Error::Ok) {
return {heif_error_Decoder_plugin_error,