satdump/plugins/firstparty_support/fcidecomp/charls/decoder_strategy.h
2025-08-12 13:42:31 +02:00

293 lines
8.3 KiB
C++

// Copyright (c) Team CharLS.
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include "jpegls_error.h"
#include "jpeg_marker_code.h"
#include "process_line.h"
#include "util.h"
#include <cassert>
#include <memory>
namespace charls {
// Purpose: Implements encoding to stream of bits. In encoding mode jls_codec inherits from decoder_strategy
class decoder_strategy
{
public:
decoder_strategy(const frame_info& frame, const coding_parameters& parameters) noexcept :
frame_info_{frame}, parameters_{parameters}
{
}
virtual ~decoder_strategy() = default;
decoder_strategy(const decoder_strategy&) = delete;
decoder_strategy(decoder_strategy&&) = delete;
decoder_strategy& operator=(const decoder_strategy&) = delete;
decoder_strategy& operator=(decoder_strategy&&) = delete;
virtual std::unique_ptr<process_line> create_process_line(byte_span destination, size_t stride) = 0;
virtual void set_presets(const jpegls_pc_parameters& preset_coding_parameters, uint32_t restart_interval) = 0;
virtual size_t decode_scan(std::unique_ptr<process_line> output_data, const JlsRect& size, const_byte_span encoded_source) = 0;
void initialize(const const_byte_span source)
{
position_ = source.data();
end_position_ = source.end();
find_jpeg_marker_start_byte();
fill_read_cache();
}
void reset()
{
valid_bits_ = 0;
read_cache_ = 0;
find_jpeg_marker_start_byte();
fill_read_cache();
}
FORCE_INLINE void skip(const int32_t length) noexcept
{
ASSERT(length);
valid_bits_ -= length; // Note: valid_bits_ may become negative to indicate that extra bits are needed.
read_cache_ = read_cache_ << length;
}
void on_line_end(const void* source, const size_t pixel_count, const size_t pixel_stride) const
{
process_line_->new_line_decoded(source, pixel_count, pixel_stride);
}
void end_scan()
{
if (UNLIKELY(position_ >= end_position_))
impl::throw_jpegls_error(jpegls_errc::source_buffer_too_small);
if (*position_ != jpeg_marker_start_byte)
{
read_bit();
if (UNLIKELY(*position_ != jpeg_marker_start_byte))
impl::throw_jpegls_error(jpegls_errc::too_much_encoded_data);
}
if (UNLIKELY(read_cache_ != 0))
impl::throw_jpegls_error(jpegls_errc::too_much_encoded_data);
}
const uint8_t* get_cur_byte_pos() const noexcept
{
int32_t valid_bits{valid_bits_};
const uint8_t* compressed_bytes{position_};
for (;;)
{
const int32_t last_bits_count{compressed_bytes[-1] == jpeg_marker_start_byte ? 7 : 8};
if (valid_bits < last_bits_count)
return compressed_bytes;
valid_bits -= last_bits_count;
--compressed_bytes;
}
}
FORCE_INLINE int32_t read_value(const int32_t length)
{
if (valid_bits_ < length)
{
fill_read_cache();
if (UNLIKELY(valid_bits_ < length))
impl::throw_jpegls_error(jpegls_errc::invalid_encoded_data);
}
ASSERT(length != 0 && length <= valid_bits_);
ASSERT(length < 32);
const auto result{static_cast<int32_t>(read_cache_ >> (cache_t_bit_count - length))};
skip(length);
return result;
}
FORCE_INLINE int32_t peek_byte()
{
if (valid_bits_ < 8)
{
fill_read_cache();
}
return static_cast<int32_t>(read_cache_ >> max_readable_cache_bits);
}
FORCE_INLINE bool read_bit()
{
if (valid_bits_ <= 0)
{
fill_read_cache();
}
const bool set{(read_cache_ & (static_cast<cache_t>(1) << (cache_t_bit_count - 1))) != 0};
skip(1);
return set;
}
FORCE_INLINE int32_t peek_0_bits()
{
if (valid_bits_ < 16)
{
fill_read_cache();
}
#if defined(_MSC_VER) || defined(__GNUC__)
const auto count{countl_zero(read_cache_)};
return count < 16 ? count : -1;
#else
cache_t val_test = read_cache_;
for (int32_t count{}; count < 16; ++count)
{
if ((val_test & (static_cast<cache_t>(1) << (cache_t_bit_count - 1))) != 0)
return count;
val_test <<= 1;
}
return -1;
#endif
}
FORCE_INLINE int32_t read_high_bits()
{
const int32_t count{peek_0_bits()};
if (count >= 0)
{
skip(count + 1);
return count;
}
skip(15);
for (int32_t high_bits_count{15};; ++high_bits_count)
{
if (read_bit())
return high_bits_count;
}
}
int32_t read_long_value(const int32_t length)
{
if (length <= 24)
return read_value(length);
return (read_value(length - 24) << 24) + read_value(24);
}
uint8_t read_byte()
{
if (UNLIKELY(position_ == end_position_))
impl::throw_jpegls_error(jpegls_errc::source_buffer_too_small);
const uint8_t value = *position_;
++position_;
return value;
}
protected:
frame_info frame_info_;
coding_parameters parameters_;
std::unique_ptr<process_line> process_line_;
private:
using cache_t = size_t;
void fill_read_cache()
{
ASSERT(valid_bits_ <= max_readable_cache_bits);
if (fill_read_cache_optimistic())
return;
do
{
if (position_ >= end_position_)
{
if (UNLIKELY(valid_bits_ == 0))
{
// Decoding process expects at least some bits to be added to the cache.
impl::throw_jpegls_error(jpegls_errc::invalid_encoded_data);
}
return;
}
const cache_t new_byte_value{*position_};
// JPEG-LS bit stream rule: if FF is followed by a 1 bit then it is a marker
if (new_byte_value == jpeg_marker_start_byte &&
(position_ == end_position_ - 1 || (position_[1] & 0x80) != 0))
{
if (UNLIKELY(valid_bits_ <= 0))
{
// Decoding process expects at least some bits to be added to the cache.
impl::throw_jpegls_error(jpegls_errc::invalid_encoded_data);
}
// End of buffer or marker detected. Typical found markers are EOI, SOS (next scan) or RSTm.
return;
}
read_cache_ |= new_byte_value << (max_readable_cache_bits - valid_bits_);
valid_bits_ += 8;
++position_;
if (new_byte_value == jpeg_marker_start_byte)
{
// The next bit after an 0xFF needs to be ignored, compensate for the next read (see ISO/IEC 14495-1,A.1)
--valid_bits_;
}
} while (valid_bits_ < max_readable_cache_bits);
find_jpeg_marker_start_byte();
}
FORCE_INLINE bool fill_read_cache_optimistic() noexcept
{
// Easy & fast: if there is no 0xFF byte in sight, we can read without bit stuffing
if (position_ < position_ff_ - (sizeof(cache_t) - 1))
{
read_cache_ |= read_big_endian_unaligned<cache_t>(position_) >> valid_bits_;
const int bytes_to_read{(cache_t_bit_count - valid_bits_) / 8};
position_ += bytes_to_read;
valid_bits_ += bytes_to_read * 8;
ASSERT(valid_bits_ >= max_readable_cache_bits);
return true;
}
return false;
}
void find_jpeg_marker_start_byte() noexcept
{
// Use memchr to find next start byte (0xFF). memchr is optimized on some platforms to search faster.
position_ff_ = static_cast<const uint8_t*>(memchr(position_, jpeg_marker_start_byte, end_position_ - position_));
if (!position_ff_)
{
position_ff_ = end_position_;
}
}
static constexpr auto cache_t_bit_count{static_cast<int32_t>(sizeof(cache_t) * 8)};
static constexpr int32_t max_readable_cache_bits{cache_t_bit_count - 8};
// decoding
cache_t read_cache_{};
int32_t valid_bits_{};
const uint8_t* position_{};
const uint8_t* end_position_{};
const uint8_t* position_ff_{};
};
} // namespace charls