diff --git a/examples/heif_convert.cc b/examples/heif_convert.cc index 0e3cb4d2de..2a24a3cc64 100644 --- a/examples/heif_convert.cc +++ b/examples/heif_convert.cc @@ -165,6 +165,9 @@ void list_all_decoders() std::cout << "HEIC decoders:\n"; list_decoders(heif_compression_HEVC); + std::cout << "VVIC decoders:\n"; + list_decoders(heif_compression_VVC); + std::cout << "AVIF decoders:\n"; list_decoders(heif_compression_AV1); diff --git a/examples/heif_enc.cc b/examples/heif_enc.cc index 754f566379..98ad840ed8 100644 --- a/examples/heif_enc.cc +++ b/examples/heif_enc.cc @@ -91,9 +91,10 @@ const int OPTION_NCLX_TRANSFER_CHARACTERISTIC = 1002; const int OPTION_NCLX_FULL_RANGE_FLAG = 1003; const int OPTION_PLUGIN_DIRECTORY = 1004; const int OPTION_PITM_DESCRIPTION = 1005; -const int OPTION_USE_JPEG_COMPRESSION = 1006; -const int OPTION_USE_JPEG2000_COMPRESSION = 1007; -const int OPTION_VERBOSE = 1008; +const int OPTION_USE_VVC_COMPRESSION = 1006; +const int OPTION_USE_JPEG_COMPRESSION = 1007; +const int OPTION_USE_JPEG2000_COMPRESSION = 1008; +const int OPTION_VERBOSE = 1009; static struct option long_options[] = { @@ -112,6 +113,7 @@ static struct option long_options[] = { {(char* const) "bit-depth", required_argument, 0, 'b'}, {(char* const) "even-size", no_argument, 0, 'E'}, {(char* const) "avif", no_argument, 0, 'A'}, + {(char* const) "vvc", no_argument, 0, OPTION_USE_VVC_COMPRESSION}, {(char* const) "jpeg", no_argument, 0, OPTION_USE_JPEG_COMPRESSION}, {(char* const) "jpeg2000", no_argument, 0, OPTION_USE_JPEG2000_COMPRESSION}, #if WITH_UNCOMPRESSED_CODEC @@ -159,6 +161,7 @@ void show_help(const char* argv0) << " -b, --bit-depth # bit-depth of generated HEIF/AVIF file when using 16-bit PNG input (default: 10 bit)\n" << " -p set encoder parameter (NAME=VALUE)\n" << " -A, --avif encode as AVIF (not needed if output filename with .avif suffix is provided)\n" + << " --vvc encode as VVC (even more experimental)\n" << " --jpeg encode as JPEG\n" << " --jpeg2000 encode as JPEG 2000 (experimental)\n" #if WITH_UNCOMPRESSED_CODEC @@ -355,6 +358,9 @@ static const char* get_compression_format_name(heif_compression_format format) case heif_compression_AV1: return "AV1"; break; + case heif_compression_VVC: + return "VVC"; + break; case heif_compression_HEVC: return "HEVC"; break; @@ -375,7 +381,8 @@ static const char* get_compression_format_name(heif_compression_format format) static void show_list_of_all_encoders() { - for (auto compression_format : {heif_compression_HEVC, heif_compression_AV1, heif_compression_JPEG, heif_compression_JPEG2000 + for (auto compression_format : {heif_compression_VVC, heif_compression_HEVC, heif_compression_AV1, heif_compression_JPEG, + heif_compression_JPEG2000 #if WITH_UNCOMPRESSED_CODEC , heif_compression_uncompressed #endif @@ -385,6 +392,9 @@ static void show_list_of_all_encoders() case heif_compression_AV1: std::cout << "AVIF"; break; + case heif_compression_VVC: + std::cout << "VVIC"; + break; case heif_compression_HEVC: std::cout << "HEIC"; break; @@ -434,6 +444,9 @@ heif_compression_format guess_compression_format_from_filename(const std::string if (ends_with(filename_lowercase, ".avif")) { return heif_compression_AV1; } + else if (ends_with(filename_lowercase, ".vvic")) { + return heif_compression_VVC; + } else if (ends_with(filename_lowercase, ".heic")) { return heif_compression_HEVC; } @@ -450,6 +463,7 @@ std::string suffix_for_compression_format(heif_compression_format format) { switch (format) { case heif_compression_AV1: return "avif"; + case heif_compression_VVC: return "vvic"; case heif_compression_HEVC: return "heic"; case heif_compression_JPEG2000: return "hej2"; default: return "data"; @@ -479,6 +493,7 @@ int main(int argc, char** argv) int thumbnail_bbox_size = 0; int output_bit_depth = 10; bool force_enc_av1f = false; + bool force_enc_vvc = false; bool force_enc_uncompressed = false; bool force_enc_jpeg = false; bool force_enc_jpeg2000 = false; @@ -557,6 +572,9 @@ int main(int argc, char** argv) case OPTION_PITM_DESCRIPTION: property_pitm_description = optarg; break; + case OPTION_USE_VVC_COMPRESSION: + force_enc_vvc = true; + break; case OPTION_USE_JPEG_COMPRESSION: force_enc_jpeg = true; break; @@ -604,7 +622,8 @@ int main(int argc, char** argv) return 5; } - if ((force_enc_av1f ? 1 : 0) + (force_enc_uncompressed ? 1 : 0) + (force_enc_jpeg ? 1 : 0) + (force_enc_jpeg2000 ? 1 : 0) > 1) { + if ((force_enc_av1f ? 1 : 0) + (force_enc_vvc ? 1 : 0) + (force_enc_uncompressed ? 1 : 0) + (force_enc_jpeg ? 1 : 0) + + (force_enc_jpeg2000 ? 1 : 0) > 1) { std::cerr << "Choose at most one output compression format.\n"; } @@ -649,6 +668,9 @@ int main(int argc, char** argv) if (force_enc_av1f) { compressionFormat = heif_compression_AV1; } + else if (force_enc_vvc) { + compressionFormat = heif_compression_VVC; + } else if (force_enc_uncompressed) { compressionFormat = heif_compression_uncompressed; } diff --git a/libheif/context.cc b/libheif/context.cc index ec9ae708bc..72238f1432 100644 --- a/libheif/context.cc +++ b/libheif/context.cc @@ -41,6 +41,7 @@ #include "api_structs.h" #include "security_limits.h" #include "hevc.h" +#include "vvc.h" #include "avif.h" #include "jpeg.h" #include "plugin_registry.h" @@ -830,6 +831,17 @@ Error HeifContext::interpret_heif_file() "No hvcC property in hvc1 type image"); } } + if (infe->get_item_type() == "vvc1") { + + auto ipma = m_heif_file->get_ipma_box(); + auto ipco = m_heif_file->get_ipco_box(); + + if (!ipco->get_property_for_item_ID(image->get_id(), ipma, fourcc("vvcC"))) { + return Error(heif_error_Invalid_input, + heif_suberror_No_vvcC_box, + "No vvcC property in vvc1 type image"); + } + } } @@ -1201,6 +1213,9 @@ Error HeifContext::Image::get_preferred_decoding_colorspace(heif_colorspace* out if (auto hvcC = m_heif_context->m_heif_file->get_property(id)) { *out_chroma = (heif_chroma)(hvcC->get_configuration().chroma_format); } + else if (auto vvcC = m_heif_context->m_heif_file->get_property(id)) { + *out_chroma = (heif_chroma)(vvcC->get_configuration().chroma_format_idc); + } else if (auto av1C = m_heif_context->m_heif_file->get_property(id)) { *out_chroma = (heif_chroma)(av1C->get_configuration().get_heif_chroma()); } @@ -1299,6 +1314,7 @@ Error HeifContext::decode_image_planar(heif_item_id ID, // --- decode image, depending on its type if (image_type == "hvc1" || + image_type == "vvc1" || image_type == "av01" || image_type == "j2k1" || image_type == "jpeg" || @@ -1308,6 +1324,9 @@ Error HeifContext::decode_image_planar(heif_item_id ID, if (image_type == "hvc1") { compression = heif_compression_HEVC; } + else if (image_type == "vvc1") { + compression = heif_compression_VVC; + } else if (image_type == "av01") { compression = heif_compression_AV1; } @@ -2260,6 +2279,15 @@ Error HeifContext::encode_image(const std::shared_ptr& pixel_ima } break; + case heif_compression_VVC: { + error = encode_image_as_vvc(pixel_image, + encoder, + options, + heif_image_input_class_normal, + out_image); + } + break; + case heif_compression_AV1: { error = encode_image_as_av1(pixel_image, encoder, @@ -2268,6 +2296,7 @@ Error HeifContext::encode_image(const std::shared_ptr& pixel_ima out_image); } break; + case heif_compression_JPEG2000: { error = encode_image_as_jpeg2000(pixel_image, encoder, @@ -2672,6 +2701,226 @@ Error HeifContext::encode_image_as_hevc(const std::shared_ptr& i } +Error HeifContext::encode_image_as_vvc(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class, + std::shared_ptr& out_image) +{ + heif_item_id image_id = m_heif_file->add_new_image("vvc1"); + out_image = std::make_shared(this, image_id); + + + // --- check whether we have to convert the image color space + + heif_colorspace colorspace = image->get_colorspace(); + heif_chroma chroma = image->get_chroma_format(); + + auto target_nclx_profile = compute_target_nclx_profile(image, options.output_nclx_profile); + + if (encoder->plugin->plugin_api_version >= 2) { + encoder->plugin->query_input_colorspace2(encoder->encoder, &colorspace, &chroma); + } + else { + encoder->plugin->query_input_colorspace(&colorspace, &chroma); + } + + std::shared_ptr src_image; + if (colorspace != image->get_colorspace() || + chroma != image->get_chroma_format() || + !nclx_profile_matches_spec(colorspace, image->get_color_profile_nclx(), options.output_nclx_profile)) { + // @TODO: use color profile when converting + int output_bpp = 0; // same as input + src_image = convert_colorspace(image, colorspace, chroma, target_nclx_profile, + output_bpp, options.color_conversion_options); + if (!src_image) { + return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_color_conversion); + } + } + else { + src_image = image; + } + + + int input_width = src_image->get_width(heif_channel_Y); + int input_height = src_image->get_height(heif_channel_Y); + + out_image->set_size(input_width, input_height); + + + m_heif_file->add_vvcC_property(image_id); + + + heif_image c_api_image; + c_api_image.image = src_image; + + struct heif_error err = encoder->plugin->encode_image(encoder->encoder, &c_api_image, input_class); + if (err.code) { + return Error(err.code, + err.subcode, + err.message); + } + + int encoded_width = 0; + int encoded_height = 0; + + for (;;) { + uint8_t* data; + int size; + + encoder->plugin->get_compressed_data(encoder->encoder, &data, &size, NULL); + + if (data == NULL) { + break; + } + + + const uint8_t NAL_SPS = 33; + + if ((data[0] >> 1) == NAL_SPS) { + Box_vvcC::configuration config; + + //parse_sps_for_vvcC_configuration(data, size, &config, &encoded_width, &encoded_height); + + m_heif_file->set_vvcC_configuration(image_id, config); + } + + switch (data[0] >> 1) { + case 0x20: + case 0x21: + case 0x22: + m_heif_file->append_vvcC_nal_data(image_id, data, size); + break; + + default: + m_heif_file->append_iloc_data_with_4byte_size(image_id, data, size); + } + } + + if (!encoded_width || !encoded_height) { + return Error(heif_error_Encoder_plugin_error, + heif_suberror_Invalid_image_size); + } + + if (encoder->plugin->plugin_api_version >= 3 && + encoder->plugin->query_encoded_size != nullptr) { + uint32_t check_encoded_width = input_width, check_encoded_height = input_height; + + encoder->plugin->query_encoded_size(encoder->encoder, + input_width, input_height, + &check_encoded_width, + &check_encoded_height); + + assert((int)check_encoded_width == encoded_width); + assert((int)check_encoded_height == encoded_height); + } + + + // Note: 'ispe' must be before the transformation properties + m_heif_file->add_ispe_property(image_id, encoded_width, encoded_height); + + // if image size was rounded up to even size, add a 'clap' box to crop the + // padding border away + + //uint32_t rotated_width = get_rotated_width(options.image_orientation, out_image->get_width(), out_image->get_height()); + //uint32_t rotated_height = get_rotated_height(options.image_orientation, out_image->get_width(), out_image->get_height()); + + if (input_width != encoded_width || + input_height != encoded_height) { + m_heif_file->add_clap_property(image_id, + input_width, + input_height, + encoded_width, + encoded_height); + + // MIAF 7.3.6.7 + // This is according to MIAF without Amd2. With Amd2, the restriction has been liften and the image is MIAF compatible. + // We might remove this code at a later point in time when MIAF Amd2 is in wide use. + + if (!is_integer_multiple_of_chroma_size(input_width, + input_height, + src_image->get_chroma_format())) { + out_image->mark_not_miaf_compatible(); + } + } + + m_heif_file->add_orientation_properties(image_id, options.image_orientation); + + // --- choose which color profile to put into 'colr' box + + if (input_class == heif_image_input_class_normal || input_class == heif_image_input_class_thumbnail) { + auto icc_profile = src_image->get_color_profile_icc(); + if (icc_profile) { + m_heif_file->set_color_profile(image_id, icc_profile); + } + + // save nclx profile + + bool save_nclx_profile = (options.output_nclx_profile != nullptr); + + // if there is an ICC profile, only save NCLX when we chose to save both profiles + if (icc_profile && !(options.version >= 3 && + options.save_two_colr_boxes_when_ICC_and_nclx_available)) { + save_nclx_profile = false; + } + + // we might have turned off nclx completely because macOS/iOS cannot read it + if (options.version >= 4 && options.macOS_compatibility_workaround_no_nclx_profile) { + save_nclx_profile = false; + } + + if (save_nclx_profile) { + m_heif_file->set_color_profile(image_id, target_nclx_profile); + } + } + + + write_image_metadata(src_image, image_id); + + m_top_level_images.push_back(out_image); + m_all_images[image_id] = out_image; + + + + // --- If there is an alpha channel, add it as an additional image. + // Save alpha after the color image because we need to know the final reference to the color image. + + if (options.save_alpha_channel && src_image->has_channel(heif_channel_Alpha)) { + + // --- generate alpha image + // TODO: can we directly code a monochrome image instead of the dummy color channels? + + std::shared_ptr alpha_image; + alpha_image = create_alpha_image_from_image_alpha_channel(src_image); + + + // --- encode the alpha image + + std::shared_ptr heif_alpha_image; + + Error error = encode_image_as_vvc(alpha_image, encoder, options, + heif_image_input_class_alpha, + heif_alpha_image); + if (error) { + return error; + } + + m_heif_file->add_iref_reference(heif_alpha_image->get_id(), fourcc("auxl"), {image_id}); + + if (src_image->is_premultiplied_alpha()) { + m_heif_file->add_iref_reference(image_id, fourcc("prem"), {heif_alpha_image->get_id()}); + } + + // TODO: MIAF says that the *:hevc:* urn is deprecated and we should use "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha" + // Is this compatible to other decoders? + m_heif_file->set_auxC_property(heif_alpha_image->get_id(), "urn:mpeg:hevc:2015:auxid:1"); + } + + + return Error::Ok; +} + + Error HeifContext::encode_image_as_av1(const std::shared_ptr& image, struct heif_encoder* encoder, const struct heif_encoding_options& options, diff --git a/libheif/context.h b/libheif/context.h index 6e88d85022..53cb093afd 100644 --- a/libheif/context.h +++ b/libheif/context.h @@ -396,6 +396,12 @@ class HeifContext : public ErrorBuffer enum heif_image_input_class input_class, std::shared_ptr& out_image); + Error encode_image_as_vvc(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class, + std::shared_ptr& out_image); + Error encode_image_as_av1(const std::shared_ptr& image, struct heif_encoder* encoder, const struct heif_encoding_options& options, diff --git a/libheif/error.cc b/libheif/error.cc index 0d78d618ab..7c0f3c5a29 100644 --- a/libheif/error.cc +++ b/libheif/error.cc @@ -102,6 +102,8 @@ const char* Error::get_error_string(heif_suberror_code err) return "No 'hdlr' box"; case heif_suberror_No_hvcC_box: return "No 'hvcC' box"; + case heif_suberror_No_vvcC_box: + return "No 'vvcC' box"; case heif_suberror_No_av1C_box: return "No 'av1C' box"; case heif_suberror_No_pitm_box: diff --git a/libheif/file.cc b/libheif/file.cc index cea8f9fcbc..a01c1db9f0 100644 --- a/libheif/file.cc +++ b/libheif/file.cc @@ -506,6 +506,15 @@ heif_chroma HeifFile::get_image_chroma_from_configuration(heif_item_id imageID) } + // VVC + + box = m_ipco_box->get_property_for_item_ID(imageID, m_ipma_box, fourcc("vvcC")); + std::shared_ptr vvcC_box = std::dynamic_pointer_cast(box); + if (vvcC_box) { + return (heif_chroma) (vvcC_box->get_configuration().chroma_format_idc); + } + + // AV1 box = m_ipco_box->get_property_for_item_ID(imageID, m_ipma_box, fourcc("av1C")); @@ -550,6 +559,23 @@ int HeifFile::get_luma_bits_per_pixel_from_configuration(heif_item_id imageID) c } + // VVC + + if (image_type == "vvc1") { + auto box = m_ipco_box->get_property_for_item_ID(imageID, m_ipma_box, fourcc("vvcC")); + std::shared_ptr vvcC_box = std::dynamic_pointer_cast(box); + if (vvcC_box) { + Box_vvcC::configuration config = vvcC_box->get_configuration(); + if (config.bit_depth_present_flag) { + return config.bit_depth; + } + else { + return 8; // TODO: what shall we do if the bit-depth is unknown? Use PIXI? + } + } + } + + // AV1 if (image_type == "av01") { @@ -569,21 +595,6 @@ int HeifFile::get_luma_bits_per_pixel_from_configuration(heif_item_id imageID) c } } - // VVC - - if (image_type == "vvc1") { - auto box = m_ipco_box->get_property_for_item_ID(imageID, m_ipma_box, fourcc("vvcC")); - std::shared_ptr vvcC_box = std::dynamic_pointer_cast(box); - if (vvcC_box) { - Box_vvcC::configuration config = vvcC_box->get_configuration(); - if (config.bit_depth_present_flag) { - return config.bit_depth; - } - else { - return 8; // TODO: what shall we do if the bit-depth is unknown? Use PIXI? - } - } - } // JPEG @@ -629,6 +640,22 @@ int HeifFile::get_chroma_bits_per_pixel_from_configuration(heif_item_id imageID) } } + // VVC + + if (image_type == "vvc1") { + auto box = m_ipco_box->get_property_for_item_ID(imageID, m_ipma_box, fourcc("vvcC")); + std::shared_ptr vvcC_box = std::dynamic_pointer_cast(box); + if (vvcC_box) { + Box_vvcC::configuration config = vvcC_box->get_configuration(); + if (config.bit_depth_present_flag) { + return config.bit_depth; + } + else { + return 8; // TODO: what shall we do if the bit-depth is unknown? Use PIXI? + } + } + } + // AV1 if (image_type == "av01") { @@ -777,6 +804,43 @@ Error HeifFile::get_compressed_image_data(heif_item_id ID, std::vector* error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); } + else if (item_type == "vvc1") { + // --- --- --- VVC + + // --- get properties for this image + + std::vector> properties; + Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties); + if (err) { + return err; + } + + // --- get codec configuration + + std::shared_ptr vvcC_box; + for (auto& prop : properties) { + if (prop->get_short_type() == fourcc("vvcC")) { + vvcC_box = std::dynamic_pointer_cast(prop); + if (vvcC_box) { + break; + } + } + } + + if (!vvcC_box) { + // Should always have an vvcC box, because we are checking this in + // heif_context::interpret_heif_file() + assert(false); + return Error(heif_error_Invalid_input, + heif_suberror_No_vvcC_box); + } + else if (!vvcC_box->get_headers(data)) { + return Error(heif_error_Invalid_input, + heif_suberror_No_item_data); + } + + error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); + } else if (item_type == "av01") { // --- --- --- AV1 @@ -1073,6 +1137,71 @@ void HeifFile::add_pixi_property(heif_item_id id, uint8_t c1, uint8_t c2, uint8_ } +void HeifFile::add_vvcC_property(heif_item_id id) +{ + auto vvcC = std::make_shared(); + int index = m_ipco_box->append_child_box(vvcC); + + m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{true, uint16_t(index + 1)}); +} + + +Error HeifFile::append_vvcC_nal_data(heif_item_id id, const std::vector& nal_data) +{ + auto vvcC = std::dynamic_pointer_cast(m_ipco_box->get_property_for_item_ID(id, + m_ipma_box, + fourcc("vvcC"))); + + if (vvcC) { + vvcC->append_nal_data(nal_data); + return Error::Ok; + } + else { + // Should always have an vvcC box, because we are checking this in + // heif_context::interpret_heif_file() + assert(false); + return Error(heif_error_Usage_error, + heif_suberror_No_vvcC_box); + } +} + + +Error HeifFile::set_vvcC_configuration(heif_item_id id, const Box_vvcC::configuration& config) +{ + auto vvcC = std::dynamic_pointer_cast(m_ipco_box->get_property_for_item_ID(id, + m_ipma_box, + fourcc("vvcC"))); + + if (vvcC) { + vvcC->set_configuration(config); + return Error::Ok; + } + else { + return Error(heif_error_Usage_error, + heif_suberror_No_vvcC_box); + } +} + + +Error HeifFile::append_vvcC_nal_data(heif_item_id id, const uint8_t* data, size_t size) +{ + std::vector> properties; + + auto vvcC = std::dynamic_pointer_cast(m_ipco_box->get_property_for_item_ID(id, + m_ipma_box, + fourcc("vvcC"))); + + if (vvcC) { + vvcC->append_nal_data(data, size); + return Error::Ok; + } + else { + return Error(heif_error_Usage_error, + heif_suberror_No_vvcC_box); + } +} + + void HeifFile::add_hvcC_property(heif_item_id id) { auto hvcC = std::make_shared(); diff --git a/libheif/file.h b/libheif/file.h index 3715bab98c..13d6b63d39 100644 --- a/libheif/file.h +++ b/libheif/file.h @@ -24,6 +24,7 @@ #include "avif.h" #include "box.h" #include "hevc.h" +#include "vvc.h" #include "nclx.h" #include @@ -143,6 +144,14 @@ class HeifFile std::shared_ptr add_new_infe_box(const char* item_type); + void add_vvcC_property(heif_item_id id); + + Error append_vvcC_nal_data(heif_item_id id, const std::vector& data); + + Error append_vvcC_nal_data(heif_item_id id, const uint8_t* data, size_t size); + + Error set_vvcC_configuration(heif_item_id id, const Box_vvcC::configuration& config); + void add_hvcC_property(heif_item_id id); Error append_hvcC_nal_data(heif_item_id id, const std::vector& data); diff --git a/libheif/heif.h b/libheif/heif.h index 2c65a01366..0123cb961c 100644 --- a/libheif/heif.h +++ b/libheif/heif.h @@ -161,73 +161,75 @@ enum heif_suberror_code heif_suberror_No_hvcC_box = 106, - heif_suberror_No_pitm_box = 107, + heif_suberror_No_vvcC_box = 107, - heif_suberror_No_ipco_box = 108, + heif_suberror_No_pitm_box = 108, - heif_suberror_No_ipma_box = 109, + heif_suberror_No_ipco_box = 109, - heif_suberror_No_iloc_box = 110, + heif_suberror_No_ipma_box = 110, - heif_suberror_No_iinf_box = 111, + heif_suberror_No_iloc_box = 111, - heif_suberror_No_iprp_box = 112, + heif_suberror_No_iinf_box = 112, - heif_suberror_No_iref_box = 113, + heif_suberror_No_iprp_box = 113, - heif_suberror_No_pict_handler = 114, + heif_suberror_No_iref_box = 114, + + heif_suberror_No_pict_handler = 115, // An item property referenced in the 'ipma' box is not existing in the 'ipco' container. - heif_suberror_Ipma_box_references_nonexisting_property = 115, + heif_suberror_Ipma_box_references_nonexisting_property = 116, // No properties have been assigned to an item. - heif_suberror_No_properties_assigned_to_item = 116, + heif_suberror_No_properties_assigned_to_item = 117, // Image has no (compressed) data - heif_suberror_No_item_data = 117, + heif_suberror_No_item_data = 118, // Invalid specification of image grid (tiled image) - heif_suberror_Invalid_grid_data = 118, + heif_suberror_Invalid_grid_data = 119, // Tile-images in a grid image are missing - heif_suberror_Missing_grid_images = 119, + heif_suberror_Missing_grid_images = 120, - heif_suberror_Invalid_clean_aperture = 120, + heif_suberror_Invalid_clean_aperture = 121, // Invalid specification of overlay image - heif_suberror_Invalid_overlay_data = 121, + heif_suberror_Invalid_overlay_data = 122, // Overlay image completely outside of visible canvas area - heif_suberror_Overlay_image_outside_of_canvas = 122, + heif_suberror_Overlay_image_outside_of_canvas = 123, - heif_suberror_Auxiliary_image_type_unspecified = 123, + heif_suberror_Auxiliary_image_type_unspecified = 124, - heif_suberror_No_or_invalid_primary_item = 124, + heif_suberror_No_or_invalid_primary_item = 125, - heif_suberror_No_infe_box = 125, + heif_suberror_No_infe_box = 126, - heif_suberror_Unknown_color_profile_type = 126, + heif_suberror_Unknown_color_profile_type = 127, - heif_suberror_Wrong_tile_image_chroma_format = 127, + heif_suberror_Wrong_tile_image_chroma_format = 128, - heif_suberror_Invalid_fractional_number = 128, + heif_suberror_Invalid_fractional_number = 129, - heif_suberror_Invalid_image_size = 129, + heif_suberror_Invalid_image_size = 130, - heif_suberror_Invalid_pixi_box = 130, + heif_suberror_Invalid_pixi_box = 131, - heif_suberror_No_av1C_box = 131, + heif_suberror_No_av1C_box = 132, - heif_suberror_Wrong_tile_image_pixel_depth = 132, + heif_suberror_Wrong_tile_image_pixel_depth = 133, - heif_suberror_Unknown_NCLX_color_primaries = 133, + heif_suberror_Unknown_NCLX_color_primaries = 134, - heif_suberror_Unknown_NCLX_transfer_characteristics = 134, + heif_suberror_Unknown_NCLX_transfer_characteristics = 135, - heif_suberror_Unknown_NCLX_matrix_coefficients = 135, + heif_suberror_Unknown_NCLX_matrix_coefficients = 136, // Invalid specification of region item - heif_suberror_Invalid_region_data = 136, + heif_suberror_Invalid_region_data = 137, // --- Memory_allocation_error --- diff --git a/libheif/heif_emscripten.h b/libheif/heif_emscripten.h index 395e1a7566..af199bef3d 100644 --- a/libheif/heif_emscripten.h +++ b/libheif/heif_emscripten.h @@ -314,6 +314,7 @@ EMSCRIPTEN_BINDINGS(libheif) { .value("heif_suberror_No_meta_box", heif_suberror_No_meta_box) .value("heif_suberror_No_hdlr_box", heif_suberror_No_hdlr_box) .value("heif_suberror_No_hvcC_box", heif_suberror_No_hvcC_box) + .value("heif_suberror_No_vvcC_box", heif_suberror_No_vvcC_box) .value("heif_suberror_No_pitm_box", heif_suberror_No_pitm_box) .value("heif_suberror_No_ipco_box", heif_suberror_No_ipco_box) .value("heif_suberror_No_ipma_box", heif_suberror_No_ipma_box) diff --git a/libheif/plugin_registry.cc b/libheif/plugin_registry.cc index 5786c369a2..417eb5675a 100644 --- a/libheif/plugin_registry.cc +++ b/libheif/plugin_registry.cc @@ -38,6 +38,10 @@ #include "libheif/plugins/encoder_kvazaar.h" #endif +#if HAVE_UVG266 +#include "libheif/plugins/encoder_uvg266.h" +#endif + #if HAVE_AOM_ENCODER #include "libheif/plugins/encoder_aom.h" #endif @@ -131,6 +135,10 @@ void register_default_plugins() register_encoder(get_encoder_plugin_kvazaar()); #endif +#if HAVE_UVG266 + register_encoder(get_encoder_plugin_uvg266()); +#endif + #if HAVE_AOM_ENCODER register_encoder(get_encoder_plugin_aom()); #endif diff --git a/libheif/plugins/encoder_uvg266.cc b/libheif/plugins/encoder_uvg266.cc new file mode 100644 index 0000000000..22876eae0b --- /dev/null +++ b/libheif/plugins/encoder_uvg266.cc @@ -0,0 +1,765 @@ +/* + * HEIF codec. + * Copyright (c) 2023 Dirk Farin + * + * 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 . + */ + +#include "libheif/heif.h" +#include "libheif/heif_plugin.h" +#include "encoder_uvg266.h" +#include +#include // apparently, this is a false positive of cpplint +#include +#include +#include +#include + +extern "C" { +#include +} + + +static const char* kError_unspecified_error = "Unspecified encoder error"; +static const char* kError_unsupported_bit_depth = "Bit depth not supported by uvg266"; +static const char* kError_unsupported_chroma = "Unsupported chroma type"; +//static const char* kError_unsupported_image_size = "Images smaller than 16 pixels are not supported"; + + +struct encoder_struct_uvg266 +{ + int quality = 75; + bool lossless = false; + + std::vector output_data; + size_t output_idx = 0; +}; + +static const int uvg266_PLUGIN_PRIORITY = 100; + +#define MAX_PLUGIN_NAME_LENGTH 80 + +static char plugin_name[MAX_PLUGIN_NAME_LENGTH]; + + +static void uvg266_set_default_parameters(void* encoder); + + +static const char* uvg266_plugin_name() +{ + strcpy(plugin_name, "uvg266 VVC encoder"); + return plugin_name; +} + + +#define MAX_NPARAMETERS 10 + +static struct heif_encoder_parameter uvg266_encoder_params[MAX_NPARAMETERS]; +static const struct heif_encoder_parameter* uvg266_encoder_parameter_ptrs[MAX_NPARAMETERS + 1]; + +static void uvg266_init_parameters() +{ + struct heif_encoder_parameter* p = uvg266_encoder_params; + const struct heif_encoder_parameter** d = uvg266_encoder_parameter_ptrs; + int i = 0; + + assert(i < MAX_NPARAMETERS); + p->version = 2; + p->name = heif_encoder_parameter_name_quality; + p->type = heif_encoder_parameter_type_integer; + p->integer.default_value = 50; + p->has_default = true; + p->integer.have_minimum_maximum = true; + p->integer.minimum = 0; + p->integer.maximum = 100; + p->integer.valid_values = NULL; + p->integer.num_valid_values = 0; + d[i++] = p++; + + assert(i < MAX_NPARAMETERS); + p->version = 2; + p->name = heif_encoder_parameter_name_lossless; + p->type = heif_encoder_parameter_type_boolean; + p->boolean.default_value = false; + p->has_default = true; + d[i++] = p++; + + d[i++] = nullptr; +} + + +const struct heif_encoder_parameter** uvg266_list_parameters(void* encoder) +{ + return uvg266_encoder_parameter_ptrs; +} + + +static void uvg266_init_plugin() +{ + uvg266_init_parameters(); +} + + +static void uvg266_cleanup_plugin() +{ +} + + +static struct heif_error uvg266_new_encoder(void** enc) +{ + struct encoder_struct_uvg266* encoder = new encoder_struct_uvg266(); + struct heif_error err = heif_error_ok; + + *enc = encoder; + + // set default parameters + + uvg266_set_default_parameters(encoder); + + return err; +} + +static void uvg266_free_encoder(void* encoder_raw) +{ + struct encoder_struct_uvg266* encoder = (struct encoder_struct_uvg266*) encoder_raw; + + delete encoder; +} + +static struct heif_error uvg266_set_parameter_quality(void* encoder_raw, int quality) +{ + struct encoder_struct_uvg266* encoder = (struct encoder_struct_uvg266*) encoder_raw; + + if (quality < 0 || quality > 100) { + return heif_error_invalid_parameter_value; + } + + encoder->quality = quality; + + return heif_error_ok; +} + +static struct heif_error uvg266_get_parameter_quality(void* encoder_raw, int* quality) +{ + struct encoder_struct_uvg266* encoder = (struct encoder_struct_uvg266*) encoder_raw; + + *quality = encoder->quality; + + return heif_error_ok; +} + +static struct heif_error uvg266_set_parameter_lossless(void* encoder_raw, int enable) +{ + struct encoder_struct_uvg266* encoder = (struct encoder_struct_uvg266*) encoder_raw; + + encoder->lossless = enable ? 1 : 0; + + return heif_error_ok; +} + +static struct heif_error uvg266_get_parameter_lossless(void* encoder_raw, int* enable) +{ + struct encoder_struct_uvg266* encoder = (struct encoder_struct_uvg266*) encoder_raw; + + *enable = encoder->lossless; + + return heif_error_ok; +} + +static struct heif_error uvg266_set_parameter_logging_level(void* encoder_raw, int logging) +{ +// struct encoder_struct_uvg266* encoder = (struct encoder_struct_uvg266*) encoder_raw; + +// return heif_error_invalid_parameter_value; + + return heif_error_ok; +} + +static struct heif_error uvg266_get_parameter_logging_level(void* encoder_raw, int* loglevel) +{ +// struct encoder_struct_uvg266* encoder = (struct encoder_struct_uvg266*) encoder_raw; + + *loglevel = 0; + + return heif_error_ok; +} + + +static struct heif_error uvg266_set_parameter_integer(void* encoder_raw, const char* name, int value) +{ + struct encoder_struct_uvg266* encoder = (struct encoder_struct_uvg266*) encoder_raw; + + if (strcmp(name, heif_encoder_parameter_name_quality) == 0) { + return uvg266_set_parameter_quality(encoder, value); + } + else if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) { + return uvg266_set_parameter_lossless(encoder, value); + } + + return heif_error_unsupported_parameter; +} + +static struct heif_error uvg266_get_parameter_integer(void* encoder_raw, const char* name, int* value) +{ + struct encoder_struct_uvg266* encoder = (struct encoder_struct_uvg266*) encoder_raw; + + if (strcmp(name, heif_encoder_parameter_name_quality) == 0) { + return uvg266_get_parameter_quality(encoder, value); + } + else if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) { + return uvg266_get_parameter_lossless(encoder, value); + } + + return heif_error_unsupported_parameter; +} + + +static struct heif_error uvg266_set_parameter_boolean(void* encoder, const char* name, int value) +{ + if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) { + return uvg266_set_parameter_lossless(encoder, value); + } + + return heif_error_unsupported_parameter; +} + +// Unused, will use "uvg266_get_parameter_integer" instead. +/* +static struct heif_error uvg266_get_parameter_boolean(void* encoder, const char* name, int* value) +{ + if (strcmp(name, heif_encoder_parameter_name_lossless)==0) { + return uvg266_get_parameter_lossless(encoder,value); + } + + return heif_error_unsupported_parameter; +} +*/ + + +static struct heif_error uvg266_set_parameter_string(void* encoder_raw, const char* name, const char* value) +{ + return heif_error_unsupported_parameter; +} + +static struct heif_error uvg266_get_parameter_string(void* encoder_raw, const char* name, + char* value, int value_size) +{ + return heif_error_unsupported_parameter; +} + + +static void uvg266_set_default_parameters(void* encoder) +{ + for (const struct heif_encoder_parameter** p = uvg266_encoder_parameter_ptrs; *p; p++) { + const struct heif_encoder_parameter* param = *p; + + if (param->has_default) { + switch (param->type) { + case heif_encoder_parameter_type_integer: + uvg266_set_parameter_integer(encoder, param->name, param->integer.default_value); + break; + case heif_encoder_parameter_type_boolean: + uvg266_set_parameter_boolean(encoder, param->name, param->boolean.default_value); + break; + case heif_encoder_parameter_type_string: + uvg266_set_parameter_string(encoder, param->name, param->string.default_value); + break; + } + } + } +} + + +static void uvg266_query_input_colorspace(heif_colorspace* colorspace, heif_chroma* chroma) +{ + if (*colorspace == heif_colorspace_monochrome) { + *colorspace = heif_colorspace_monochrome; + *chroma = heif_chroma_monochrome; + } + else { + *colorspace = heif_colorspace_YCbCr; + *chroma = heif_chroma_420; + } +} + + +static void uvg266_query_input_colorspace2(void* encoder_raw, heif_colorspace* colorspace, heif_chroma* chroma) +{ + if (*colorspace == heif_colorspace_monochrome) { + *colorspace = heif_colorspace_monochrome; + *chroma = heif_chroma_monochrome; + } + else { + *colorspace = heif_colorspace_YCbCr; + if (*chroma != heif_chroma_420 && + *chroma != heif_chroma_422 && + *chroma != heif_chroma_444) { + *chroma = heif_chroma_420; + } + } +} + +void uvg266_query_encoded_size(void* encoder_raw, uint32_t input_width, uint32_t input_height, + uint32_t* encoded_width, uint32_t* encoded_height) +{ + *encoded_width = (input_width + 7) & ~0x7; + *encoded_height = (input_height + 7) & ~0x7; +} + + +#if 0 +static int rounded_size(int s) +{ + s = (s + 1) & ~1; + + if (s < 64) { + s = 64; + } + + return s; +} +#endif + +static void append_chunk_data(uvg_data_chunk* data, std::vector& out) +{ + if (!data || data->len == 0) { + return; + } + + size_t old_size = out.size(); + out.resize(old_size + data->len); + memcpy(out.data() + old_size, data->data, data->len); + + if (data->next) { + append_chunk_data(data->next, out); + } +} + + +static void copy_plane(uvg_pixel* out_p, uint32_t out_stride, const uint8_t* in_p, uint32_t in_stride, int w, int h, int padded_width, int padded_height) +{ + for (int y = 0; y < padded_height; y++) { + int sy = std::min(y, h - 1); // source y + memcpy(out_p + y * out_stride, in_p + sy * in_stride, w); + + if (padded_width > w) { + memset(out_p + y * out_stride + w, *(in_p + sy * in_stride + w - 1), padded_width - w); + } + } +} + + +static struct heif_error uvg266_encode_image(void* encoder_raw, const struct heif_image* image, + heif_image_input_class input_class) +{ + struct encoder_struct_uvg266* encoder = (struct encoder_struct_uvg266*) encoder_raw; + + int bit_depth = heif_image_get_bits_per_pixel_range(image, heif_channel_Y); + bool isGreyscale = (heif_image_get_colorspace(image) == heif_colorspace_monochrome); + heif_chroma chroma = heif_image_get_chroma_format(image); + + const uvg_api* api = uvg_api_get(bit_depth); + if (api == nullptr) { + struct heif_error err = { + heif_error_Encoder_plugin_error, + heif_suberror_Unsupported_bit_depth, + kError_unsupported_bit_depth + }; + return err; + } + + uvg_config* config = api->config_alloc(); + api->config_init(config); // param, encoder->preset.c_str(), encoder->tune.c_str()); + +#if HAVE_UVG266_ENABLE_LOGGING + config->enable_logging_output = 0; +#endif + +#if !ENABLE_MULTITHREADING_SUPPORT + // 0: Process everything with main thread + // -1 (default): Select automatically. + config->threads = 0; +#endif + +#if 1 +#if 0 + while (ctuSize > 16 && + (heif_image_get_width(image, heif_channel_Y) < ctuSize || + heif_image_get_height(image, heif_channel_Y) < ctuSize)) { + ctuSize /= 2; + } + + if (ctuSize < 16) { + api->config_destroy(config); + struct heif_error err = { + heif_error_Encoder_plugin_error, + heif_suberror_Invalid_parameter_value, + kError_unsupported_image_size + }; + return err; + } +#endif +#else + // TODO: There seems to be a bug in uvg266 where increasing the CTU size between + // multiple encoding jobs causes a segmentation fault. E.g. encoding multiple + // times with a CTU of 16 works, the next encoding with a CTU of 32 crashes. + // Use hardcoded value of 64 and reject images that are too small. + + if (heif_image_get_width(image, heif_channel_Y) < ctuSize || + heif_image_get_height(image, heif_channel_Y) < ctuSize) { + api->param_free(param); + struct heif_error err = { + heif_error_Encoder_plugin_error, + heif_suberror_Invalid_parameter_value, + kError_unsupported_image_size + }; + return err; + } +#endif + +#if 0 + // ctuSize should be a power of 2 in [16;64] + switch (ctuSize) { + case 64: + ctu = "64"; + break; + case 32: + ctu = "32"; + break; + case 16: + ctu = "16"; + break; + default: + struct heif_error err = { + heif_error_Encoder_plugin_error, + heif_suberror_Invalid_parameter_value, + kError_unsupported_image_size + }; + return err; + } + (void) ctu; +#endif + + int input_width = heif_image_get_width(image, heif_channel_Y); + int input_height = heif_image_get_height(image, heif_channel_Y); + int input_chroma_width = 0; + int input_chroma_height = 0; + + uint32_t encoded_width, encoded_height; + uvg266_query_encoded_size(encoder_raw, input_width, input_height, &encoded_width, &encoded_height); + + uvg_chroma_format kvzChroma; + int chroma_stride_shift = 0; + int chroma_height_shift = 0; + + if (isGreyscale) { + config->input_format = UVG_FORMAT_P400; + kvzChroma = UVG_CSP_400; + } + else if (chroma == heif_chroma_420) { + config->input_format = UVG_FORMAT_P420; + kvzChroma = UVG_CSP_420; + chroma_stride_shift = 1; + chroma_height_shift = 1; + input_chroma_width = (input_width + 1) / 2; + input_chroma_height = (input_height + 1) / 2; + } + else if (chroma == heif_chroma_422) { + config->input_format = UVG_FORMAT_P422; + kvzChroma = UVG_CSP_422; + chroma_stride_shift = 1; + chroma_height_shift = 0; + input_chroma_width = (input_width + 1) / 2; + input_chroma_height = input_height; + } + else if (chroma == heif_chroma_444) { + config->input_format = UVG_FORMAT_P444; + kvzChroma = UVG_CSP_444; + chroma_stride_shift = 0; + chroma_height_shift = 0; + input_chroma_width = input_width; + input_chroma_height = input_height; + } + else { + return heif_error{ + heif_error_Encoder_plugin_error, + heif_suberror_Unsupported_image_type, + kError_unsupported_chroma + }; + } + + if (chroma != heif_chroma_monochrome) { + int w = heif_image_get_width(image, heif_channel_Y); + int h = heif_image_get_height(image, heif_channel_Y); + if (chroma != heif_chroma_444) { w = (w + 1) / 2; } + if (chroma == heif_chroma_420) { h = (h + 1) / 2; } + + assert(heif_image_get_width(image, heif_channel_Cb) == w); + assert(heif_image_get_width(image, heif_channel_Cr) == w); + assert(heif_image_get_height(image, heif_channel_Cb) == h); + assert(heif_image_get_height(image, heif_channel_Cr) == h); + (void) w; + (void) h; + } + + struct heif_color_profile_nclx* nclx = nullptr; + heif_error err = heif_image_get_nclx_color_profile(image, &nclx); + if (err.code != heif_error_Ok) { + nclx = nullptr; + } + + // make sure NCLX profile is deleted at end of function + auto nclx_deleter = std::unique_ptr(nclx, heif_nclx_color_profile_free); + + if (nclx) { + config->vui.fullrange = nclx->full_range_flag; + } + else { + config->vui.fullrange = 1; + } + + if (nclx && + (input_class == heif_image_input_class_normal || + input_class == heif_image_input_class_thumbnail)) { + config->vui.colorprim = nclx->color_primaries; + config->vui.transfer = nclx->transfer_characteristics; + config->vui.colormatrix = nclx->matrix_coefficients; + } + + config->qp = ((100 - encoder->quality) * 51 + 50) / 100; + config->lossless = encoder->lossless ? 1 : 0; + + config->width = encoded_width; + config->height = encoded_height; + + // Note: it is ok to cast away the const, as the image content is not changed. + // However, we have to guarantee that there are no plane pointers or stride values kept over calling the svt_encode_image() function. + /* + err = heif_image_extend_padding_to_size(const_cast(image), + param->sourceWidth, + param->sourceHeight); + if (err.code) { + return err; + } +*/ + + uvg_picture* pic = api->picture_alloc_csp(kvzChroma, encoded_width, encoded_height); + if (!pic) { + api->config_destroy(config); + return heif_error{ + heif_error_Encoder_plugin_error, + heif_suberror_Encoder_encoding, + kError_unspecified_error + }; + } + + if (isGreyscale) { + int stride; + const uint8_t* data = heif_image_get_plane_readonly(image, heif_channel_Y, &stride); + + copy_plane(pic->y, pic->stride, data, stride, input_width, input_height, encoded_width, encoded_height); + } + else { + int stride; + const uint8_t* data; + + data = heif_image_get_plane_readonly(image, heif_channel_Y, &stride); + copy_plane(pic->y, pic->stride, data, stride, input_width, input_height, encoded_width, encoded_height); + + data = heif_image_get_plane_readonly(image, heif_channel_Cb, &stride); + copy_plane(pic->u, pic->stride >> chroma_stride_shift, data, stride, input_chroma_width, input_chroma_height, + encoded_width >> chroma_stride_shift, encoded_height >> chroma_height_shift); + + data = heif_image_get_plane_readonly(image, heif_channel_Cr, &stride); + copy_plane(pic->v, pic->stride >> chroma_stride_shift, data, stride, input_chroma_width, input_chroma_height, + encoded_width >> chroma_stride_shift, encoded_height >> chroma_height_shift); + } + + uvg_encoder* kvzencoder = api->encoder_open(config); + if (!kvzencoder) { + api->picture_free(pic); + api->config_destroy(config); + + return heif_error{ + heif_error_Encoder_plugin_error, + heif_suberror_Encoder_encoding, + kError_unspecified_error + }; + } + + uvg_data_chunk* data = nullptr; + uint32_t data_len; + int success; + success = api->encoder_headers(kvzencoder, &data, &data_len); + if (!success) { + api->picture_free(pic); + api->config_destroy(config); + api->encoder_close(kvzencoder); + + return heif_error{ + heif_error_Encoder_plugin_error, + heif_suberror_Encoder_encoding, + kError_unspecified_error + }; + } + + append_chunk_data(data, encoder->output_data); + + success = api->encoder_encode(kvzencoder, + pic, + &data, &data_len, + nullptr, nullptr, nullptr); + if (!success) { + api->chunk_free(data); + api->picture_free(pic); + api->config_destroy(config); + api->encoder_close(kvzencoder); + + return heif_error{ + heif_error_Encoder_plugin_error, + heif_suberror_Encoder_encoding, + kError_unspecified_error + }; + } + + append_chunk_data(data, encoder->output_data); + + for (;;) { + success = api->encoder_encode(kvzencoder, + nullptr, + &data, &data_len, + nullptr, nullptr, nullptr); + if (!success) { + api->chunk_free(data); + api->picture_free(pic); + api->config_destroy(config); + api->encoder_close(kvzencoder); + + return heif_error{ + heif_error_Encoder_plugin_error, + heif_suberror_Encoder_encoding, + kError_unspecified_error + }; + } + + if (data == nullptr || data->len == 0) { + break; + } + + append_chunk_data(data, encoder->output_data); + } + + (void) success; + + api->chunk_free(data); + + api->encoder_close(kvzencoder); + api->picture_free(pic); + api->config_destroy(config); + + return heif_error_ok; +} + + +static struct heif_error uvg266_get_compressed_data(void* encoder_raw, uint8_t** data, int* size, + enum heif_encoded_data_type* type) +{ + struct encoder_struct_uvg266* encoder = (struct encoder_struct_uvg266*) encoder_raw; + + if (encoder->output_idx == encoder->output_data.size()) { + *data = nullptr; + *size = 0; + + return heif_error_ok; + } + + size_t start_idx = encoder->output_idx; + + while (start_idx < encoder->output_data.size() - 3 && + (encoder->output_data[start_idx] != 0 || + encoder->output_data[start_idx + 1] != 0 || + encoder->output_data[start_idx + 2] != 1)) { + start_idx++; + } + + size_t end_idx = start_idx + 1; + + while (end_idx < encoder->output_data.size() - 3 && + (encoder->output_data[end_idx] != 0 || + encoder->output_data[end_idx + 1] != 0 || + encoder->output_data[end_idx + 2] != 1)) { + end_idx++; + } + + if (end_idx == encoder->output_data.size() - 3) { + end_idx = encoder->output_data.size(); + } + + *data = encoder->output_data.data() + start_idx + 3; + *size = (int) (end_idx - start_idx - 3); + + encoder->output_idx = end_idx; + + return heif_error_ok; +} + + +static const struct heif_encoder_plugin encoder_plugin_uvg266 + { + /* plugin_api_version */ 3, + /* compression_format */ heif_compression_VVC, + /* id_name */ "uvg266", + /* priority */ uvg266_PLUGIN_PRIORITY, + /* supports_lossy_compression */ true, + /* supports_lossless_compression */ true, + /* get_plugin_name */ uvg266_plugin_name, + /* init_plugin */ uvg266_init_plugin, + /* cleanup_plugin */ uvg266_cleanup_plugin, + /* new_encoder */ uvg266_new_encoder, + /* free_encoder */ uvg266_free_encoder, + /* set_parameter_quality */ uvg266_set_parameter_quality, + /* get_parameter_quality */ uvg266_get_parameter_quality, + /* set_parameter_lossless */ uvg266_set_parameter_lossless, + /* get_parameter_lossless */ uvg266_get_parameter_lossless, + /* set_parameter_logging_level */ uvg266_set_parameter_logging_level, + /* get_parameter_logging_level */ uvg266_get_parameter_logging_level, + /* list_parameters */ uvg266_list_parameters, + /* set_parameter_integer */ uvg266_set_parameter_integer, + /* get_parameter_integer */ uvg266_get_parameter_integer, + /* set_parameter_boolean */ uvg266_set_parameter_integer, // boolean also maps to integer function + /* get_parameter_boolean */ uvg266_get_parameter_integer, // boolean also maps to integer function + /* set_parameter_string */ uvg266_set_parameter_string, + /* get_parameter_string */ uvg266_get_parameter_string, + /* query_input_colorspace */ uvg266_query_input_colorspace, + /* encode_image */ uvg266_encode_image, + /* get_compressed_data */ uvg266_get_compressed_data, + /* query_input_colorspace (v2) */ uvg266_query_input_colorspace2, + /* query_encoded_size (v3) */ uvg266_query_encoded_size + }; + +const struct heif_encoder_plugin* get_encoder_plugin_uvg266() +{ + return &encoder_plugin_uvg266; +} + + +#if PLUGIN_UVG266 +heif_plugin_info plugin_info { + 1, + heif_plugin_type_encoder, + &encoder_plugin_uvg266 +}; +#endif diff --git a/libheif/plugins/encoder_uvg266.h b/libheif/plugins/encoder_uvg266.h new file mode 100644 index 0000000000..b9eec90c25 --- /dev/null +++ b/libheif/plugins/encoder_uvg266.h @@ -0,0 +1,47 @@ +/* + * HEIF codec. + * Copyright (c) 2023 Dirk Farin + * + * 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 . + */ + +#ifndef LIBHEIF_ENCODER_UVG266_H +#define LIBHEIF_ENCODER_UVG266_H + +#include "libheif/common_utils.h" + +/* TODO: check whether this is also the case with uvg266. + * + * Image sizes in HEVC: since HEVC does not allow for odd image dimensions when + using chroma 4:2:0, our strategy is as follows. + + - Images with odd dimensions are extended with an extra row/column which + contains a copy of the border. + - The HEVC image size generated by uvg266 is rounded up to the CTU size (?) + and the conformance window has to be respected. + - We add an additional crop transform to remove the extra row/column. + */ + + +const struct heif_encoder_plugin* get_encoder_plugin_uvg266(); + +#if PLUGIN_UVG266 +extern "C" { +MAYBE_UNUSED LIBHEIF_API extern heif_plugin_info plugin_info; +} +#endif + +#endif diff --git a/libheif/vvc.cc b/libheif/vvc.cc index f778324c1b..1e084eeb8d 100644 --- a/libheif/vvc.cc +++ b/libheif/vvc.cc @@ -19,6 +19,7 @@ */ #include "vvc.h" +#include #include Error Box_vvcC::parse(BitstreamRange& range) @@ -50,6 +51,40 @@ Error Box_vvcC::parse(BitstreamRange& range) c.numOfArrays = range.read8(); + int nArrays = range.read8(); + + for (int i = 0; i < nArrays && !range.error(); i++) { + byte = range.read8(); + + NalArray array; + + array.m_array_completeness = (byte >> 6) & 1; + array.m_NAL_unit_type = (byte & 0x3F); + + int nUnits = range.read16(); + for (int u = 0; u < nUnits && !range.error(); u++) { + + std::vector nal_unit; + int size = range.read16(); + if (!size) { + // Ignore empty NAL units. + continue; + } + + if (range.prepare_read(size)) { + nal_unit.resize(size); + bool success = range.get_istream()->read((char*) nal_unit.data(), size); + if (!success) { + return Error{heif_error_Invalid_input, heif_suberror_End_of_data, "error while reading hvcC box"}; + } + } + + array.m_nal_units.push_back(std::move(nal_unit)); + } + + m_nal_array.push_back(std::move(array)); + } + #if 0 const int64_t configOBUs_bytes = range.get_remaining_bytes(); m_config_OBUs.resize(configOBUs_bytes); @@ -63,6 +98,32 @@ Error Box_vvcC::parse(BitstreamRange& range) } +void Box_vvcC::append_nal_data(const std::vector& nal) +{ + NalArray array; + array.m_array_completeness = 0; + array.m_NAL_unit_type = uint8_t(nal[0] >> 1); + array.m_nal_units.push_back(nal); + + m_nal_array.push_back(array); +} + + +void Box_vvcC::append_nal_data(const uint8_t* data, size_t size) +{ + std::vector nal; + nal.resize(size); + memcpy(nal.data(), data, size); + + NalArray array; + array.m_array_completeness = 0; + array.m_NAL_unit_type = uint8_t(nal[0] >> 1); + array.m_nal_units.push_back(std::move(nal)); + + m_nal_array.push_back(array); +} + + Error Box_vvcC::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); diff --git a/libheif/vvc.h b/libheif/vvc.h index ce51331813..92bf34e764 100644 --- a/libheif/vvc.h +++ b/libheif/vvc.h @@ -80,8 +80,8 @@ class Box_vvcC : public Box const configuration& get_configuration() const { return m_configuration; } - //void append_nal_data(const std::vector& nal); - //void append_nal_data(const uint8_t* data, size_t size); + void append_nal_data(const std::vector& nal); + void append_nal_data(const uint8_t* data, size_t size); Error write(StreamWriter& writer) const override; @@ -89,8 +89,18 @@ class Box_vvcC : public Box Error parse(BitstreamRange& range) override; private: + struct NalArray + { + uint8_t m_array_completeness; + uint8_t m_NAL_unit_type; + + std::vector > m_nal_units; + }; + configuration m_configuration; + //uint8_t m_length_size = 4; // default: 4 bytes for NAL unit lengths + std::vector m_nal_array; std::vector m_config_NALs; };