mirror of
https://github.com/team-charls/charls
synced 2025-03-28 21:03:13 +00:00
Extend API of jpeg_encoder with encode_components methods (#332)
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.
This commit is contained in:
parent
fae5060b39
commit
8421eef930
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
/// </remarks>
|
||||
/// <param name="decoder">Reference to the decoder instance.</param>
|
||||
/// <param name="component">The component index for which the NEAR parameter should be retrieved.</param>
|
||||
/// <param name="component_index">The component index for which the NEAR parameter should be retrieved.</param>
|
||||
/// <param name="near_lossless">Reference that will hold the value of the NEAR parameter.</param>
|
||||
/// <returns>The result of the operation: success or a failure code.</returns>
|
||||
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));
|
||||
|
||||
/// <summary>
|
||||
@ -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.
|
||||
/// </remarks>
|
||||
/// <param name="decoder">Reference to the decoder instance.</param>
|
||||
/// <param name="component_index">The component index for which the interleave mode should be retrieved.</param>
|
||||
/// <param name="interleave_mode">Reference that will hold the value of the interleave mode.</param>
|
||||
/// <returns>The result of the operation: success or a failure code.</returns>
|
||||
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));
|
||||
|
||||
|
@ -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));
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="encoder">Reference to the encoder instance.</param>
|
||||
/// <param name="source_buffer">Byte array that holds the image data that needs to be encoded.</param>
|
||||
/// <param name="source_size_bytes">Length of the array in bytes.</param>
|
||||
/// <param name="source_component_count">The number of components present in the input source.</param>
|
||||
/// <param name="stride">
|
||||
/// 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.
|
||||
/// </param>
|
||||
/// <returns>The result of the operation: success or a failure code.</returns>
|
||||
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));
|
||||
|
||||
/// <summary>
|
||||
/// Creates a JPEG-LS stream in the abbreviated format that only contain mapping tables (See JPEG-LS standard, C.4).
|
||||
|
@ -48,7 +48,7 @@ public:
|
||||
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 @@ public:
|
||||
/// <summary>
|
||||
/// Returns the interleave mode that was used to encode the scan.
|
||||
/// </summary>
|
||||
/// <param name="component_index">The component index for which the interleave mode should be retrieved.</param>
|
||||
/// <exception cref="charls::jpegls_error">An error occurred during the operation.</exception>
|
||||
/// <returns>The value of the interleave mode.</returns>
|
||||
[[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;
|
||||
}
|
||||
|
||||
|
@ -328,6 +328,14 @@ public:
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes the passed STL like container with the source image data to the destination.
|
||||
/// </summary>
|
||||
@ -343,6 +351,26 @@ public:
|
||||
return encode(source_container.data(), source_container.size() * sizeof(ContainerValueType), stride);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="source_container">Container that holds the image data that needs to be encoded.</param>
|
||||
/// <param name="source_component_count">The number of components present in the input source.</param>
|
||||
/// <param name="stride">
|
||||
/// 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.
|
||||
/// </param>
|
||||
/// <returns>The number of bytes written to the destination.</returns>
|
||||
template<typename Container, typename ContainerValueType = typename Container::value_type>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
|
@ -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<size_t>(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<byte> destination, size_t stride)
|
||||
void decode(span<byte> 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<scan_decoder>(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<scan_decoder>(
|
||||
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 @@ private:
|
||||
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<size_t>(frame_info().component_count)};
|
||||
: static_cast<size_t>(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 (...)
|
||||
|
@ -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<size_t>(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<const byte> source, size_t stride)
|
||||
void encode(const span<const byte> source, const size_t stride)
|
||||
{
|
||||
encode_components(source, frame_info_.component_count, stride);
|
||||
}
|
||||
|
||||
void encode_components(span<const byte> 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 @@ private:
|
||||
}
|
||||
|
||||
[[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 @@ private:
|
||||
|
||||
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 @@ private:
|
||||
}
|
||||
|
||||
[[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<size_t>(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 @@ private:
|
||||
|
||||
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<const byte*>(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
|
||||
|
@ -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};
|
||||
|
@ -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<size_t>(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<size_t>(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;
|
||||
|
||||
check_segment_size((component_count_in_scan * 2) + 4);
|
||||
array<uint8_t, maximum_component_count_in_scan> component_ids{};
|
||||
array<uint8_t, maximum_component_count_in_scan> mapping_table_ids{};
|
||||
|
||||
for (size_t i{}; i != component_count_in_scan; ++i)
|
||||
check_segment_size((scan_component_count * size_t{2}) + 4);
|
||||
|
||||
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<int>(maximum_sample_value()))))
|
||||
throw_jpegls_error(jpegls_errc::invalid_parameter_near_lossless);
|
||||
|
||||
const auto mode{static_cast<interleave_mode>(read_byte())}; // Read ILV parameter
|
||||
check_interleave_mode(mode);
|
||||
parameters_.interleave_mode = mode;
|
||||
scan_interleave_mode_ = static_cast<interleave_mode>(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<uint8_t>(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();
|
||||
}
|
||||
|
||||
|
||||
|
@ -37,6 +37,12 @@ public:
|
||||
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 @@ public:
|
||||
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 @@ public:
|
||||
[[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 @@ private:
|
||||
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 @@ private:
|
||||
void call_application_data_callback(jpeg_marker_code marker_code) const;
|
||||
void add_mapping_table(uint8_t table_id, uint8_t entry_size, span<const std::byte> table_data);
|
||||
void extend_mapping_table(uint8_t table_id, uint8_t entry_size, span<const std::byte> 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 @@ private:
|
||||
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 @@ private:
|
||||
charls::frame_info frame_info_{};
|
||||
coding_parameters parameters_{};
|
||||
jpegls_pc_parameters preset_coding_parameters_{};
|
||||
std::vector<scan_info> scan_infos_;
|
||||
std::vector<component_info> component_infos_;
|
||||
std::vector<mapping_table_entry> 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_handler> at_comment_callback_{};
|
||||
|
@ -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});
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -110,11 +110,11 @@ public:
|
||||
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 @@ public:
|
||||
|
||||
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
|
||||
|
@ -208,7 +208,7 @@ private:
|
||||
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);
|
||||
}
|
||||
|
@ -147,7 +147,7 @@ private:
|
||||
Assert::AreEqual(static_cast<uint32_t>(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<std::byte> destination(decoder.get_destination_size());
|
||||
decoder.decode(destination);
|
||||
|
@ -231,7 +231,162 @@ public:
|
||||
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<byte> 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<byte> destination(decoder.get_destination_size());
|
||||
decoder.decode(destination);
|
||||
|
||||
check_output(data.data(), data.size(), destination.data(), decoder, 3,
|
||||
static_cast<size_t>(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<byte> 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<byte> destination(decoder.get_destination_size());
|
||||
decoder.decode(destination);
|
||||
|
||||
check_output(data.data(), data.size(), destination.data(), decoder, 3,
|
||||
static_cast<size_t>(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<byte> 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<byte> 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<byte> 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<byte> 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<uint8_t>(source[i]) - static_cast<uint8_t>(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 @@ private:
|
||||
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<byte> destination(decoder.get_destination_size());
|
||||
|
@ -893,7 +893,7 @@ public:
|
||||
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);
|
||||
|
||||
|
@ -130,7 +130,7 @@ public:
|
||||
const vector<byte> 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 @@ public:
|
||||
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 @@ public:
|
||||
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 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
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<byte> 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 @@ public:
|
||||
const auto destination{decoder.decode<vector<byte>>()};
|
||||
|
||||
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 @@ public:
|
||||
const auto destination{decoder.decode<vector<uint16_t>>()};
|
||||
|
||||
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<const byte*>(destination.data())};
|
||||
@ -355,12 +339,12 @@ public:
|
||||
|
||||
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 @@ public:
|
||||
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 @@ public:
|
||||
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 @@ public:
|
||||
vector<byte> 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 @@ public:
|
||||
vector<byte> 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 @@ public:
|
||||
|
||||
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 @@ private:
|
||||
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<byte> destination(decoder.get_destination_size(stride) - 1);
|
||||
vector<byte> 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); });
|
||||
}
|
||||
};
|
||||
|
@ -1892,7 +1892,7 @@ private:
|
||||
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<byte> destination(decoder.get_destination_size());
|
||||
|
@ -182,7 +182,7 @@ bool verify_encoded_bytes(const vector<byte>& 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());
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user