Skip to content

Commit

Permalink
Add support to retrieve height from a DNL marker segment.
Browse files Browse the repository at this point in the history
DNL markers are part of the official JPEG-LS standard (but almost never used). Add support to scan the buffer for this
marker segment if the height is zero.
By design CharLS itself will never write DNL marker segment as it always knows the height of the image before it writes the SOF marker segment.
  • Loading branch information
vbaderks committed Sep 3, 2024
1 parent e7b2d6f commit c4dff49
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 36 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
### Added

- Support to encode and decode mapping tables.
- Support to retrieve the height from a DNL marker segment.

### 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 public charls.h header has been split into charls.h (C users) and charls.hpp (C++ users).
- BREAKING: The public charls.h header has been split into charls.h (C applications) and charls.hpp (C++ applications).

### Removed

Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,12 @@ According to preliminary test results published on <https://imagecompression.inf
The following JPEG-LS options are not supported by the CharLS implementation. Most of these options are rarely used in practice.

* No support to encode JPEG restart markers
Decoding is supported, but no recovery mechanisme is implemented for corrupted JPEG-LS files.
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 to encode\decode images with the height defined after the first scan (DNL marker).
* No support for point transform.
Point transform is a lossly encoding mechanism and not used in lossless scenarios.

Expand Down
42 changes: 27 additions & 15 deletions include/charls/public_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ enum charls_jpegls_errc
CHARLS_JPEGLS_ERRC_START_OF_IMAGE_MARKER_NOT_FOUND = 11,
CHARLS_JPEGLS_ERRC_INVALID_SPIFF_HEADER = 12,
CHARLS_JPEGLS_ERRC_UNKNOWN_JPEG_MARKER_FOUND = 13,
CHARLS_JPEGLS_ERRC_UNEXPECTED_MARKER_FOUND = 14,
CHARLS_JPEGLS_ERRC_UNEXPECTED_START_OF_SCAN_MARKER = 14,
CHARLS_JPEGLS_ERRC_INVALID_MARKER_SEGMENT_SIZE = 15,
CHARLS_JPEGLS_ERRC_DUPLICATE_START_OF_IMAGE_MARKER = 16,
CHARLS_JPEGLS_ERRC_DUPLICATE_START_OF_FRAME_MARKER = 17,
Expand All @@ -55,18 +55,20 @@ enum charls_jpegls_errc
CHARLS_JPEGLS_ERRC_UNEXPECTED_RESTART_MARKER = 22,
CHARLS_JPEGLS_ERRC_RESTART_MARKER_NOT_FOUND = 23,
CHARLS_JPEGLS_ERRC_END_OF_IMAGE_MARKER_NOT_FOUND = 24,
CHARLS_JPEGLS_ERRC_UNKNOWN_COMPONENT_ID = 25,
CHARLS_JPEGLS_ERRC_ABBREVIATED_FORMAT_AND_SPIFF_HEADER_MISMATCH = 26,
CHARLS_JPEGLS_ERRC_INVALID_PARAMETER_WIDTH = 27,
CHARLS_JPEGLS_ERRC_INVALID_PARAMETER_HEIGHT = 28,
CHARLS_JPEGLS_ERRC_INVALID_PARAMETER_BITS_PER_SAMPLE = 29,
CHARLS_JPEGLS_ERRC_INVALID_PARAMETER_COMPONENT_COUNT = 30,
CHARLS_JPEGLS_ERRC_INVALID_PARAMETER_INTERLEAVE_MODE = 31,
CHARLS_JPEGLS_ERRC_INVALID_PARAMETER_NEAR_LOSSLESS = 32,
CHARLS_JPEGLS_ERRC_INVALID_PARAMETER_JPEGLS_PRESET_PARAMETERS = 33,
CHARLS_JPEGLS_ERRC_INVALID_PARAMETER_COLOR_TRANSFORMATION = 34,
CHARLS_JPEGLS_ERRC_INVALID_PARAMETER_MAPPING_TABLE_ID = 35,
CHARLS_JPEGLS_ERRC_INVALID_PARAMETER_MAPPING_TABLE_CONTINUATION = 36,
CHARLS_JPEGLS_ERRC_UNEXPECTED_DEFINE_NUMBER_OF_LINES_MARKER = 25,
CHARLS_JPEGLS_ERRC_DEFINE_NUMBER_OF_LINES_MARKER_NOT_FOUND = 26,
CHARLS_JPEGLS_ERRC_UNKNOWN_COMPONENT_ID = 27,
CHARLS_JPEGLS_ERRC_ABBREVIATED_FORMAT_AND_SPIFF_HEADER_MISMATCH = 28,
CHARLS_JPEGLS_ERRC_INVALID_PARAMETER_WIDTH = 29,
CHARLS_JPEGLS_ERRC_INVALID_PARAMETER_HEIGHT = 30,
CHARLS_JPEGLS_ERRC_INVALID_PARAMETER_BITS_PER_SAMPLE = 31,
CHARLS_JPEGLS_ERRC_INVALID_PARAMETER_COMPONENT_COUNT = 32,
CHARLS_JPEGLS_ERRC_INVALID_PARAMETER_INTERLEAVE_MODE = 33,
CHARLS_JPEGLS_ERRC_INVALID_PARAMETER_NEAR_LOSSLESS = 34,
CHARLS_JPEGLS_ERRC_INVALID_PARAMETER_JPEGLS_PRESET_PARAMETERS = 35,
CHARLS_JPEGLS_ERRC_INVALID_PARAMETER_COLOR_TRANSFORMATION = 36,
CHARLS_JPEGLS_ERRC_INVALID_PARAMETER_MAPPING_TABLE_ID = 37,
CHARLS_JPEGLS_ERRC_INVALID_PARAMETER_MAPPING_TABLE_CONTINUATION = 38,

// Logic errors:

Expand Down Expand Up @@ -270,9 +272,9 @@ enum class [[nodiscard]] jpegls_errc
unknown_jpeg_marker_found = impl::CHARLS_JPEGLS_ERRC_UNKNOWN_JPEG_MARKER_FOUND,

/// <summary>
/// This error is returned when a JPEG marker is found that is not valid for the current state.
/// This error is returned when the stream contains an unexpected SOS marker.
/// </summary>
unexpected_marker_found = impl::CHARLS_JPEGLS_ERRC_UNEXPECTED_MARKER_FOUND,
unexpected_start_of_scan_marker = impl::CHARLS_JPEGLS_ERRC_UNEXPECTED_START_OF_SCAN_MARKER,

/// <summary>
/// This error is returned when the segment size of a marker segment is invalid.
Expand Down Expand Up @@ -325,6 +327,16 @@ enum class [[nodiscard]] jpegls_errc
/// </summary>
end_of_image_marker_not_found = impl::CHARLS_JPEGLS_ERRC_END_OF_IMAGE_MARKER_NOT_FOUND,

/// <summary>
/// This error is returned when the stream contains an unexpected DefineNumberOfLines (DNL) marker.
/// </summary>
unexpected_define_number_of_lines_marker = impl::CHARLS_JPEGLS_ERRC_UNEXPECTED_DEFINE_NUMBER_OF_LINES_MARKER,

/// <summary>
/// This error is returned when the DefineNumberOfLines (DNL) marker could not be found.
/// </summary>
define_number_of_lines_marker_not_found = impl::CHARLS_JPEGLS_ERRC_DEFINE_NUMBER_OF_LINES_MARKER_NOT_FOUND,

/// <summary>
/// This error is returned when an unknown component ID in a scan is detected.
/// </summary>
Expand Down
71 changes: 58 additions & 13 deletions src/jpeg_stream_reader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ void jpeg_stream_reader::read_header(spiff_header* header, bool* spiff_header_fo

if (state_ == state::bit_stream_section)
{
if (frame_info_.height == 0)
{
find_and_read_define_number_of_lines_segment();
}

check_width();
check_coding_parameters();
return;
}
Expand Down Expand Up @@ -197,7 +203,7 @@ void jpeg_stream_reader::validate_marker_code(const jpeg_marker_code marker_code
{
case jpeg_marker_code::start_of_scan:
if (UNLIKELY(state_ != state::scan_section))
throw_jpegls_error(jpegls_errc::unexpected_marker_found);
throw_jpegls_error(jpegls_errc::unexpected_start_of_scan_marker);

return;

Expand Down Expand Up @@ -228,8 +234,11 @@ void jpeg_stream_reader::validate_marker_code(const jpeg_marker_code marker_code
case jpeg_marker_code::application_data15:
return;

case jpeg_marker_code::define_number_of_lines: // DLN is a JPEG-LS valid marker, but not supported: handle as unknown.
throw_jpegls_error(jpegls_errc::unknown_jpeg_marker_found);
case jpeg_marker_code::define_number_of_lines:
if (!dnl_marker_expected_)
throw_jpegls_error(jpegls_errc::unexpected_define_number_of_lines_marker);

break;

case jpeg_marker_code::start_of_image:
throw_jpegls_error(jpegls_errc::duplicate_start_of_image_marker);
Expand Down Expand Up @@ -301,7 +310,6 @@ void jpeg_stream_reader::read_marker_segment(const jpeg_marker_code marker_code,
break;

case jpeg_marker_code::start_of_scan:
check_height_and_width();
read_start_of_scan_segment();
break;

Expand All @@ -313,6 +321,11 @@ void jpeg_stream_reader::read_marker_segment(const jpeg_marker_code marker_code,
read_define_restart_interval_segment();
break;

case jpeg_marker_code::define_number_of_lines:
read_define_number_of_lines_segment();
dnl_marker_expected_ = false;
break;

case jpeg_marker_code::application_data8:
try_read_application_data8_segment(header, spiff_header_found);
break;
Expand Down Expand Up @@ -372,7 +385,7 @@ void jpeg_stream_reader::read_start_of_frame_segment()
frame_info_.bits_per_sample > maximum_bits_per_sample))
throw_jpegls_error(jpegls_errc::invalid_parameter_bits_per_sample);

frame_info_height(read_uint16());
frame_info_height(read_uint16(), false);
frame_info_width(read_uint16());

frame_info_.component_count = read_uint8();
Expand Down Expand Up @@ -414,6 +427,13 @@ void jpeg_stream_reader::read_application_data_segment(const jpeg_marker_code ma
}


void jpeg_stream_reader::read_define_number_of_lines_segment()
{
check_segment_size(2);
frame_info_height(read_uint16(), true);
}


void jpeg_stream_reader::read_preset_parameters_segment()
{
check_minimal_segment_size(1);
Expand Down Expand Up @@ -490,7 +510,7 @@ void jpeg_stream_reader::read_oversize_image_dimension()
throw_jpegls_error(jpegls_errc::invalid_parameter_jpegls_preset_parameters);
}

frame_info_height(height);
frame_info_height(height, false);
frame_info_width(width);
}

Expand Down Expand Up @@ -798,11 +818,8 @@ void jpeg_stream_reader::skip_remaining_segment_data() noexcept
}


void jpeg_stream_reader::check_height_and_width() const
void jpeg_stream_reader::check_width() const
{
if (UNLIKELY(frame_info_.height < 1))
throw_jpegls_error(jpegls_errc::parameter_value_not_supported);

if (UNLIKELY(frame_info_.width < 1))
throw_jpegls_error(jpegls_errc::invalid_parameter_width);
}
Expand All @@ -815,12 +832,12 @@ void jpeg_stream_reader::check_coding_parameters() const
}


void jpeg_stream_reader::frame_info_height(const uint32_t height)
void jpeg_stream_reader::frame_info_height(const uint32_t height, const bool final_update)
{
if (height == 0)
if (height == 0 && !final_update)
return;

if (UNLIKELY(frame_info_.height != 0))
if (UNLIKELY(frame_info_.height != 0 || height == 0))
throw_jpegls_error(jpegls_errc::invalid_parameter_height);

frame_info_.height = height;
Expand Down Expand Up @@ -849,6 +866,34 @@ void jpeg_stream_reader::call_application_data_callback(const jpeg_marker_code m
}


void jpeg_stream_reader::find_and_read_define_number_of_lines_segment()
{
for (auto position{position_}; position < end_position_ - 1; ++position)
{
if (*position != jpeg_marker_start_byte)
continue;

const byte optional_marker_code{*(position + 1)};
if (optional_marker_code < byte{128} || optional_marker_code == jpeg_marker_start_byte)
continue;

// Found a marker, ISO / IEC 10918 - 1 B .2.5 requires that if DNL is used it must be at the end of the first scan.
if (static_cast<jpeg_marker_code>(optional_marker_code) != jpeg_marker_code::define_number_of_lines)
break;

const auto current_position{position_};
position_ = position + 2;
read_segment_size();
read_define_number_of_lines_segment();
dnl_marker_expected_ = true;
position_ = current_position;
return;
}

throw_jpegls_error(jpegls_errc::define_number_of_lines_marker_not_found);
}


void jpeg_stream_reader::add_mapping_table(const uint8_t table_id, const uint8_t entry_size,
const span<const byte> table_data)
{
Expand Down
7 changes: 5 additions & 2 deletions src/jpeg_stream_reader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ class jpeg_stream_reader final
void read_start_of_scan_segment();
void read_comment_segment();
void read_application_data_segment(jpeg_marker_code marker_code);
void read_define_number_of_lines_segment();
void read_preset_parameters_segment();
void read_preset_coding_parameters();
void read_oversize_image_dimension();
Expand All @@ -173,10 +174,11 @@ class jpeg_stream_reader final
[[nodiscard]]
uint32_t maximum_sample_value() const noexcept;

void find_and_read_define_number_of_lines_segment();
void skip_remaining_segment_data() noexcept;
void check_height_and_width() const;
void check_width() const;
void check_coding_parameters() const;
void frame_info_height(uint32_t height);
void frame_info_height(uint32_t height, bool final_update);
void frame_info_width(uint32_t width);
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);
Expand Down Expand Up @@ -284,6 +286,7 @@ class jpeg_stream_reader final
std::vector<scan_info> scan_infos_;
std::vector<mapping_table_entry> mapping_tables_;
state state_{};
bool dnl_marker_expected_{};
charls::compressed_data_format compressed_data_format_{};
callback_function<at_comment_handler> at_comment_callback_{};
callback_function<at_application_data_handler> at_application_data_callback_{};
Expand Down
10 changes: 8 additions & 2 deletions src/jpegls_error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ const char* CHARLS_API_CALLING_CONVENTION charls_get_error_message(const charls_
case jpegls_errc::unknown_jpeg_marker_found:
return "Invalid JPEG-LS stream: an unknown JPEG marker code was found";

case jpegls_errc::unexpected_marker_found:
return "Invalid JPEG-LS stream: unexpected marker found";
case jpegls_errc::unexpected_start_of_scan_marker:
return "Invalid JPEG-LS stream: unexpected Start Of Scan (SOS) marker found";

case jpegls_errc::invalid_marker_segment_size:
return "Invalid JPEG-LS stream: segment size of a marker segment is invalid";
Expand Down Expand Up @@ -117,6 +117,12 @@ const char* CHARLS_API_CALLING_CONVENTION charls_get_error_message(const charls_
case jpegls_errc::end_of_image_marker_not_found:
return "Invalid JPEG-LS stream: missing End Of Image (EOI) marker";

case jpegls_errc::unexpected_define_number_of_lines_marker:
return "Invalid JPEG-LS stream: unexpected Define Number of Lines (DNL) marker";

case jpegls_errc::define_number_of_lines_marker_not_found:
return "Invalid JPEG-LS stream: missing expected Define Number of Lines (DNL) marker";

case jpegls_errc::unknown_component_id:
return "Invalid JPEG-LS stream: unknown component ID in scan segment";

Expand Down
Loading

0 comments on commit c4dff49

Please sign in to comment.