diff --git a/CMakeLists.txt b/CMakeLists.txt index f785a0a78c..1d7f2f4ae9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required (VERSION 3.16.3) # Oldest Ubuntu LTS (20.04 currently) -project(libheif LANGUAGES C CXX VERSION 1.17.1) +project(libheif LANGUAGES C CXX VERSION 1.17.2) # compatibility_version is never allowed to be decreased for any specific SONAME. # Libtool in the libheif-1.15.1 release had set it to 17.0.0, so we have to use this for the v1.x.y versions. diff --git a/examples/decoder_png.cc b/examples/decoder_png.cc index 5f8feb3cc4..5742f02653 100644 --- a/examples/decoder_png.cc +++ b/examples/decoder_png.cc @@ -213,6 +213,7 @@ InputImage loadPNG(const char* filename, int output_bit_depth) // --- read XMP data +#ifdef PNG_iTXt_SUPPORTED png_textp textPtr = nullptr; const png_uint_32 nTextChunks = png_get_text(png_ptr, info_ptr, &textPtr, nullptr); for (png_uint_32 i = 0; i < nTextChunks; i++, textPtr++) { @@ -231,6 +232,7 @@ InputImage loadPNG(const char* filename, int output_bit_depth) } } } +#endif int band = png_get_channels(png_ptr, info_ptr); diff --git a/examples/encoder_png.cc b/examples/encoder_png.cc index e53660a1f1..a24b0593ea 100644 --- a/examples/encoder_png.cc +++ b/examples/encoder_png.cc @@ -134,6 +134,7 @@ bool PngEncoder::Encode(const struct heif_image_handle* handle, // --- write XMP metadata +#ifdef PNG_iTXt_SUPPORTED // spec: https://raw.githubusercontent.com/adobe/xmp-docs/master/XMPSpecifications/XMPSpecificationPart3.pdf std::vector xmp = get_xmp_metadata(handle); if (!xmp.empty()) { @@ -156,6 +157,7 @@ bool PngEncoder::Encode(const struct heif_image_handle* handle, xmp_text.itxt_length = text_length; png_set_text(png_ptr, info_ptr, &xmp_text, 1); } +#endif png_write_info(png_ptr, info_ptr); diff --git a/examples/heif_convert.cc b/examples/heif_convert.cc index f474901798..0e3cb4d2de 100644 --- a/examples/heif_convert.cc +++ b/examples/heif_convert.cc @@ -171,7 +171,7 @@ void list_all_decoders() std::cout << "JPEG decoders:\n"; list_decoders(heif_compression_JPEG); - std::cout << "JPEG-2000 decoders:\n"; + std::cout << "JPEG 2000 decoders:\n"; list_decoders(heif_compression_JPEG2000); #if WITH_UNCOMPRESSED_CODEC diff --git a/examples/heif_enc.cc b/examples/heif_enc.cc index 5760ccecd4..754f566379 100644 --- a/examples/heif_enc.cc +++ b/examples/heif_enc.cc @@ -64,9 +64,9 @@ int metadata_compression = 0; const char* encoderId = nullptr; std::string chroma_downsampling; +uint16_t nclx_colour_primaries = 1; +uint16_t nclx_transfer_characteristic = 13; uint16_t nclx_matrix_coefficients = 6; -uint16_t nclx_colour_primaries = 2; -uint16_t nclx_transfer_characteristic = 2; int nclx_full_range = true; std::string property_pitm_description; @@ -160,7 +160,7 @@ void show_help(const char* argv0) << " -p set encoder parameter (NAME=VALUE)\n" << " -A, --avif encode as AVIF (not needed if output filename with .avif suffix is provided)\n" << " --jpeg encode as JPEG\n" - << " --jpeg2000 encode as JPEG-2000 (experimental)\n" + << " --jpeg2000 encode as JPEG 2000 (experimental)\n" #if WITH_UNCOMPRESSED_CODEC << " -U, --uncompressed encode as uncompressed image (according to ISO 23001-17) (EXPERIMENTAL)\n" #endif @@ -349,6 +349,30 @@ static void show_list_of_encoders(const heif_encoder_descriptor* const* encoder_ } +static const char* get_compression_format_name(heif_compression_format format) +{ + switch (format) { + case heif_compression_AV1: + return "AV1"; + break; + case heif_compression_HEVC: + return "HEVC"; + break; + case heif_compression_JPEG: + return "JPEG"; + break; + case heif_compression_JPEG2000: + return "JPEG 2000"; + break; + case heif_compression_uncompressed: + return "Uncompressed"; + break; + default: + assert(false); + return "unknown"; + } +} + static void show_list_of_all_encoders() { for (auto compression_format : {heif_compression_HEVC, heif_compression_AV1, heif_compression_JPEG, heif_compression_JPEG2000 @@ -368,7 +392,7 @@ static void show_list_of_all_encoders() std::cout << "JPEG"; break; case heif_compression_JPEG2000: - std::cout << "JPEG-2000"; + std::cout << "JPEG 2000"; break; case heif_compression_uncompressed: std::cout << "Uncompressed"; @@ -687,7 +711,7 @@ int main(int argc, char** argv) active_encoder_descriptor = encoder_descriptors[idx]; } else { - std::cerr << "No " << (compressionFormat == heif_compression_AV1 ? "AV1" : "HEVC") << " encoder available.\n"; + std::cerr << "No " << get_compression_format_name(compressionFormat) << " encoder available.\n"; return 5; } diff --git a/libheif/box.cc b/libheif/box.cc index 4c0a85afe4..ccf8257be4 100644 --- a/libheif/box.cc +++ b/libheif/box.cc @@ -85,7 +85,7 @@ Fraction::Fraction(int64_t num, int64_t den) Fraction Fraction::operator+(const Fraction& b) const { if (denominator == b.denominator) { - int64_t n = numerator + b.numerator; + int64_t n = int64_t{numerator} + b.numerator; int64_t d = denominator; return Fraction{n,d}; } @@ -99,7 +99,7 @@ Fraction Fraction::operator+(const Fraction& b) const Fraction Fraction::operator-(const Fraction& b) const { if (denominator == b.denominator) { - int64_t n = numerator - b.numerator; + int64_t n = int64_t{numerator} - b.numerator; int64_t d = denominator; return Fraction{n,d}; } @@ -3087,3 +3087,74 @@ Error Box_udes::write(StreamWriter& writer) const prepend_header(writer, box_start); return Error::Ok; } + + + +std::string Box_taic::dump(Indent& indent) const { + std::ostringstream sstr; + sstr << Box::dump(indent); + sstr << indent << "time_uncertainty: " << m_time_uncertainty << "\n"; + sstr << indent << "correction_offset: " << m_correction_offset << "\n"; + sstr << indent << "clock_drift_rate: " << m_clock_drift_rate << "\n"; + sstr << indent << "clock_source: " << m_clock_source << "\n"; + return sstr.str(); +} + +Error Box_taic::write(StreamWriter& writer) const { + uint32_t cdr_uint32; + std::memcpy(&cdr_uint32, &m_clock_drift_rate, sizeof(float)); + + size_t box_start = reserve_box_header_space(writer); + writer.write64(m_time_uncertainty); + writer.write64(m_correction_offset); + writer.write32(cdr_uint32); + writer.write8(m_clock_source); + + prepend_header(writer, box_start); + + return Error::Ok; +} + +Error Box_taic::parse(BitstreamRange& range) { + parse_full_box_header(range); + + uint64_t high = range.read32(); + uint64_t low = range.read32(); + m_time_uncertainty = (high << 32) | low; + + high = range.read32(); + low = range.read32(); + m_correction_offset = (high << 32) | low; + + m_clock_drift_rate = (float) range.read32(); + m_clock_source = range.read8(); + return range.get_error(); +} + +std::string Box_itai::dump(Indent& indent) const { + std::ostringstream sstr; + sstr << Box::dump(indent); + sstr << indent << "TAI_timestamp: " << m_TAI_timestamp << "\n"; + sstr << indent << "status_bits: " << m_status_bits << "\n"; + return sstr.str(); +} + +Error Box_itai::write(StreamWriter& writer) const { + size_t box_start = reserve_box_header_space(writer); + writer.write64(m_TAI_timestamp); + writer.write8(m_status_bits); + + prepend_header(writer, box_start); + return Error::Ok; +} + +Error Box_itai::parse(BitstreamRange& range) { + parse_full_box_header(range); + + uint64_t high = range.read32(); + uint64_t low = range.read32(); + m_TAI_timestamp = (high << 32) | low; + + m_status_bits = range.read8(); + return range.get_error(); +} \ No newline at end of file diff --git a/libheif/box.h b/libheif/box.h index 3a651f6ea9..ae70c1677f 100644 --- a/libheif/box.h +++ b/libheif/box.h @@ -1058,4 +1058,112 @@ class Box_udes : public FullBox std::string m_tags; }; + + +class Box_taic : public FullBox +{ +public: + Box_taic() + { + set_short_type(fourcc("taic")); + } + + std::string dump(Indent&) const override; + + Error write(StreamWriter& writer) const override; + + /** + * time_uncertainty. + * + * The standard deviation measurement uncertainty in nanoseconds + * for the timestamp generation process. + */ + void set_time_uncertainty(uint64_t time_uncertainty) { m_time_uncertainty = time_uncertainty;} + + /** + * correction_offset. + * + * The difference in nanoseconds between the clock’s reported + * timestamp and true time value of the measurement event. + */ + void set_correction_offset(int64_t correction_offset) { m_correction_offset = correction_offset; } + + /** + * clock_drift_rate. + * + * The difference between the synchronized and unsynchronized + * time, over a period of one second. + */ + void set_clock_drift_rate(float clock_drift_rate) { m_clock_drift_rate = clock_drift_rate; } + + /** + * clock_source. + * + * 0 = Clock type is unkown + * 1 = The clock does not synchronize to an atomic source of absolute TAI time + * 2 = The clock can synchronize to an atomic source of absolute TAI time + */ + void set_clock_source(uint8_t clock_source) { m_clock_source = clock_source; } + + uint64_t get_time_uncertainty() const { return m_time_uncertainty; } + + int64_t get_correction_offset() const { return m_correction_offset; } + + float get_clock_drift_rate() const { return m_clock_drift_rate; } + + uint8_t get_clock_source() const { return m_clock_source; } + +protected: + Error parse(BitstreamRange& range) override; + +private: + // Initialized to "unknown" + uint64_t m_time_uncertainty = 0xFFFFFFFFFFFFFFFF; + int64_t m_correction_offset = 0x7FFFFFFFFFFFFFFF; + float m_clock_drift_rate = std::numeric_limits::quiet_NaN(); + uint8_t m_clock_source = 0; +}; + + +class Box_itai : public FullBox +{ +public: + Box_itai() + { + set_short_type(fourcc("itai")); + } + + std::string dump(Indent&) const override; + + Error write(StreamWriter& writer) const override; + + /** + * timestamp. + * + * The number of nanoseconds since the TAI epoch of 1958-01-01T00:00:00.0Z. + */ + void set_TAI_timestamp(uint64_t timestamp) { m_TAI_timestamp = timestamp; } + + /** + * status_bits. + * + * Bit 0: Synchronization Status (0=unsynchronized, 1=synchronized) + * Bit 1: Timestamp validity (0=invalid, 1=valid) + * Bits 2-7: Reserved + */ + void set_status_bits(uint8_t status_bits) { m_status_bits = status_bits; } + + uint64_t get_TAI_timestamp() const { return m_TAI_timestamp; } + + uint8_t get_status_bits() const { return m_status_bits; } + +protected: + Error parse(BitstreamRange& range) override; + +private: + // Initialized to "unknown" + uint64_t m_TAI_timestamp = 0xFFFFFFFFFFFFFFFF; + uint8_t m_status_bits = 0; +}; + #endif diff --git a/libheif/color-conversion/colorconversion.cc b/libheif/color-conversion/colorconversion.cc index 8afb75733d..5a0d6b57a4 100644 --- a/libheif/color-conversion/colorconversion.cc +++ b/libheif/color-conversion/colorconversion.cc @@ -446,8 +446,7 @@ std::shared_ptr ColorConversionPipeline::convert_image(const std // --- pass the color profiles to the new image - auto output_nclx = std::make_shared(); - *output_nclx = step.output_state.nclx_profile; + auto output_nclx = std::make_shared(step.output_state.nclx_profile); out->set_color_profile_nclx(output_nclx); out->set_color_profile_icc(in->get_color_profile_icc()); @@ -521,19 +520,7 @@ std::shared_ptr convert_colorspace(const std::shared_ptrget_color_profile_nclx(); } - // If some input nclx values are unspecified, use CCIR-601 values as default. - - if (input_state.nclx_profile.get_matrix_coefficients() == heif_matrix_coefficients_unspecified) { - input_state.nclx_profile.set_matrix_coefficients(heif_matrix_coefficients_ITU_R_BT_601_6); - } - - if (input_state.nclx_profile.get_colour_primaries() == heif_color_primaries_unspecified) { - input_state.nclx_profile.set_colour_primaries(heif_color_primaries_ITU_R_BT_601_6); - } - - if (input_state.nclx_profile.get_transfer_characteristics() == heif_color_primaries_unspecified) { - input_state.nclx_profile.set_transfer_characteristics(heif_transfer_characteristic_ITU_R_BT_601_6); - } + input_state.nclx_profile.replace_undefined_values_with_sRGB_defaults(); std::set channels = input->get_channel_set(); assert(!channels.empty()); @@ -546,7 +533,7 @@ std::shared_ptr convert_colorspace(const std::shared_ptr convert_colorspace(const std::shared_ptr compute_target_nclx_profile(const std::shared_ptr& image, const heif_color_profile_nclx* output_nclx_profile) +{ + auto target_nclx_profile = std::make_shared(); + + // If there is an output NCLX specified, use that. + if (output_nclx_profile) { + target_nclx_profile->set_from_heif_color_profile_nclx(output_nclx_profile); + } + // Otherwise, if there is an input NCLX, keep that. + else if (auto input_nclx = image->get_color_profile_nclx()) { + *target_nclx_profile = *input_nclx; + } + // Otherwise, just use the defaults (set below) + else { + target_nclx_profile->set_undefined(); + } + + target_nclx_profile->replace_undefined_values_with_sRGB_defaults(); + + return target_nclx_profile; +} + + Error HeifContext::encode_image_as_hevc(const std::shared_ptr& image, struct heif_encoder* encoder, const struct heif_encoding_options& options, @@ -2444,8 +2467,7 @@ Error HeifContext::encode_image_as_hevc(const std::shared_ptr& i heif_colorspace colorspace = image->get_colorspace(); heif_chroma chroma = image->get_chroma_format(); - auto target_nclx_profile = std::make_shared(); - target_nclx_profile->set_from_heif_color_profile_nclx(options.output_nclx_profile); + 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); @@ -2652,8 +2674,7 @@ Error HeifContext::encode_image_as_av1(const std::shared_ptr& im heif_colorspace colorspace = image->get_colorspace(); heif_chroma chroma = image->get_chroma_format(); - auto target_nclx_profile = std::make_shared(); - target_nclx_profile->set_from_heif_color_profile_nclx(options.output_nclx_profile); + 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); @@ -2828,8 +2849,7 @@ Error HeifContext::encode_image_as_jpeg2000(const std::shared_ptr(color_profile); */ - auto target_nclx_profile = std::make_shared(); - target_nclx_profile->set_from_heif_color_profile_nclx(options.output_nclx_profile); + 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); @@ -2855,6 +2875,38 @@ Error HeifContext::encode_image_as_jpeg2000(const std::shared_ptrhas_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_jpeg2000(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}); + m_heif_file->set_auxC_property(heif_alpha_image->get_id(), "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"); + + if (src_image->is_premultiplied_alpha()) { + m_heif_file->add_iref_reference(image_id, fourcc("prem"), {heif_alpha_image->get_id()}); + } + } + + //Encode Image heif_image c_api_image; c_api_image.image = src_image; diff --git a/libheif/heif.cc b/libheif/heif.cc index 05bf31cc52..373fd9971a 100644 --- a/libheif/heif.cc +++ b/libheif/heif.cc @@ -83,24 +83,19 @@ uint32_t heif_get_version_number(void) return (LIBHEIF_NUMERIC_VERSION); } -static uint8_t bcd2dec(uint8_t v) -{ - return uint8_t((v >> 4) * 10 + (v & 0x0F)); -} - int heif_get_version_number_major(void) { - return bcd2dec(((LIBHEIF_NUMERIC_VERSION) >> 24) & 0xFF); + return ((LIBHEIF_NUMERIC_VERSION) >> 24) & 0xFF; } int heif_get_version_number_minor(void) { - return bcd2dec(((LIBHEIF_NUMERIC_VERSION) >> 16) & 0xFF); + return ((LIBHEIF_NUMERIC_VERSION) >> 16) & 0xFF; } int heif_get_version_number_maintenance(void) { - return bcd2dec(((LIBHEIF_NUMERIC_VERSION) >> 8) & 0xFF); + return ((LIBHEIF_NUMERIC_VERSION) >> 8) & 0xFF; } @@ -2606,7 +2601,7 @@ static void set_default_options(heif_encoding_options& options) options.macOS_compatibility_workaround = false; options.save_two_colr_boxes_when_ICC_and_nclx_available = false; options.output_nclx_profile = nullptr; - options.macOS_compatibility_workaround_no_nclx_profile = true; + options.macOS_compatibility_workaround_no_nclx_profile = false; options.image_orientation = heif_orientation_normal; options.color_conversion_options.version = 1; diff --git a/libheif/heif.h b/libheif/heif.h index 0c25f5eada..71c325644f 100644 --- a/libheif/heif.h +++ b/libheif/heif.h @@ -77,19 +77,18 @@ extern "C" { // Version string of linked libheif library. LIBHEIF_API const char* heif_get_version(void); -// Numeric version of linked libheif library, encoded as BCD 0xHHMMLL00 = HH.MM.LL. -// For example: 0x02143000 is version 2.14.30 +// Numeric version of linked libheif library, encoded as 0xHHMMLL00 = hh.mm.ll, where hh, mm, ll is the decimal representation of HH, MM, LL. +// For example: 0x02150300 is version 2.21.3 LIBHEIF_API uint32_t heif_get_version_number(void); -// Numeric part "HH" from above. Returned as a decimal number (not BCD). +// Numeric part "HH" from above. Returned as a decimal number. LIBHEIF_API int heif_get_version_number_major(void); -// Numeric part "MM" from above. Returned as a decimal number (not BCD). +// Numeric part "MM" from above. Returned as a decimal number. LIBHEIF_API int heif_get_version_number_minor(void); -// Numeric part "LL" from above. Returned as a decimal number (not BCD). +// Numeric part "LL" from above. Returned as a decimal number. LIBHEIF_API int heif_get_version_number_maintenance(void); // Helper macros to check for given versions of libheif at compile time. -// Note: h, m, l should be 2-digit BCD numbers. I.e., decimal 17 = 0x17 (BCD) #define LIBHEIF_MAKE_VERSION(h, m, l) ((h) << 24 | (m) << 16 | (l) << 8) #define LIBHEIF_HAVE_VERSION(h, m, l) (LIBHEIF_NUMERIC_VERSION >= LIBHEIF_MAKE_VERSION(h, m, l)) diff --git a/libheif/heif_properties.cc b/libheif/heif_properties.cc index d73cea41ca..9189113e24 100644 --- a/libheif/heif_properties.cc +++ b/libheif/heif_properties.cc @@ -300,3 +300,168 @@ void heif_property_user_description_release(struct heif_property_user_descriptio delete udes; } + +struct heif_error heif_property_add_clock_info(const struct heif_context* context, + heif_item_id itemId, + const uint64_t* time_uncertainty, + const int64_t* correction_offset, + const float* clock_drift_rate, + const uint8_t* clock_source, + heif_property_id* out_propertyId) +{ + if (!context) { + return {heif_error_Usage_error, heif_suberror_Null_pointer_argument, "NULL passed"}; + } + + auto taic = std::make_shared(); + if (time_uncertainty != nullptr) { + taic->set_time_uncertainty(*time_uncertainty); + } + if (correction_offset != nullptr) { + taic->set_correction_offset(*correction_offset); + } + if (clock_drift_rate != nullptr) { + taic->set_clock_drift_rate(*clock_drift_rate); + } + if (clock_source != nullptr) { + taic->set_clock_source(*clock_source); + } + + bool essential = false; + heif_property_id id = context->context->add_property(itemId, taic, essential); + + if (out_propertyId) { + *out_propertyId = id; + } + + return heif_error_success; +} + +struct heif_error heif_property_get_clock_info(const struct heif_context* context, + heif_item_id itemId, + heif_property_id propertyId, + uint64_t* out_time_uncertainty, + int64_t* out_correction_offset, + float* out_clock_drift_rate, + uint8_t* out_clock_source) +{ + if (!context) { + return {heif_error_Usage_error, heif_suberror_Invalid_parameter_value, "NULL passed"}; + } + + auto file = context->context->get_heif_file(); + + std::vector> properties; + Error err = file->get_properties(itemId, properties); + if (err) { + return err.error_struct(context->context.get()); + } + + uint32_t propertyIndex = propertyId - 1; + if (propertyIndex < 0 || propertyIndex >= properties.size()) { + return {heif_error_Usage_error, heif_suberror_Invalid_property, "property index out of range"}; + } + + auto taic = std::dynamic_pointer_cast(properties[propertyIndex]); + if (!taic) { + return {heif_error_Usage_error, heif_suberror_Invalid_property, "wrong property type"}; + } + + if (out_time_uncertainty == nullptr) { + *out_time_uncertainty = taic->get_time_uncertainty(); + } + if (out_correction_offset == nullptr) { + *out_correction_offset = taic->get_correction_offset(); + } + if (out_clock_drift_rate == nullptr) { + *out_clock_drift_rate = taic->get_clock_drift_rate(); + } + if (out_clock_source == nullptr) { + *out_clock_source = taic->get_clock_source(); + } + + + return heif_error_success; + +} + +struct heif_error heif_property_add_tai_timestamp(const struct heif_context* context, + heif_item_id itemId, + const uint64_t* tai_timestamp, + const uint8_t* status_bits, + heif_property_id* out_propertyId) +{ + if (!context) { + return {heif_error_Usage_error, heif_suberror_Null_pointer_argument, "NULL passed"}; + } + + auto itai = std::make_shared(); + if (tai_timestamp != nullptr) { + itai->set_TAI_timestamp(*tai_timestamp); + } + if (status_bits != nullptr) { + itai->set_status_bits(*status_bits); + } + + bool essential = false; + heif_property_id id = context->context->add_property(itemId, itai, essential); + + // A taic box shall be present point to the same item as the itai box. + heif_error err; + auto ipco = context->context->get_heif_file()->get_ipco_box(); + auto impa = context->context->get_heif_file()->get_ipma_box(); + auto taic = ipco->get_property_for_item_ID(itemId, impa, fourcc("taic")); + if (!taic) { + err = heif_property_add_clock_info(context, itemId, nullptr, nullptr, nullptr, nullptr, nullptr); + if (err.code != heif_error_Ok) { + return err; + } + } + + + if (out_propertyId) { + *out_propertyId = id; + } + + return heif_error_success; +} + +struct heif_error heif_property_get_tai_timestamp(const struct heif_context* context, + heif_item_id itemId, + heif_property_id propertyId, + uint64_t* out_tai_timestamp, + uint8_t* out_status_bits) +{ + if (!context) { + return {heif_error_Usage_error, heif_suberror_Invalid_parameter_value, "NULL passed"}; + } + + auto file = context->context->get_heif_file(); + + std::vector> properties; + Error err = file->get_properties(itemId, properties); + if (err) { + return err.error_struct(context->context.get()); + } + + uint32_t propertyIndex = propertyId - 1; + if (propertyIndex < 0 || propertyIndex >= properties.size()) { + return {heif_error_Usage_error, heif_suberror_Invalid_property, "property index out of range"}; + } + + auto itai = std::dynamic_pointer_cast(properties[propertyIndex]); + if (!itai) { + return {heif_error_Usage_error, heif_suberror_Invalid_property, "wrong property type"}; + } + + if (out_tai_timestamp) { + *out_tai_timestamp = itai->get_TAI_timestamp(); + } + if (out_status_bits) { + *out_status_bits = itai->get_status_bits(); + } + + return heif_error_success; +} + + diff --git a/libheif/heif_properties.h b/libheif/heif_properties.h index 4ed15c8a90..1dc9c680f4 100644 --- a/libheif/heif_properties.h +++ b/libheif/heif_properties.h @@ -37,7 +37,9 @@ enum heif_item_property_type heif_item_property_type_transform_mirror = heif_fourcc('i', 'm', 'i', 'r'), heif_item_property_type_transform_rotation = heif_fourcc('i', 'r', 'o', 't'), heif_item_property_type_transform_crop = heif_fourcc('c', 'l', 'a', 'p'), - heif_item_property_type_image_size = heif_fourcc('i', 's', 'p', 'e') + heif_item_property_type_image_size = heif_fourcc('i', 's', 'p', 'e'), + heif_item_property_type_tai_clock_info = heif_fourcc('t', 'a', 'i', 'c'), + heif_item_property_type_tai_timestamp = heif_fourcc('i', 't', 'a', 'i'), }; // Get the heif_property_id for a heif_item_id. @@ -131,6 +133,40 @@ void heif_item_get_property_transform_crop_borders(const struct heif_context* co int image_width, int image_height, int* left, int* top, int* right, int* bottom); + +// ========================= Timestamps ========================= +// Add required metadata about +LIBHEIF_API +struct heif_error heif_property_add_clock_info(const struct heif_context* context, + heif_item_id itemId, + const uint64_t* time_uncertainty, + const int64_t* correction_offset, + const float* clock_drift_rate, + const uint8_t* clock_source, + heif_property_id* out_propertyId); + +LIBHEIF_API +struct heif_error heif_property_get_clock_info(const struct heif_context* context, + heif_item_id itemId, + uint64_t* out_time_uncertainty, + int64_t* out_correction_offset, + float* out_clock_drift_rate, + uint8_t* out_clock_source); + +struct heif_error heif_property_add_tai_timestamp(const struct heif_context* context, + heif_item_id itemId, + const uint64_t* tai_timestamp, + const uint8_t* status_bits, + heif_property_id* out_propertyId); + +LIBHEIF_API +struct heif_error heif_property_get_tai_timestamp(const struct heif_context* context, + heif_item_id itemId, + heif_property_id propertyId, + uint64_t* out_tai_timestamp, + uint8_t* out_status_bits); + + #ifdef __cplusplus } #endif diff --git a/libheif/jpeg.cc b/libheif/jpeg.cc index bb71587aed..22347cdd26 100644 --- a/libheif/jpeg.cc +++ b/libheif/jpeg.cc @@ -20,6 +20,7 @@ #include "jpeg.h" #include +#include "security_limits.h" std::string Box_jpgC::dump(Indent& indent) const { @@ -46,7 +47,15 @@ Error Box_jpgC::write(StreamWriter& writer) const Error Box_jpgC::parse(BitstreamRange& range) { + if (!has_fixed_box_size()) { + return Error{heif_error_Unsupported_feature, heif_suberror_Unspecified, "jpgC with unspecified size are not supported"}; + } + size_t nBytes = range.get_remaining_bytes(); + if (nBytes > MAX_MEMORY_BLOCK_SIZE) { + return Error{heif_error_Invalid_input, heif_suberror_Unspecified, "jpgC block exceeds maximum size"}; + } + m_data.resize(nBytes); range.read(m_data.data(), nBytes); return range.get_error(); diff --git a/libheif/nclx.cc b/libheif/nclx.cc index 46d0bdf454..95339f4ed5 100644 --- a/libheif/nclx.cc +++ b/libheif/nclx.cc @@ -20,6 +20,7 @@ #include "nclx.h" +#include "security_limits.h" #include #include @@ -294,9 +295,11 @@ struct heif_color_profile_nclx* color_profile_nclx::alloc_nclx_color_profile() if (profile) { profile->version = 1; - profile->color_primaries = heif_color_primaries_unspecified; - profile->transfer_characteristics = heif_transfer_characteristic_unspecified; - profile->matrix_coefficients = heif_matrix_coefficients_ITU_R_BT_601_6; + + // sRGB defaults + profile->color_primaries = heif_color_primaries_ITU_R_BT_709_5; // 1 + profile->transfer_characteristics = heif_transfer_characteristic_IEC_61966_2_1; // 13 + profile->matrix_coefficients = heif_matrix_coefficients_ITU_R_BT_601_6; // 6 profile->full_range_flag = true; } @@ -310,10 +313,11 @@ void color_profile_nclx::free_nclx_color_profile(struct heif_color_profile_nclx* } -void color_profile_nclx::set_default() +void color_profile_nclx::set_sRGB_defaults() { - m_colour_primaries = 2; - m_transfer_characteristics = 2; + // sRGB defaults + m_colour_primaries = 1; + m_transfer_characteristics = 13; m_matrix_coefficients = 6; m_full_range_flag = true; } @@ -339,6 +343,22 @@ void color_profile_nclx::set_from_heif_color_profile_nclx(const struct heif_colo } +void color_profile_nclx::replace_undefined_values_with_sRGB_defaults() +{ + if (m_matrix_coefficients == heif_matrix_coefficients_unspecified) { + m_matrix_coefficients = heif_matrix_coefficients_ITU_R_BT_601_6; + } + + if (m_colour_primaries == heif_color_primaries_unspecified) { + m_colour_primaries = heif_color_primaries_ITU_R_BT_709_5; + } + + if (m_transfer_characteristics == heif_transfer_characteristic_unspecified) { + m_transfer_characteristics = heif_transfer_characteristic_IEC_61966_2_1; + } +} + + Error Box_colr::parse(BitstreamRange& range) { StreamReader::grow_status status; @@ -354,8 +374,12 @@ Error Box_colr::parse(BitstreamRange& range) } else if (colour_type == fourcc("prof") || colour_type == fourcc("rICC")) { + if (!has_fixed_box_size()) { + return Error(heif_error_Unsupported_feature, heif_suberror_Unspecified, "colr boxes with undefined box size are not supported"); + } + uint64_t profile_size_64 = get_box_size() - get_header_size() - 4; - if (profile_size_64 > std::numeric_limits::max()) { + if (profile_size_64 > MAX_COLOR_PROFILE_SIZE) { return Error(heif_error_Invalid_input, heif_suberror_Security_limit_exceeded, "Color profile exceeds maximum supported size"); } diff --git a/libheif/nclx.h b/libheif/nclx.h index 9d6bb666bb..5779e440e3 100644 --- a/libheif/nclx.h +++ b/libheif/nclx.h @@ -121,7 +121,7 @@ class color_profile_raw : public color_profile class color_profile_nclx : public color_profile { public: - color_profile_nclx() { set_default(); } + color_profile_nclx() { set_sRGB_defaults(); } uint32_t get_type() const override { return fourcc("nclx"); } @@ -147,7 +147,7 @@ class color_profile_nclx : public color_profile void set_full_range_flag(bool full_range) { m_full_range_flag = full_range; } - void set_default(); + void set_sRGB_defaults(); void set_undefined(); @@ -159,6 +159,8 @@ class color_profile_nclx : public color_profile void set_from_heif_color_profile_nclx(const struct heif_color_profile_nclx* nclx); + void replace_undefined_values_with_sRGB_defaults(); + private: uint16_t m_colour_primaries = heif_color_primaries_unspecified; uint16_t m_transfer_characteristics = heif_transfer_characteristic_unspecified; diff --git a/libheif/plugins/encoder_jpeg.cc b/libheif/plugins/encoder_jpeg.cc index 23dce539a1..d6c785449d 100644 --- a/libheif/plugins/encoder_jpeg.cc +++ b/libheif/plugins/encoder_jpeg.cc @@ -411,6 +411,7 @@ struct heif_error jpeg_encode_image(void* encoder_raw, const struct heif_image* jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); + encoder->data_read = false; encoder->compressed_data.resize(outlength); memcpy(encoder->compressed_data.data(), outbuffer, outlength); diff --git a/libheif/plugins/encoder_openjpeg.cc b/libheif/plugins/encoder_openjpeg.cc index daa5bc1f4e..b8cdc4fc74 100644 --- a/libheif/plugins/encoder_openjpeg.cc +++ b/libheif/plugins/encoder_openjpeg.cc @@ -321,6 +321,9 @@ void opj_query_input_colorspace2(void* encoder_raw, enum heif_colorspace* inout_ if (encoder->chroma != heif_chroma_undefined) { *inout_chroma = encoder->chroma; } + else { + *inout_chroma = heif_chroma_444; + } } } @@ -518,6 +521,7 @@ struct heif_error opj_encode_image(void* encoder_raw, const struct heif_image* i } } + encoder->data_read = false; encoder->codestream.clear(); //Fixes issue when encoding multiple images and old data persists. //Encodes the image into a 'codestream' which is stored in the 'encoder' variable diff --git a/libheif/security_limits.h b/libheif/security_limits.h index 966f360aab..f1c177c0a1 100644 --- a/libheif/security_limits.h +++ b/libheif/security_limits.h @@ -27,6 +27,7 @@ static const size_t MAX_CHILDREN_PER_BOX = 20000; static const int MAX_ILOC_ITEMS = 20000; static const int MAX_ILOC_EXTENTS_PER_ITEM = 32; static const int MAX_MEMORY_BLOCK_SIZE = 512 * 1024 * 1024; // 512 MB +static const int MAX_COLOR_PROFILE_SIZE = 100 * 1024 * 1024; // 100 MB // Artificial limit to avoid allocating too much memory. // 32768^2 = 1.5 GB as YUV-4:2:0 or 4 GB as RGB32 diff --git a/libheif/uncompressed_image.cc b/libheif/uncompressed_image.cc index dec2737137..a0387b6c80 100644 --- a/libheif/uncompressed_image.cc +++ b/libheif/uncompressed_image.cc @@ -679,7 +679,8 @@ Error UncompressedImageCodec::decode_uncompressed_image(const std::shared_ptrget_components()) { uint16_t component_index = component.component_index; uint16_t component_type = cmpd->get_components()[component_index].component_type; - if (component_type == component_type_Y) { + if (component_type == component_type_Y || + component_type == component_type_monochrome) { img->add_plane(heif_channel_Y, width, height, component.component_bit_depth); channels.push_back(heif_channel_Y); channel_to_pixelOffset.emplace(heif_channel_Y, componentOffset); @@ -751,7 +752,7 @@ Error UncompressedImageCodec::decode_uncompressed_image(const std::shared_ptr& cmpd, std::shared_ptrget_bits_per_pixel(heif_channel_Alpha); Box_uncC::Component component3 = {3, (uint8_t)(bpp_alpha), component_format_unsigned, 0}; - uncC->add_component(component3); + uncC->add_component(component3); } } uncC->set_sampling_type(sampling_type_no_subsampling); @@ -1008,7 +1009,7 @@ Error fill_cmpd_and_uncC(std::shared_ptr& cmpd, std::shared_ptr& heif_file, const std::shared_ptr& src_image,