From 390cb99968ed6bd646db49b0a4b4b4e19df09c3e Mon Sep 17 00:00:00 2001 From: Victor Derks Date: Tue, 10 Sep 2024 13:47:06 +0200 Subject: [PATCH] Extend API of jpeg_encoder with encode_components methods Add an additional method for advanced encodings scenarios. This methods allow to encode components with different coding parameters like: near lossless, jpeg-ls preset coding parameters or scans with different interleave modes. Extend the decoder to also be able to decode scans with different interleave modes. --- CHANGELOG.md | 6 +- README.md | 3 - include/charls/charls_jpegls_decoder.h | 7 +- include/charls/charls_jpegls_encoder.h | 20 +++ include/charls/jpegls_decoder.hpp | 9 +- include/charls/jpegls_encoder.hpp | 28 +++++ src/charls_jpegls_decoder.cpp | 94 +++++++------- src/charls_jpegls_encoder.cpp | 87 +++++++++---- src/constants.hpp | 2 +- src/jpeg_stream_reader.cpp | 81 +++++++----- src/jpeg_stream_reader.hpp | 42 ++++++- test/compliance.cpp | 4 +- test/main.cpp | 2 +- unittest/charls_jpegls_decoder_test.cpp | 8 +- unittest/compliance_test.cpp | 2 +- unittest/documentation_test.cpp | 2 +- unittest/encode_test.cpp | 157 +++++++++++++++++++++++- unittest/jpeg_stream_reader_test.cpp | 2 +- unittest/jpegls_decoder_test.cpp | 46 +++---- unittest/jpegls_encoder_test.cpp | 2 +- unittest/util.cpp | 2 +- 21 files changed, 444 insertions(+), 162 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f44f3139..147261d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,18 +10,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) - Support to encode and decode mapping tables. - Support to retrieve the height from a DNL marker segment. +- Support to encode and decode mixed interleaved mode scans. ### Changed - BREAKING: Updated the minimal required C++ language version to C++17. - BREAKING: encoding_options::include_pc_parameters_jai is not enabled by default anymore. - BREAKING: charls::jpegls_decoder and charls::jpegls_encoder follow the same const pattern as the C API. -- BREAKING: the failure values of the enum charls::jpegls_errc are now divided in 2 groups: runtime failures and logic. +- BREAKING: the failure values of the enum charls::jpegls_errc are now divided in 2 groups: runtime errors and logic errors. - BREAKING: The public charls.h header has been split into charls.h (C applications) and charls.hpp (C++ applications). +- BREAKING: Method charls_jpegls_decoder_get_interleave_mode has an additional extra parameter: component_index. ### Removed -- BREAKING: Legacy 1.x API methods have been removed. +- BREAKING: Deprecated legacy 1.x API methods have been removed. ## [2.4.2] - 2023-5-16 diff --git a/README.md b/README.md index e5d3adb8..e5b45726 100644 --- a/README.md +++ b/README.md @@ -38,9 +38,6 @@ The following JPEG-LS options are not supported by the CharLS implementation. Mo Decoding is supported, but no recovery mechanism is implemented for corrupted JPEG-LS files. * No support for sub-sampled scans. Sub-sampling is a lossly encoding mechanism and not used in lossless scenarios. -* No support for multi component frames with mixed component counts in a single scan. - While technical possible all known JPEG-LS codecs put multi-component (color) images in a single scan - or in multiple scans, but not use a mix of these in one file. * No support for point transform. Point transform is a lossly encoding mechanism and not used in lossless scenarios. diff --git a/include/charls/charls_jpegls_decoder.h b/include/charls/charls_jpegls_decoder.h index cfd8417e..32a89002 100644 --- a/include/charls/charls_jpegls_decoder.h +++ b/include/charls/charls_jpegls_decoder.h @@ -92,11 +92,11 @@ charls_jpegls_decoder_get_frame_info(CHARLS_IN const charls_jpegls_decoder* deco /// Function should be called after calling the function charls_jpegls_decoder_read_header. /// /// Reference to the decoder instance. -/// The component index for which the NEAR parameter should be retrieved. +/// The component index for which the NEAR parameter should be retrieved. /// Reference that will hold the value of the NEAR parameter. /// The result of the operation: success or a failure code. CHARLS_CHECK_RETURN CHARLS_API_IMPORT_EXPORT charls_jpegls_errc CHARLS_API_CALLING_CONVENTION -charls_jpegls_decoder_get_near_lossless(CHARLS_IN const charls_jpegls_decoder* decoder, int32_t component, +charls_jpegls_decoder_get_near_lossless(CHARLS_IN const charls_jpegls_decoder* decoder, int32_t component_index, CHARLS_OUT int32_t* near_lossless) CHARLS_NOEXCEPT CHARLS_ATTRIBUTE((nonnull)); /// @@ -106,10 +106,11 @@ charls_jpegls_decoder_get_near_lossless(CHARLS_IN const charls_jpegls_decoder* d /// Function should be called after calling the function charls_jpegls_decoder_read_header. /// /// Reference to the decoder instance. +/// The component index for which the interleave mode should be retrieved. /// Reference that will hold the value of the interleave mode. /// The result of the operation: success or a failure code. CHARLS_CHECK_RETURN CHARLS_API_IMPORT_EXPORT charls_jpegls_errc CHARLS_API_CALLING_CONVENTION -charls_jpegls_decoder_get_interleave_mode(CHARLS_IN const charls_jpegls_decoder* decoder, +charls_jpegls_decoder_get_interleave_mode(CHARLS_IN const charls_jpegls_decoder* decoder, int32_t component_index, CHARLS_OUT charls_interleave_mode* interleave_mode) CHARLS_NOEXCEPT CHARLS_ATTRIBUTE((nonnull)); diff --git a/include/charls/charls_jpegls_encoder.h b/include/charls/charls_jpegls_encoder.h index 1f5cd39d..0994c281 100644 --- a/include/charls/charls_jpegls_encoder.h +++ b/include/charls/charls_jpegls_encoder.h @@ -280,6 +280,26 @@ charls_jpegls_encoder_encode_from_buffer(CHARLS_IN charls_jpegls_encoder* encode size_t source_size_bytes, uint32_t stride) CHARLS_NOEXCEPT CHARLS_ATTRIBUTE((nonnull)); +/// +/// Encodes the passed buffer with the source image data to the destination. +/// This is an advanced method that provides more control how image data is encoded in JPEG-LS scans. +/// It should be called until all components are encoded. +/// +/// Reference to the encoder instance. +/// Byte array that holds the image data that needs to be encoded. +/// Length of the array in bytes. +/// The number of components present in the input source. +/// +/// The number of bytes from one row of pixels in memory to the next row of pixels in memory. +/// Stride is sometimes called pitch. If padding bytes are present, the stride is wider than the width of the image. +/// +/// The result of the operation: success or a failure code. +CHARLS_ATTRIBUTE_ACCESS((access(read_only, 2, 3))) +CHARLS_CHECK_RETURN CHARLS_API_IMPORT_EXPORT charls_jpegls_errc CHARLS_API_CALLING_CONVENTION +charls_jpegls_encoder_encode_components_from_buffer(CHARLS_IN charls_jpegls_encoder* encoder, + CHARLS_IN_READS_BYTES(source_size_bytes) const void* source_buffer, + size_t source_size_bytes, int32_t source_component_count, + uint32_t stride) CHARLS_NOEXCEPT CHARLS_ATTRIBUTE((nonnull)); /// /// Creates a JPEG-LS stream in the abbreviated format that only contain mapping tables (See JPEG-LS standard, C.4). diff --git a/include/charls/jpegls_decoder.hpp b/include/charls/jpegls_decoder.hpp index dedb8f98..442cd4d1 100644 --- a/include/charls/jpegls_decoder.hpp +++ b/include/charls/jpegls_decoder.hpp @@ -48,7 +48,7 @@ class jpegls_decoder final destination.resize(destination_size / sizeof(DestinationContainerValueType)); decoder.decode(destination); - return std::make_pair(decoder.frame_info(), decoder.interleave_mode()); + return std::make_pair(decoder.frame_info(), decoder.get_interleave_mode()); } jpegls_decoder() = default; @@ -258,13 +258,14 @@ class jpegls_decoder final /// /// Returns the interleave mode that was used to encode the scan. /// + /// The component index for which the interleave mode should be retrieved. /// An error occurred during the operation. /// The value of the interleave mode. [[nodiscard]] - charls::interleave_mode interleave_mode() const + interleave_mode get_interleave_mode(const int32_t component_index = 0) const { - charls::interleave_mode interleave_mode; - check_jpegls_errc(charls_jpegls_decoder_get_interleave_mode(decoder(), &interleave_mode)); + interleave_mode interleave_mode; + check_jpegls_errc(charls_jpegls_decoder_get_interleave_mode(decoder(), component_index, &interleave_mode)); return interleave_mode; } diff --git a/include/charls/jpegls_encoder.hpp b/include/charls/jpegls_encoder.hpp index a9aef480..2960dc42 100644 --- a/include/charls/jpegls_encoder.hpp +++ b/include/charls/jpegls_encoder.hpp @@ -328,6 +328,14 @@ class jpegls_encoder final return bytes_written(); } + size_t encode_components(CHARLS_IN_READS_BYTES(source_size_bytes) const void* source_buffer, const size_t source_size_bytes, + const int32_t source_component_count, const uint32_t stride = 0) + { + check_jpegls_errc(charls_jpegls_encoder_encode_components_from_buffer(encoder(), source_buffer, source_size_bytes, + source_component_count, stride)); + return bytes_written(); + } + /// /// Encodes the passed STL like container with the source image data to the destination. /// @@ -343,6 +351,26 @@ class jpegls_encoder final return encode(source_container.data(), source_container.size() * sizeof(ContainerValueType), stride); } + /// + /// Encodes the passed STL like container with the source image data to the destination. + /// This is an advanced method that provides more control how image data is encoded in JPEG-LS scans. + /// It should be called until all components are encoded. + /// + /// Container that holds the image data that needs to be encoded. + /// The number of components present in the input source. + /// + /// The number of bytes from one row of pixels in memory to the next row of pixels in memory. + /// Stride is sometimes called pitch. If padding bytes are present, the stride is wider than the width of the image. + /// + /// The number of bytes written to the destination. + template + size_t encode_components(const Container& source_container, const int32_t source_component_count, + const uint32_t stride = 0) + { + return encode_components(source_container.data(), source_container.size() * sizeof(ContainerValueType), + source_component_count, stride); + } + /// /// Creates a JPEG-LS stream in abbreviated format that only contain mapping tables (See JPEG-LS standard, C.4). /// These tables should have been written to the stream first with the method write_mapping_table. diff --git a/src/charls_jpegls_decoder.cpp b/src/charls_jpegls_decoder.cpp index f6ebabf0..af32275c 100644 --- a/src/charls_jpegls_decoder.cpp +++ b/src/charls_jpegls_decoder.cpp @@ -58,22 +58,19 @@ struct charls_jpegls_decoder final } [[nodiscard]] - int32_t near_lossless(int32_t /*component*/ = 0) const + int32_t get_near_lossless(const size_t component_index) const { check_state_header_read(); - - // Note: The JPEG-LS standard allows to define different NEAR parameter for every scan. - return reader_.parameters().near_lossless; + check_argument(component_index < reader_.component_count()); + return reader_.get_near_lossless(component_index); } [[nodiscard]] - charls::interleave_mode interleave_mode() const + interleave_mode get_interleave_mode(const size_t component_index) const { check_state_header_read(); - - // Note: The JPEG-LS standard allows to define different interleave modes for every scan. - // CharLS doesn't support mixed interleave modes, first scan determines the mode. - return reader_.parameters().interleave_mode; + check_argument(component_index < reader_.component_count()); + return reader_.get_interleave_mode(component_index); } [[nodiscard]] @@ -100,7 +97,7 @@ struct charls_jpegls_decoder final return checked_mul(checked_mul(checked_mul(component_count, height), width), bit_to_byte_count(bits_per_sample)); } - switch (interleave_mode()) + switch (get_interleave_mode(0)) { case interleave_mode::none: { const size_t minimum_stride{static_cast(width) * bit_to_byte_count(bits_per_sample)}; @@ -173,48 +170,29 @@ struct charls_jpegls_decoder final reader_.get_mapping_table_data(mapping_table_index, table_data); } - void decode(span destination, size_t stride) + void decode(span destination, const size_t stride) { check_argument(destination.data() || destination.empty()); check_operation(state_ == state::header_read); - // Compute the stride for the uncompressed destination buffer. - const size_t minimum_stride{calculate_minimum_stride()}; - if (stride == auto_calculate_stride) + for (size_t component{};;) { - stride = minimum_stride; - } - else - { - if (UNLIKELY(stride < minimum_stride)) - throw_jpegls_error(jpegls_errc::invalid_argument_stride); - } + const size_t scan_stride{check_stride_and_destination_size(destination.size(), stride)}; - // Compute the layout of the destination buffer. - const size_t bytes_per_plane{stride * frame_info().height}; - const size_t plane_count{reader_.parameters().interleave_mode == interleave_mode::none ? frame_info().component_count - : 1U}; - if (const size_t minimum_destination_size = bytes_per_plane * plane_count - (stride - minimum_stride); - UNLIKELY(destination.size() < minimum_destination_size)) - throw_jpegls_error(jpegls_errc::destination_too_small); - - for (size_t plane{};;) - { - const auto decoder{make_scan_codec(frame_info(), reader_.get_validated_preset_coding_parameters(), - reader_.parameters())}; - const size_t bytes_read{decoder->decode_scan(reader_.remaining_source(), destination.data(), stride)}; + const auto decoder{make_scan_codec( + reader_.scan_frame_info(), reader_.get_validated_preset_coding_parameters(), reader_.parameters())}; + const size_t bytes_read{decoder->decode_scan(reader_.remaining_source(), destination.data(), scan_stride)}; reader_.advance_position(bytes_read); - ++plane; - if (plane == plane_count) + component += reader_.scan_component_count(); + if (component == reader_.component_count()) break; + destination = destination.subspan(scan_stride * frame_info().height); reader_.read_next_start_of_scan(); - destination = destination.subspan(bytes_per_plane); } reader_.read_end_of_image(); - state_ = state::completed; } @@ -225,12 +203,40 @@ struct charls_jpegls_decoder final return reader_.frame_info(); } + [[nodiscard]] + size_t check_stride_and_destination_size(const size_t destination_length, size_t stride) const + { + const size_t minimum_stride{calculate_minimum_stride()}; + + if (stride == auto_calculate_stride) + { + stride = minimum_stride; + } + else + { + if (UNLIKELY(stride < minimum_stride)) + throw_jpegls_error(jpegls_errc::invalid_argument_stride); + } + + const size_t not_used_bytes_at_end{stride - minimum_stride}; + const uint32_t height{reader_.frame_info().height}; + const size_t minimum_destination_scan_length{reader_.scan_interleave_mode() == interleave_mode::none + ? (stride * reader_.scan_component_count() * height) - + not_used_bytes_at_end + : (stride * height) - not_used_bytes_at_end}; + + if (UNLIKELY(destination_length < minimum_destination_scan_length)) + throw_jpegls_error(jpegls_errc::invalid_argument_size); + + return stride; + } + [[nodiscard]] size_t calculate_minimum_stride() const noexcept { - const size_t components_in_plane_count{reader_.parameters().interleave_mode == interleave_mode::none + const size_t components_in_plane_count{reader_.scan_interleave_mode() == interleave_mode::none ? 1U - : static_cast(frame_info().component_count)}; + : static_cast(reader_.scan_component_count())}; return components_in_plane_count * frame_info().width * bit_to_byte_count(frame_info().bits_per_sample); } @@ -334,10 +340,10 @@ catch (...) USE_DECL_ANNOTATIONS jpegls_errc CHARLS_API_CALLING_CONVENTION charls_jpegls_decoder_get_near_lossless( - const charls_jpegls_decoder* decoder, const int32_t component, int32_t* near_lossless) noexcept + const charls_jpegls_decoder* decoder, const int32_t component_index, int32_t* near_lossless) noexcept try { - *check_pointer(near_lossless) = check_pointer(decoder)->near_lossless(component); + *check_pointer(near_lossless) = check_pointer(decoder)->get_near_lossless(component_index); return jpegls_errc::success; } catch (...) @@ -347,10 +353,10 @@ catch (...) USE_DECL_ANNOTATIONS jpegls_errc CHARLS_API_CALLING_CONVENTION charls_jpegls_decoder_get_interleave_mode( - const charls_jpegls_decoder* decoder, charls_interleave_mode* interleave_mode) noexcept + const charls_jpegls_decoder* decoder, const int32_t component_index, charls_interleave_mode* interleave_mode) noexcept try { - *check_pointer(interleave_mode) = check_pointer(decoder)->interleave_mode(); + *check_pointer(interleave_mode) = check_pointer(decoder)->get_interleave_mode(component_index); return jpegls_errc::success; } catch (...) diff --git a/src/charls_jpegls_encoder.cpp b/src/charls_jpegls_encoder.cpp index 1c95073d..ba41b69b 100644 --- a/src/charls_jpegls_encoder.cpp +++ b/src/charls_jpegls_encoder.cpp @@ -144,7 +144,7 @@ struct charls_jpegls_encoder final { check_argument(comment.data() || comment.empty()); check_argument(comment.size() <= segment_max_data_size, jpegls_errc::invalid_argument_size); - check_operation(state_ >= state::destination_set && state_ < state::completed); + check_state_can_write(); transition_to_tables_and_miscellaneous_state(); writer_.write_comment_segment(comment); @@ -155,7 +155,7 @@ struct charls_jpegls_encoder final check_argument_range(minimum_application_data_id, maximum_application_data_id, application_data_id); check_argument(application_data.data() || application_data.empty()); check_argument(application_data.size() <= segment_max_data_size, jpegls_errc::invalid_argument_size); - check_operation(state_ >= state::destination_set && state_ < state::completed); + check_state_can_write(); transition_to_tables_and_miscellaneous_state(); writer_.write_application_data_segment(application_data_id, application_data); @@ -167,52 +167,65 @@ struct charls_jpegls_encoder final check_argument_range(minimum_mapping_entry_size, maximum_mapping_entry_size, entry_size); check_argument(table_data.data() || table_data.empty()); check_argument(table_data.size() >= static_cast(entry_size), jpegls_errc::invalid_argument_size); - check_operation(state_ >= state::destination_set && state_ < state::completed); + check_state_can_write(); transition_to_tables_and_miscellaneous_state(); writer_.write_jpegls_preset_parameters_segment(table_id, entry_size, table_data); } - void encode(span source, size_t stride) + void encode(const span source, const size_t stride) + { + encode_components(source, frame_info_.component_count, stride); + } + + void encode_components(span source, const int32_t source_component_count, const size_t stride) { check_argument(source.data() || source.empty()); - check_operation(is_frame_info_configured() && state_ != state::initial); + check_state_can_write(); + check_operation(is_frame_info_configured()); check_interleave_mode_against_component_count(); - stride = check_stride_and_source(source.size(), stride); + const size_t scan_stride{check_stride_and_source_size(source.size(), stride, source_component_count)}; const int32_t maximum_sample_value{calculate_maximum_sample_value(frame_info_.bits_per_sample)}; if (UNLIKELY( !is_valid(user_preset_coding_parameters_, maximum_sample_value, near_lossless_, &preset_coding_parameters_))) throw_jpegls_error(jpegls_errc::invalid_argument_jpegls_pc_parameters); - transition_to_tables_and_miscellaneous_state(); - write_color_transform_segment(); - write_start_of_frame_segment(); - write_jpegls_preset_parameters_segment(maximum_sample_value); + if (encoded_component_count_ == 0) + { + transition_to_tables_and_miscellaneous_state(); + write_color_transform_segment(); + write_start_of_frame_segment(); + write_jpegls_preset_parameters_segment(maximum_sample_value); + } if (interleave_mode_ == interleave_mode::none) { - const size_t byte_count_component{stride * frame_info_.height}; - const int32_t last_component{frame_info_.component_count - 1}; - for (int32_t component{}; component != frame_info_.component_count; ++component) + const size_t byte_count_component{scan_stride * frame_info_.height}; + for (int32_t component{};;) { writer_.write_start_of_scan_segment(1, near_lossless_, interleave_mode_); - encode_scan(source.data(), stride, 1); + encode_scan(source.data(), scan_stride, 1); + + ++component; + if (component == source_component_count) + break; // Synchronize the source stream (encode_scan works on a local copy) - if (component != last_component) - { - source = source.subspan(byte_count_component); - } + source = source.subspan(byte_count_component); } } else { - writer_.write_start_of_scan_segment(frame_info_.component_count, near_lossless_, interleave_mode_); - encode_scan(source.data(), stride, frame_info_.component_count); + writer_.write_start_of_scan_segment(source_component_count, near_lossless_, interleave_mode_); + encode_scan(source.data(), scan_stride, source_component_count); } - write_end_of_image(); + encoded_component_count_ += source_component_count; + if (encoded_component_count_ == frame_info_.component_count) + { + write_end_of_image(); + } } void create_abbreviated_format() @@ -234,6 +247,7 @@ struct charls_jpegls_encoder final writer_.rewind(); state_ = state::destination_set; + encoded_component_count_ = 0; } private: @@ -275,9 +289,9 @@ struct charls_jpegls_encoder final } [[nodiscard]] - size_t check_stride_and_source(const size_t source_size, size_t stride) const + size_t check_stride_and_source_size(const size_t source_size, size_t stride, const int32_t source_component_count) const { - const size_t minimum_stride{calculate_minimum_stride()}; + const size_t minimum_stride{calculate_minimum_stride(source_component_count)}; if (stride == auto_calculate_stride) { stride = minimum_stride; @@ -290,7 +304,7 @@ struct charls_jpegls_encoder final const size_t not_used_bytes_at_end{stride - minimum_stride}; const size_t minimum_source_size{interleave_mode_ == interleave_mode::none - ? (stride * frame_info_.component_count * frame_info_.height) - + ? (stride * source_component_count * frame_info_.height) - not_used_bytes_at_end : (stride * frame_info_.height) - not_used_bytes_at_end}; @@ -301,13 +315,18 @@ struct charls_jpegls_encoder final } [[nodiscard]] - size_t calculate_minimum_stride() const noexcept + size_t calculate_minimum_stride(const int32_t source_component_count) const noexcept { const auto stride{static_cast(frame_info_.width) * bit_to_byte_count(frame_info_.bits_per_sample)}; if (interleave_mode_ == interleave_mode::none) return stride; - return stride * frame_info_.component_count; + return stride * source_component_count; + } + + void check_state_can_write() const + { + check_operation(state_ >= state::destination_set && state_ < state::completed); } void check_interleave_mode_against_component_count() const @@ -385,6 +404,7 @@ struct charls_jpegls_encoder final charls_frame_info frame_info_{}; int32_t near_lossless_{}; + int32_t encoded_component_count_{}; charls::interleave_mode interleave_mode_{}; charls::color_transformation color_transformation_{}; charls::encoding_options encoding_options_{}; @@ -641,6 +661,21 @@ catch (...) } +USE_DECL_ANNOTATIONS jpegls_errc CHARLS_API_CALLING_CONVENTION charls_jpegls_encoder_encode_components_from_buffer( + charls_jpegls_encoder* encoder, const void* source_buffer, const size_t source_size_bytes, + const int32_t source_component_count, const uint32_t stride) noexcept +try +{ + check_pointer(encoder)->encode_components({static_cast(source_buffer), source_size_bytes}, + source_component_count, stride); + return jpegls_errc::success; +} +catch (...) +{ + return to_jpegls_errc(); +} + + USE_DECL_ANNOTATIONS charls_jpegls_errc CHARLS_API_CALLING_CONVENTION charls_jpegls_encoder_create_abbreviated_format(charls_jpegls_encoder* encoder) noexcept try diff --git a/src/constants.hpp b/src/constants.hpp index 20ae2a06..ea6d5f2a 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -13,7 +13,7 @@ constexpr int32_t default_reset_threshold{64}; // Default RESET value as defined constexpr int32_t minimum_component_count{1}; constexpr int32_t maximum_component_count{255}; -constexpr size_t maximum_component_count_in_scan{4}; +constexpr int32_t maximum_component_count_in_scan{4}; constexpr int32_t minimum_component_index{}; constexpr int32_t maximum_component_index{maximum_component_count - 1}; constexpr int32_t minimum_bits_per_sample{2}; diff --git a/src/jpeg_stream_reader.cpp b/src/jpeg_stream_reader.cpp index 915479bf..aa0d97ad 100644 --- a/src/jpeg_stream_reader.cpp +++ b/src/jpeg_stream_reader.cpp @@ -90,7 +90,7 @@ void jpeg_stream_reader::read_header(spiff_header* header, bool* spiff_header_fo if (UNLIKELY(read_next_marker_code() != jpeg_marker_code::start_of_image)) throw_jpegls_error(jpegls_errc::start_of_image_marker_not_found); - scan_infos_.reserve(4); // expect 4 components or fewer. + component_infos_.reserve(4); // expect 4 components or fewer. state_ = state::header_section; } @@ -270,9 +270,21 @@ jpegls_pc_parameters jpeg_stream_reader::get_validated_preset_coding_parameters( } +int32_t jpeg_stream_reader::get_near_lossless(const size_t component_index) const noexcept +{ + return component_infos_[component_index].near_lossless; +} + + +interleave_mode jpeg_stream_reader::get_interleave_mode(const size_t component_index) const noexcept +{ + return component_infos_[component_index].interleave_mode; +} + + int32_t jpeg_stream_reader::get_mapping_table_id(const size_t component_index) const noexcept { - return scan_infos_[component_index].table_id; + return component_infos_[component_index].table_id; } @@ -580,33 +592,40 @@ void jpeg_stream_reader::read_define_restart_interval_segment() void jpeg_stream_reader::read_start_of_scan_segment() { check_minimal_segment_size(1); - const size_t component_count_in_scan{read_uint8()}; // ISO 10918-1, B2.3. defines the limits for the number of image components parameter in an SOS. - if (UNLIKELY(component_count_in_scan == 0U || component_count_in_scan > maximum_component_count_in_scan || - component_count_in_scan > static_cast(frame_info_.component_count))) + const int32_t scan_component_count{read_uint8()}; + if (UNLIKELY(scan_component_count < 1 || scan_component_count > maximum_component_count_in_scan || + scan_component_count > frame_info_.component_count - read_component_count_)) throw_jpegls_error(jpegls_errc::invalid_parameter_component_count); - if (UNLIKELY(component_count_in_scan != 1 && - component_count_in_scan != static_cast(frame_info_.component_count))) - throw_jpegls_error(jpegls_errc::parameter_value_not_supported); + scan_component_count_ = scan_component_count; + read_component_count_ += scan_component_count; + + array component_ids{}; + array mapping_table_ids{}; - check_segment_size((component_count_in_scan * 2) + 4); + check_segment_size((scan_component_count * size_t{2}) + 4); - for (size_t i{}; i != component_count_in_scan; ++i) + for (int32_t i{}; i != scan_component_count; ++i) { - const uint8_t component_id{read_uint8()}; - const uint8_t mapping_table_id{read_uint8()}; - store_mapping_table_id(component_id, mapping_table_id); + component_ids[i] = read_uint8(); + mapping_table_ids[i] = read_uint8(); } parameters_.near_lossless = read_uint8(); // Read NEAR parameter if (UNLIKELY(parameters_.near_lossless > compute_maximum_near_lossless(static_cast(maximum_sample_value())))) throw_jpegls_error(jpegls_errc::invalid_parameter_near_lossless); - const auto mode{static_cast(read_byte())}; // Read ILV parameter - check_interleave_mode(mode); - parameters_.interleave_mode = mode; + scan_interleave_mode_ = static_cast(read_byte()); // Read ILV parameter + check_interleave_mode(scan_interleave_mode_, scan_component_count); + parameters_.interleave_mode = scan_interleave_mode_; + + for (int32_t i{}; i != scan_component_count; ++i) + { + store_component_info(component_ids[i], mapping_table_ids[i], static_cast(parameters_.near_lossless), + scan_interleave_mode_); + } if (UNLIKELY((read_byte() & byte{0xFU}) != byte{})) // Read Ah (no meaning) and Al (point transform). throw_jpegls_error(jpegls_errc::parameter_value_not_supported); @@ -798,20 +817,20 @@ USE_DECL_ANNOTATIONS void jpeg_stream_reader::try_read_spiff_header_segment(spif void jpeg_stream_reader::add_component(const uint8_t component_id) { - if (UNLIKELY(find_if(scan_infos_.cbegin(), scan_infos_.cend(), [component_id](const scan_info& info) noexcept { - return info.component_id == component_id; - }) != scan_infos_.cend())) + if (UNLIKELY(find_if(component_infos_.cbegin(), component_infos_.cend(), + [component_id](const component_info& info) noexcept { return info.id == component_id; }) != + component_infos_.cend())) throw_jpegls_error(jpegls_errc::duplicate_component_id_in_sof_segment); - scan_infos_.push_back({component_id, 0}); + component_infos_.push_back({component_id, 0, 0, interleave_mode::none}); } -void jpeg_stream_reader::check_interleave_mode(const interleave_mode mode) const +void jpeg_stream_reader::check_interleave_mode(const interleave_mode mode, const int32_t scan_component_count) { constexpr auto errc{jpegls_errc::invalid_parameter_interleave_mode}; charls::check_interleave_mode(mode, errc); - if (UNLIKELY(frame_info_.component_count == 1 && mode != interleave_mode::none)) + if (UNLIKELY(scan_component_count == 1 && mode != interleave_mode::none)) throw_jpegls_error(errc); } @@ -929,27 +948,31 @@ void jpeg_stream_reader::extend_mapping_table(const uint8_t table_id, const uint } -void jpeg_stream_reader::store_mapping_table_id(const uint8_t component_id, const uint8_t table_id) +void jpeg_stream_reader::store_component_info(const uint8_t component_id, const uint8_t table_id, + const uint8_t near_lossless, const interleave_mode interleave_mode) { - if (table_id == 0) + // Ignore when info is default, prevent search and ID mismatch issues. + if (table_id == 0 && near_lossless == 0 && interleave_mode == interleave_mode::none) return; // default is already 0, no need to search and update. - const auto it{find_if(scan_infos_.begin(), scan_infos_.end(), - [component_id](const scan_info& info) noexcept { return info.component_id == component_id; })}; - if (it == scan_infos_.end()) + const auto it{find_if(component_infos_.begin(), component_infos_.end(), + [component_id](const component_info& info) noexcept { return info.id == component_id; })}; + if (it == component_infos_.end()) throw_jpegls_error(jpegls_errc::unknown_component_id); + it->near_lossless = near_lossless; it->table_id = table_id; + it->interleave_mode = interleave_mode; } bool jpeg_stream_reader::has_external_mapping_table_ids() const noexcept { - const auto it{find_if(scan_infos_.cbegin(), scan_infos_.cend(), [this](const scan_info& info) noexcept { + const auto it{find_if(component_infos_.cbegin(), component_infos_.cend(), [this](const component_info& info) noexcept { return info.table_id != 0 && find_mapping_table_entry(info.table_id) == mapping_tables_.cend(); })}; - return it != scan_infos_.cend(); + return it != component_infos_.cend(); } diff --git a/src/jpeg_stream_reader.hpp b/src/jpeg_stream_reader.hpp index 1461c19f..405883a1 100644 --- a/src/jpeg_stream_reader.hpp +++ b/src/jpeg_stream_reader.hpp @@ -37,6 +37,12 @@ class jpeg_stream_reader final return frame_info_; } + [[nodiscard]] + charls::frame_info scan_frame_info() const noexcept + { + return {frame_info_.width, frame_info_.height, frame_info_.bits_per_sample, scan_component_count()}; + } + [[nodiscard]] const coding_parameters& parameters() const noexcept { @@ -91,6 +97,12 @@ class jpeg_stream_reader final return compressed_data_format_; } + [[nodiscard]] + int32_t get_near_lossless(size_t component_index) const noexcept; + + [[nodiscard]] + interleave_mode get_interleave_mode(size_t component_index) const noexcept; + [[nodiscard]] int32_t get_mapping_table_id(size_t component_index) const noexcept; @@ -103,7 +115,19 @@ class jpeg_stream_reader final [[nodiscard]] size_t component_count() const noexcept { - return scan_infos_.size(); + return component_infos_.size(); + } + + [[nodiscard]] + int32_t scan_component_count() const noexcept + { + return scan_component_count_; + } + + [[nodiscard]] + interleave_mode scan_interleave_mode() const noexcept + { + return scan_interleave_mode_; } [[nodiscard]] @@ -169,7 +193,7 @@ class jpeg_stream_reader final void try_read_spiff_header_segment(CHARLS_OUT spiff_header& header, CHARLS_OUT bool& spiff_header_found); void try_read_hp_color_transform_segment(); void add_component(uint8_t component_id); - void check_interleave_mode(interleave_mode mode) const; + static void check_interleave_mode(interleave_mode mode, int32_t scan_component_count); [[nodiscard]] uint32_t maximum_sample_value() const noexcept; @@ -183,7 +207,8 @@ class jpeg_stream_reader final void call_application_data_callback(jpeg_marker_code marker_code) const; void add_mapping_table(uint8_t table_id, uint8_t entry_size, span table_data); void extend_mapping_table(uint8_t table_id, uint8_t entry_size, span table_data); - void store_mapping_table_id(uint8_t component_id, uint8_t table_id); + void store_component_info(uint8_t component_id, uint8_t table_id, uint8_t near_lossless, + interleave_mode interleave_mode); [[nodiscard]] bool has_external_mapping_table_ids() const noexcept; @@ -215,10 +240,12 @@ class jpeg_stream_reader final after_end_of_image }; - struct scan_info final + struct component_info final { - uint8_t component_id; + uint8_t id; + uint8_t near_lossless; uint8_t table_id; + charls::interleave_mode interleave_mode; }; class mapping_table_entry final @@ -283,9 +310,12 @@ class jpeg_stream_reader final charls::frame_info frame_info_{}; coding_parameters parameters_{}; jpegls_pc_parameters preset_coding_parameters_{}; - std::vector scan_infos_; + std::vector component_infos_; std::vector mapping_tables_; state state_{}; + int32_t read_component_count_{}; + int32_t scan_component_count_{}; + interleave_mode scan_interleave_mode_{}; bool dnl_marker_expected_{}; charls::compressed_data_format compressed_data_format_{}; callback_function at_comment_callback_{}; diff --git a/test/compliance.cpp b/test/compliance.cpp index 97622e54..18eb3796 100644 --- a/test/compliance.cpp +++ b/test/compliance.cpp @@ -62,7 +62,7 @@ bool verify_encoded_bytes(const void* uncompressed_data, const size_t uncompress jpegls_encoder encoder; encoder.destination(our_encoded_bytes); encoder.frame_info(decoder.frame_info()); - encoder.interleave_mode(decoder.interleave_mode()); + encoder.interleave_mode(decoder.get_interleave_mode()); encoder.near_lossless(decoder.get_near_lossless()); encoder.preset_coding_parameters(decoder.preset_coding_parameters()); std::ignore = encoder.encode(uncompressed_data, uncompressed_length); @@ -143,7 +143,7 @@ void decompress_file(const char* name_encoded, const char* name_raw, const int o fix_endian(&raw_buffer, false); } - if (decoder.interleave_mode() == interleave_mode::none && component_count == 3) + if (decoder.get_interleave_mode() == interleave_mode::none && component_count == 3) { triplet2_planar(raw_buffer, {width, height}); } diff --git a/test/main.cpp b/test/main.cpp index c17e408a..768167c3 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -290,7 +290,7 @@ void test_too_small_output_buffer() error = e.code(); } - assert::is_true(error == jpegls_errc::destination_too_small); + assert::is_true(error == jpegls_errc::invalid_argument_size); } diff --git a/unittest/charls_jpegls_decoder_test.cpp b/unittest/charls_jpegls_decoder_test.cpp index dbb42b47..baca5bcf 100644 --- a/unittest/charls_jpegls_decoder_test.cpp +++ b/unittest/charls_jpegls_decoder_test.cpp @@ -110,11 +110,11 @@ TEST_CLASS(charls_jpegls_decoder_test) TEST_METHOD(get_interleave_mode_nullptr) // NOLINT { interleave_mode interleave_mode; - auto error{charls_jpegls_decoder_get_interleave_mode(nullptr, &interleave_mode)}; + auto error{charls_jpegls_decoder_get_interleave_mode(nullptr, 0, &interleave_mode)}; Assert::AreEqual(jpegls_errc::invalid_argument, error); const auto decoder{get_initialized_decoder()}; - error = charls_jpegls_decoder_get_interleave_mode(decoder.get(), nullptr); + error = charls_jpegls_decoder_get_interleave_mode(decoder.get(), 0, nullptr); Assert::AreEqual(jpegls_errc::invalid_argument, error); } @@ -167,10 +167,10 @@ TEST_CLASS(charls_jpegls_decoder_test) TEST_METHOD(decode_to_zero_size_buffer) // NOLINT { - auto decoder{get_initialized_decoder()}; + const auto decoder{get_initialized_decoder()}; const auto error{charls_jpegls_decoder_decode_to_buffer(decoder.get(), nullptr, 0, 0)}; - Assert::AreEqual(jpegls_errc::destination_too_small, error); + Assert::AreEqual(jpegls_errc::invalid_argument_size, error); } TEST_METHOD(at_comment_nullptr) // NOLINT diff --git a/unittest/compliance_test.cpp b/unittest/compliance_test.cpp index d35e074c..ff7a5a5a 100644 --- a/unittest/compliance_test.cpp +++ b/unittest/compliance_test.cpp @@ -208,7 +208,7 @@ TEST_CLASS(compliance_test) const jpegls_decoder decoder{encoded_source, true}; portable_anymap_file reference_file{ - read_anymap_reference_file(raw_filename, decoder.interleave_mode(), decoder.frame_info())}; + read_anymap_reference_file(raw_filename, decoder.get_interleave_mode(), decoder.frame_info())}; test_compliance(encoded_source, reference_file.image_data(), check_encode); } diff --git a/unittest/documentation_test.cpp b/unittest/documentation_test.cpp index f58a4348..291821c4 100644 --- a/unittest/documentation_test.cpp +++ b/unittest/documentation_test.cpp @@ -147,7 +147,7 @@ TEST_CLASS(documentation_test) Assert::AreEqual(static_cast(reference_file.height()), frame_info.height); Assert::AreEqual(reference_file.component_count(), frame_info.component_count); Assert::AreEqual(reference_file.bits_per_sample(), frame_info.bits_per_sample); - Assert::AreEqual(interleave_mode, decoder.interleave_mode()); + Assert::AreEqual(interleave_mode, decoder.get_interleave_mode()); vector destination(decoder.get_destination_size()); decoder.decode(destination); diff --git a/unittest/encode_test.cpp b/unittest/encode_test.cpp index 08bef137..5b21d5f9 100644 --- a/unittest/encode_test.cpp +++ b/unittest/encode_test.cpp @@ -231,7 +231,162 @@ TEST_CLASS(encode_test) encode({2, 2, 16, 4}, {data.cbegin(), data.cend()}, 61, interleave_mode::sample); } + TEST_METHOD(encode_with_different_lossless_values) // NOLINT + { + jpegls_encoder encoder; + encoder.frame_info({2, 2, 8, 3}); + + constexpr array data{byte{24}, byte{23}, byte{22}, byte{21}}; + + vector encoded_data(encoder.estimated_destination_size()); + encoder.destination(encoded_data); + + encoder.near_lossless(0); + encoder.encode_components(data, 1); + encoder.near_lossless(2); + encoder.encode_components(data, 1); + encoder.near_lossless(10); + encoder.encode_components(data, 1); + + jpegls_decoder decoder(encoded_data, true); + + vector destination(decoder.get_destination_size()); + decoder.decode(destination); + + check_output(data.data(), data.size(), destination.data(), decoder, 3, + static_cast(decoder.frame_info().height) * decoder.frame_info().width); + + Assert::AreEqual(0, decoder.get_near_lossless(0)); + Assert::AreEqual(2, decoder.get_near_lossless(1)); + Assert::AreEqual(10, decoder.get_near_lossless(2)); + } + + TEST_METHOD(encode_with_different_preset_coding_parameters) // NOLINT + { + jpegls_encoder encoder; + encoder.frame_info({2, 2, 8, 3}); + + constexpr array data{byte{24}, byte{23}, byte{22}, byte{21}}; + + vector encoded_data(encoder.estimated_destination_size()); + encoder.destination(encoded_data); + + encoder.preset_coding_parameters({}); + encoder.encode_components(data, 1); + encoder.preset_coding_parameters({25, 10, 20, 22, 64}); + encoder.encode_components(data, 1); + encoder.preset_coding_parameters({25, 0, 0, 0, 3}); + encoder.encode_components(data, 1); + + jpegls_decoder decoder(encoded_data, true); + + vector destination(decoder.get_destination_size()); + decoder.decode(destination); + + check_output(data.data(), data.size(), destination.data(), decoder, 3, + static_cast(decoder.frame_info().height) * decoder.frame_info().width); + } + + TEST_METHOD(encode_with_different_interleave_modes_none_first) // NOLINT + { + jpegls_encoder encoder; + encoder.frame_info({8, 2, 8, 4}); + + constexpr array component0{byte{24}, byte{23}, byte{22}, byte{21}, byte{20}, byte{19}, byte{18}, byte{17}, + byte{16}, byte{15}, byte{14}, byte{13}, byte{12}, byte{11}, byte{10}, byte{9}}; + + constexpr array component_1_and_2_and_3{ + byte{24}, byte{16}, byte{23}, byte{15}, byte{22}, byte{14}, byte{21}, byte{13}, byte{20}, byte{12}, + byte{19}, byte{11}, byte{18}, byte{10}, byte{17}, byte{9}, byte{24}, byte{16}, byte{23}, byte{15}, + byte{22}, byte{14}, byte{21}, byte{13}, byte{20}, byte{12}, byte{19}, byte{11}, byte{18}, byte{10}, + byte{17}, byte{9}, byte{24}, byte{16}, byte{23}, byte{15}, byte{22}, byte{14}, byte{21}, byte{13}, + byte{20}, byte{12}, byte{19}, byte{11}, byte{18}, byte{10}, byte{17}, byte{9}}; + + vector encoded_data(encoder.estimated_destination_size()); + encoder.destination(encoded_data); + + encoder.interleave_mode(interleave_mode::none); + encoder.encode_components(component0, 1); + encoder.interleave_mode(interleave_mode::sample); + encoder.encode_components(component_1_and_2_and_3, 3); + + jpegls_decoder decoder(encoded_data, true); + + vector destination(decoder.get_destination_size()); + decoder.decode(destination); + + check_output(component0.data(), component0.size(), destination.data(), decoder, 1, 8 * 2); + check_output(component_1_and_2_and_3.data(), component_1_and_2_and_3.size(), destination.data() + 8 * 2, decoder, 1, + 8 * 2 * 3); + Assert::AreEqual(interleave_mode::none, decoder.get_interleave_mode(0)); + Assert::AreEqual(interleave_mode::sample, decoder.get_interleave_mode(1)); + Assert::AreEqual(interleave_mode::sample, decoder.get_interleave_mode(2)); + Assert::AreEqual(interleave_mode::sample, decoder.get_interleave_mode(3)); + } + + TEST_METHOD(encode_with_different_interleave_modes_sample_first) // NOLINT + { + jpegls_encoder encoder; + encoder.frame_info({8, 2, 8, 4}); + + constexpr array component_0_and_1_and_2{ + byte{24}, byte{16}, byte{23}, byte{15}, byte{22}, byte{14}, byte{21}, byte{13}, byte{20}, byte{12}, + byte{19}, byte{11}, byte{18}, byte{10}, byte{17}, byte{9}, byte{24}, byte{16}, byte{23}, byte{15}, + byte{22}, byte{14}, byte{21}, byte{13}, byte{20}, byte{12}, byte{19}, byte{11}, byte{18}, byte{10}, + byte{17}, byte{9}, byte{24}, byte{16}, byte{23}, byte{15}, byte{22}, byte{14}, byte{21}, byte{13}, + byte{20}, byte{12}, byte{19}, byte{11}, byte{18}, byte{10}, byte{17}, byte{9}}; + + constexpr array component3{byte{24}, byte{23}, byte{22}, byte{21}, byte{20}, byte{19}, byte{18}, byte{17}, + byte{16}, byte{15}, byte{14}, byte{13}, byte{12}, byte{11}, byte{10}, byte{9}}; + + vector encoded_data(encoder.estimated_destination_size()); + encoder.destination(encoded_data); + + encoder.interleave_mode(interleave_mode::sample); + encoder.encode_components(component_0_and_1_and_2, 3); + encoder.interleave_mode(interleave_mode::none); + encoder.encode_components(component3, 1); + + jpegls_decoder decoder(encoded_data, true); + + vector destination(decoder.get_destination_size()); + decoder.decode(destination); + + check_output(component_0_and_1_and_2.data(), component_0_and_1_and_2.size(), destination.data(), decoder, 1, + 8 * 2 * 3); + check_output(component3.data(), component3.size(), destination.data() + 8 * 2 * 3, decoder, 1, 8 * 2); + Assert::AreEqual(interleave_mode::sample, decoder.get_interleave_mode(0)); + Assert::AreEqual(interleave_mode::sample, decoder.get_interleave_mode(1)); + Assert::AreEqual(interleave_mode::sample, decoder.get_interleave_mode(2)); + Assert::AreEqual(interleave_mode::none, decoder.get_interleave_mode(3)); + } + private: + static void check_output(const byte* source, const size_t source_size, const byte* destination, + const jpegls_decoder& decoder, const int component_count, const size_t component_size) + { + for (int component = 0; component < component_count; ++component) + { + const byte* component_destination = destination + component_size * component; + + if (const int near_lossless = decoder.get_near_lossless(component); near_lossless == 0) + { + for (size_t i{}; i != source_size; ++i) + { + Assert::AreEqual(source[i], component_destination[i]); + } + } + else + { + for (size_t i{}; i != source_size; ++i) + { + Assert::IsTrue(abs(static_cast(source[i]) - static_cast(component_destination[i])) <= + near_lossless); + } + } + } + } + static void encode(const char* filename, const size_t expected_size, const interleave_mode interleave_mode = interleave_mode::none, const color_transformation color_transformation = color_transformation::none) @@ -273,7 +428,7 @@ TEST_CLASS(encode_test) Assert::AreEqual(reference_frame_info.height, frame_info.height); Assert::AreEqual(reference_frame_info.bits_per_sample, frame_info.bits_per_sample); Assert::AreEqual(reference_frame_info.component_count, frame_info.component_count); - Assert::IsTrue(interleave_mode == decoder.interleave_mode()); + Assert::IsTrue(interleave_mode == decoder.get_interleave_mode()); Assert::IsTrue(color_transformation == decoder.color_transformation()); vector destination(decoder.get_destination_size()); diff --git a/unittest/jpeg_stream_reader_test.cpp b/unittest/jpeg_stream_reader_test.cpp index a6a74d93..c11ef49d 100644 --- a/unittest/jpeg_stream_reader_test.cpp +++ b/unittest/jpeg_stream_reader_test.cpp @@ -893,7 +893,7 @@ TEST_CLASS(jpeg_stream_reader_test) jpeg_test_stream_writer writer; writer.write_start_of_image(); writer.write_start_of_frame_segment(1, 0, 2, 3); - writer.write_start_of_scan_segment(0, 3, 0, interleave_mode::none); + writer.write_start_of_scan_segment(0, 1, 0, interleave_mode::none); writer.write_define_number_of_lines(1, 3); writer.write_start_of_scan_segment(1, 1, 0, interleave_mode::none); diff --git a/unittest/jpegls_decoder_test.cpp b/unittest/jpegls_decoder_test.cpp index e6009975..0d01abf1 100644 --- a/unittest/jpegls_decoder_test.cpp +++ b/unittest/jpegls_decoder_test.cpp @@ -130,7 +130,7 @@ TEST_CLASS(jpegls_decoder_test) const vector source(2000); const jpegls_decoder decoder{source, false}; - assert_expect_exception(jpegls_errc::invalid_operation, [&decoder] { ignore = decoder.interleave_mode(); }); + assert_expect_exception(jpegls_errc::invalid_operation, [&decoder] { ignore = decoder.get_interleave_mode(); }); } TEST_METHOD(near_lossless_without_read_header_throws) // NOLINT @@ -257,7 +257,7 @@ TEST_CLASS(jpegls_decoder_test) decoder.decode(destination); portable_anymap_file reference_file{ - read_anymap_reference_file("DataFiles/test8.ppm", decoder.interleave_mode(), decoder.frame_info())}; + read_anymap_reference_file("DataFiles/test8.ppm", decoder.get_interleave_mode(), decoder.frame_info())}; const auto& reference_image_data{reference_file.image_data()}; for (size_t i{}; i != destination.size(); ++i) @@ -277,7 +277,7 @@ TEST_CLASS(jpegls_decoder_test) decoder.decode(destination); portable_anymap_file reference_file{ - read_anymap_reference_file("DataFiles/test8.ppm", decoder.interleave_mode(), decoder.frame_info())}; + read_anymap_reference_file("DataFiles/test8.ppm", decoder.get_interleave_mode(), decoder.frame_info())}; const auto& reference_image_data{reference_file.image_data()}; for (size_t i{}; i != destination.size(); ++i) @@ -286,22 +286,6 @@ TEST_CLASS(jpegls_decoder_test) } } - TEST_METHOD(start_of_scan_with_mixed_interleave_mode_throws) // NOLINT - { - jpeg_test_stream_writer writer; - writer.write_start_of_image(); - writer.write_start_of_frame_segment(1, 1, 8, 3); - writer.write_start_of_scan_segment(0, 1, 0, interleave_mode::none); - writer.write_byte(byte{0x80}); - writer.write_start_of_scan_segment(1, 2, 0, interleave_mode::sample); - - jpegls_decoder decoder(writer.buffer, true); - std::vector destination(decoder.get_destination_size()); - - assert_expect_exception(jpegls_errc::parameter_value_not_supported, - [&decoder, &destination] { decoder.decode(destination); }); - } - TEST_METHOD(decode_with_destination_as_return) // NOLINT { const auto source{read_file("DataFiles/t8c0e0.jls")}; @@ -309,7 +293,7 @@ TEST_CLASS(jpegls_decoder_test) const auto destination{decoder.decode>()}; portable_anymap_file reference_file{ - read_anymap_reference_file("DataFiles/test8.ppm", decoder.interleave_mode(), decoder.frame_info())}; + read_anymap_reference_file("DataFiles/test8.ppm", decoder.get_interleave_mode(), decoder.frame_info())}; const auto& reference_image_data{reference_file.image_data()}; for (size_t i{}; i != destination.size(); ++i) @@ -325,7 +309,7 @@ TEST_CLASS(jpegls_decoder_test) const auto destination{decoder.decode>()}; portable_anymap_file reference_file{ - read_anymap_reference_file("DataFiles/test8.ppm", decoder.interleave_mode(), decoder.frame_info())}; + read_anymap_reference_file("DataFiles/test8.ppm", decoder.get_interleave_mode(), decoder.frame_info())}; const auto& reference_image_data{reference_file.image_data()}; const auto* destination_as_bytes{reinterpret_cast(destination.data())}; @@ -355,12 +339,12 @@ TEST_CLASS(jpegls_decoder_test) TEST_METHOD(decode_color_interleave_none_custom_stride_with_too_small_buffer_throws) // NOLINT { - decode_image_with_too_small_buffer_throws("DataFiles/t8c0e0.jls", 256 + 1); + decode_image_with_too_small_buffer_throws("DataFiles/t8c0e0.jls", 256 + 1, 1 + 1); } TEST_METHOD(decode_color_interleave_sample_custom_stride_with_too_small_buffer_throws) // NOLINT { - decode_image_with_too_small_buffer_throws("DataFiles/t8c2e0.jls", 256 * 3 + 1); + decode_image_with_too_small_buffer_throws("DataFiles/t8c2e0.jls", 256 * 3 + 1, 1 + 1); } TEST_METHOD(decode_color_interleave_none_with_too_small_stride_throws) // NOLINT @@ -398,7 +382,7 @@ TEST_CLASS(jpegls_decoder_test) const uint32_t standard_stride{decoder.frame_info().width}; decoder.decode(destination, standard_stride); - verify_decoded_bytes(decoder.interleave_mode(), decoder.frame_info(), destination, standard_stride, + verify_decoded_bytes(decoder.get_interleave_mode(), decoder.frame_info(), destination, standard_stride, "DataFiles/test8.ppm"); } @@ -411,7 +395,7 @@ TEST_CLASS(jpegls_decoder_test) const uint32_t standard_stride{decoder.frame_info().width * 3}; decoder.decode(destination, standard_stride); - verify_decoded_bytes(decoder.interleave_mode(), decoder.frame_info(), destination, standard_stride, + verify_decoded_bytes(decoder.get_interleave_mode(), decoder.frame_info(), destination, standard_stride, "DataFiles/test8.ppm"); } @@ -424,7 +408,7 @@ TEST_CLASS(jpegls_decoder_test) vector destination(decoder.get_destination_size(custom_stride)); decoder.decode(destination, custom_stride); - verify_decoded_bytes(decoder.interleave_mode(), decoder.frame_info(), destination, custom_stride, + verify_decoded_bytes(decoder.get_interleave_mode(), decoder.frame_info(), destination, custom_stride, "DataFiles/test8.ppm"); } @@ -437,7 +421,7 @@ TEST_CLASS(jpegls_decoder_test) vector destination(decoder.get_destination_size(custom_stride)); decoder.decode(destination, custom_stride); - verify_decoded_bytes(decoder.interleave_mode(), decoder.frame_info(), destination, custom_stride, + verify_decoded_bytes(decoder.get_interleave_mode(), decoder.frame_info(), destination, custom_stride, "DataFiles/test8.ppm"); } @@ -724,7 +708,7 @@ TEST_CLASS(jpegls_decoder_test) const jpegls_decoder decoder{source, true}; portable_anymap_file reference_file{ - read_anymap_reference_file("DataFiles/test8.ppm", decoder.interleave_mode(), decoder.frame_info())}; + read_anymap_reference_file("DataFiles/test8.ppm", decoder.get_interleave_mode(), decoder.frame_info())}; test_compliance(source, reference_file.image_data(), false); } @@ -1395,14 +1379,14 @@ TEST_CLASS(jpegls_decoder_test) assert_expect_exception(jpegls_errc::invalid_marker_segment_size, [&decoder] { decoder.read_header(); }); } - static void decode_image_with_too_small_buffer_throws(const char* image_filename, const uint32_t stride = 0) + static void decode_image_with_too_small_buffer_throws(const char* image_filename, const uint32_t stride = 0, const uint32_t too_small_byte_count = 1) { const auto source{read_file(image_filename)}; jpegls_decoder decoder{source, true}; - vector destination(decoder.get_destination_size(stride) - 1); + vector destination(decoder.get_destination_size(stride) - too_small_byte_count); - assert_expect_exception(jpegls_errc::destination_too_small, + assert_expect_exception(jpegls_errc::invalid_argument_size, [&decoder, &destination, &stride] { decoder.decode(destination, stride); }); } }; diff --git a/unittest/jpegls_encoder_test.cpp b/unittest/jpegls_encoder_test.cpp index b3950498..48dcb0bd 100644 --- a/unittest/jpegls_encoder_test.cpp +++ b/unittest/jpegls_encoder_test.cpp @@ -1892,7 +1892,7 @@ TEST_CLASS(jpegls_encoder_test) Assert::AreEqual(source_frame_info.height, frame_info.height); Assert::AreEqual(source_frame_info.bits_per_sample, frame_info.bits_per_sample); Assert::AreEqual(source_frame_info.component_count, frame_info.component_count); - Assert::IsTrue(interleave_mode == decoder.interleave_mode()); + Assert::IsTrue(interleave_mode == decoder.get_interleave_mode()); Assert::IsTrue(color_transformation == decoder.color_transformation()); vector destination(decoder.get_destination_size()); diff --git a/unittest/util.cpp b/unittest/util.cpp index e5a3fe22..177676b9 100644 --- a/unittest/util.cpp +++ b/unittest/util.cpp @@ -182,7 +182,7 @@ bool verify_encoded_bytes(const vector& uncompressed_source, const vector< jpegls_encoder encoder; encoder.frame_info(decoder.frame_info()) - .interleave_mode(decoder.interleave_mode()) + .interleave_mode(decoder.get_interleave_mode()) .near_lossless(decoder.get_near_lossless()) .preset_coding_parameters(decoder.preset_coding_parameters());