Skip to content

Commit

Permalink
Extend API of jpeg_encoder with encode_components methods
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
vbaderks committed Sep 10, 2024
1 parent fae5060 commit 390cb99
Show file tree
Hide file tree
Showing 21 changed files with 444 additions and 162 deletions.
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
7 changes: 4 additions & 3 deletions include/charls/charls_jpegls_decoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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));

Expand Down
20 changes: 20 additions & 0 deletions include/charls/charls_jpegls_encoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
9 changes: 5 additions & 4 deletions include/charls/jpegls_decoder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -258,13 +258,14 @@ class jpegls_decoder final
/// <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;
}

Expand Down
28 changes: 28 additions & 0 deletions include/charls/jpegls_encoder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

/// <summary>
/// Encodes the passed STL like container with the source image data to the destination.
/// </summary>
Expand All @@ -343,6 +351,26 @@ class jpegls_encoder final
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.
Expand Down
94 changes: 50 additions & 44 deletions src/charls_jpegls_decoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
Expand All @@ -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)};
Expand Down Expand Up @@ -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;
}

Expand All @@ -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<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);
}

Expand Down Expand Up @@ -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 (...)
Expand All @@ -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 (...)
Expand Down
Loading

0 comments on commit 390cb99

Please sign in to comment.