mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 14:38:11 +00:00
LibGfx/JPEG: Encapsulate operations on HuffmanStream
Advantages of encapsulation are really obvious here: - Put related code together - Prevent external functions to modify the object - Abstract the implementation No functional changes intended.
This commit is contained in:
parent
66108d102e
commit
86ce9cc30f
1 changed files with 121 additions and 100 deletions
|
@ -187,10 +187,115 @@ struct HuffmanTableSpec {
|
|||
Vector<u16> codes;
|
||||
};
|
||||
|
||||
struct HuffmanStream {
|
||||
Vector<u8> stream;
|
||||
u8 bit_offset { 0 };
|
||||
size_t byte_offset { 0 };
|
||||
class HuffmanStream {
|
||||
public:
|
||||
static ErrorOr<HuffmanStream> create(SeekableStream& stream)
|
||||
{
|
||||
HuffmanStream huffman {};
|
||||
|
||||
u8 last_byte {};
|
||||
u8 current_byte = TRY(stream.read_value<u8>());
|
||||
|
||||
for (;;) {
|
||||
last_byte = current_byte;
|
||||
current_byte = TRY(stream.read_value<u8>());
|
||||
|
||||
if (last_byte == 0xFF) {
|
||||
if (current_byte == 0xFF)
|
||||
continue;
|
||||
if (current_byte == 0x00) {
|
||||
current_byte = TRY(stream.read_value<u8>());
|
||||
huffman.m_stream.append(last_byte);
|
||||
continue;
|
||||
}
|
||||
Marker marker = 0xFF00 | current_byte;
|
||||
if (marker >= JPEG_RST0 && marker <= JPEG_RST7) {
|
||||
huffman.m_stream.append(marker);
|
||||
current_byte = TRY(stream.read_value<u8>());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Rollback the marker we just read
|
||||
TRY(stream.seek(-2, AK::SeekMode::FromCurrentPosition));
|
||||
return huffman;
|
||||
}
|
||||
|
||||
huffman.m_stream.append(last_byte);
|
||||
}
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
ErrorOr<u8> next_symbol(HuffmanTableSpec const& table)
|
||||
{
|
||||
unsigned code = 0;
|
||||
u64 code_cursor = 0;
|
||||
|
||||
for (int i = 0; i < 16; i++) { // Codes can't be longer than 16 bits.
|
||||
auto result = TRY(read_bits());
|
||||
code = (code << 1) | result;
|
||||
for (int j = 0; j < table.code_counts[i]; j++) {
|
||||
if (code == table.codes[code_cursor])
|
||||
return table.symbols[code_cursor];
|
||||
code_cursor++;
|
||||
}
|
||||
}
|
||||
|
||||
dbgln_if(JPEG_DEBUG, "If you're seeing this...the jpeg decoder needs to support more kinds of JPEGs!");
|
||||
return Error::from_string_literal("This kind of JPEG is not yet supported by the decoder");
|
||||
}
|
||||
|
||||
ErrorOr<u16> read_bits(u8 count = 1)
|
||||
{
|
||||
|
||||
if (count > NumericLimits<u16>::digits()) {
|
||||
dbgln_if(JPEG_DEBUG, "Can't read {} bits at once!", count);
|
||||
return Error::from_string_literal("Reading too much huffman bits at once");
|
||||
}
|
||||
|
||||
u16 value = 0;
|
||||
while (count--) {
|
||||
if (m_byte_offset >= m_stream.size()) {
|
||||
dbgln_if(JPEG_DEBUG, "Huffman stream exhausted. This could be an error!");
|
||||
return Error::from_string_literal("Huffman stream exhausted.");
|
||||
}
|
||||
|
||||
u8 const current_byte = m_stream[m_byte_offset];
|
||||
u8 const current_bit = 1u & (current_byte >> (7 - m_bit_offset)); // MSB first.
|
||||
|
||||
m_bit_offset++;
|
||||
value = (value << 1) | current_bit;
|
||||
|
||||
if (m_bit_offset == 8) {
|
||||
m_byte_offset++;
|
||||
m_bit_offset = 0;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void advance_to_byte_boundary()
|
||||
{
|
||||
if (m_bit_offset > 0) {
|
||||
m_bit_offset = 0;
|
||||
m_byte_offset++;
|
||||
}
|
||||
}
|
||||
|
||||
void skip_byte()
|
||||
{
|
||||
m_byte_offset++;
|
||||
}
|
||||
|
||||
u64 byte_offset() const
|
||||
{
|
||||
return m_byte_offset;
|
||||
}
|
||||
|
||||
private:
|
||||
Vector<u8> m_stream;
|
||||
u8 m_bit_offset { 0 };
|
||||
u64 m_byte_offset { 0 };
|
||||
};
|
||||
|
||||
struct ICCMultiChunkState {
|
||||
|
@ -270,48 +375,6 @@ static void generate_huffman_codes(HuffmanTableSpec& table)
|
|||
}
|
||||
}
|
||||
|
||||
static ErrorOr<size_t> read_huffman_bits(HuffmanStream& hstream, size_t count = 1)
|
||||
{
|
||||
if (count > (8 * sizeof(size_t))) {
|
||||
dbgln_if(JPEG_DEBUG, "Can't read {} bits at once!", count);
|
||||
return Error::from_string_literal("Reading too much huffman bits at once");
|
||||
}
|
||||
size_t value = 0;
|
||||
while (count--) {
|
||||
if (hstream.byte_offset >= hstream.stream.size()) {
|
||||
dbgln_if(JPEG_DEBUG, "Huffman stream exhausted. This could be an error!");
|
||||
return Error::from_string_literal("Huffman stream exhausted.");
|
||||
}
|
||||
u8 current_byte = hstream.stream[hstream.byte_offset];
|
||||
u8 current_bit = 1u & (u32)(current_byte >> (7 - hstream.bit_offset)); // MSB first.
|
||||
hstream.bit_offset++;
|
||||
value = (value << 1) | (size_t)current_bit;
|
||||
if (hstream.bit_offset == 8) {
|
||||
hstream.byte_offset++;
|
||||
hstream.bit_offset = 0;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static ErrorOr<u8> get_next_symbol(HuffmanStream& hstream, HuffmanTableSpec const& table)
|
||||
{
|
||||
unsigned code = 0;
|
||||
size_t code_cursor = 0;
|
||||
for (int i = 0; i < 16; i++) { // Codes can't be longer than 16 bits.
|
||||
auto result = TRY(read_huffman_bits(hstream));
|
||||
code = (code << 1) | (i32)result;
|
||||
for (int j = 0; j < table.code_counts[i]; j++) {
|
||||
if (code == table.codes[code_cursor])
|
||||
return table.symbols[code_cursor];
|
||||
code_cursor++;
|
||||
}
|
||||
}
|
||||
|
||||
dbgln_if(JPEG_DEBUG, "If you're seeing this...the jpeg decoder needs to support more kinds of JPEGs!");
|
||||
return Error::from_string_literal("This kind of JPEG is not yet supported by the decoder");
|
||||
}
|
||||
|
||||
static inline auto* get_component(Macroblock& block, unsigned component)
|
||||
{
|
||||
switch (component) {
|
||||
|
@ -332,7 +395,7 @@ static ErrorOr<void> refine_coefficient(Scan& scan, auto& coefficient)
|
|||
{
|
||||
// G.1.2.3 - Coding model for subsequent scans of successive approximation
|
||||
// See the correction bit from rule b.
|
||||
u8 const bit = TRY(read_huffman_bits(scan.huffman_stream, 1));
|
||||
u8 const bit = TRY(scan.huffman_stream.read_bits(1));
|
||||
if (bit == 1)
|
||||
coefficient |= 1 << scan.successive_approximation_low;
|
||||
|
||||
|
@ -359,14 +422,14 @@ static ErrorOr<void> add_dc(JPEGLoadingContext& context, Macroblock& macroblock,
|
|||
}
|
||||
|
||||
// For DC coefficients, symbol encodes the length of the coefficient.
|
||||
auto dc_length = TRY(get_next_symbol(scan.huffman_stream, dc_table));
|
||||
auto dc_length = TRY(scan.huffman_stream.next_symbol(dc_table));
|
||||
if (dc_length > 11) {
|
||||
dbgln_if(JPEG_DEBUG, "DC coefficient too long: {}!", dc_length);
|
||||
return Error::from_string_literal("DC coefficient too long");
|
||||
}
|
||||
|
||||
// DC coefficients are encoded as the difference between previous and current DC values.
|
||||
i16 dc_diff = TRY(read_huffman_bits(scan.huffman_stream, dc_length));
|
||||
i16 dc_diff = TRY(scan.huffman_stream.read_bits(dc_length));
|
||||
|
||||
// If MSB in diff is 0, the difference is -ve. Otherwise +ve.
|
||||
if (dc_length != 0 && dc_diff < (1 << (dc_length - 1)))
|
||||
|
@ -387,7 +450,7 @@ static ErrorOr<bool> read_eob(Scan& scan, u32 symbol)
|
|||
if (auto const eob = symbol & 0x0F; eob == 0 && symbol != JPEG_ZRL) {
|
||||
// We encountered an EOB marker
|
||||
auto const eob_base = symbol >> 4;
|
||||
auto const additional_value = TRY(read_huffman_bits(scan.huffman_stream, eob_base));
|
||||
auto const additional_value = TRY(scan.huffman_stream.read_bits(eob_base));
|
||||
|
||||
scan.end_of_bands_run_count = additional_value + (1 << eob_base) - 1;
|
||||
|
||||
|
@ -439,7 +502,7 @@ static ErrorOr<void> add_ac(JPEGLoadingContext& context, Macroblock& macroblock,
|
|||
// number of zeroes to be stuffed before reading the coefficient. Low 4
|
||||
// bits represent the magnitude of the coefficient.
|
||||
if (!in_zrl && scan.end_of_bands_run_count == 0 && !saved_symbol.has_value()) {
|
||||
saved_symbol = TRY(get_next_symbol(scan.huffman_stream, ac_table));
|
||||
saved_symbol = TRY(scan.huffman_stream.next_symbol(ac_table));
|
||||
|
||||
if (!TRY(read_eob(scan, *saved_symbol))) {
|
||||
to_skip = *saved_symbol >> 4;
|
||||
|
@ -453,7 +516,7 @@ static ErrorOr<void> add_ac(JPEGLoadingContext& context, Macroblock& macroblock,
|
|||
if (!in_zrl && is_progressive(context.frame.type) && scan.successive_approximation_high != 0) {
|
||||
// G.1.2.3 - Coding model for subsequent scans of successive approximation
|
||||
// Bit sign from rule a
|
||||
saved_bit_for_rule_a = TRY(read_huffman_bits(scan.huffman_stream, 1));
|
||||
saved_bit_for_rule_a = TRY(scan.huffman_stream.read_bits(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -492,7 +555,7 @@ static ErrorOr<void> add_ac(JPEGLoadingContext& context, Macroblock& macroblock,
|
|||
}
|
||||
|
||||
if (coeff_length != 0) {
|
||||
i32 ac_coefficient = TRY(read_huffman_bits(scan.huffman_stream, coeff_length));
|
||||
i32 ac_coefficient = TRY(scan.huffman_stream.read_bits(coeff_length));
|
||||
if (ac_coefficient < (1 << (coeff_length - 1)))
|
||||
ac_coefficient -= (1 << coeff_length) - 1;
|
||||
|
||||
|
@ -614,23 +677,17 @@ static ErrorOr<void> decode_huffman_stream(JPEGLoadingContext& context, Vector<M
|
|||
|
||||
// Restart markers are stored in byte boundaries. Advance the huffman stream cursor to
|
||||
// the 0th bit of the next byte.
|
||||
if (huffman_stream.byte_offset < huffman_stream.stream.size()) {
|
||||
if (huffman_stream.bit_offset > 0) {
|
||||
huffman_stream.bit_offset = 0;
|
||||
huffman_stream.byte_offset++;
|
||||
}
|
||||
huffman_stream.advance_to_byte_boundary();
|
||||
|
||||
// Skip the restart marker (RSTn).
|
||||
huffman_stream.byte_offset++;
|
||||
}
|
||||
// Skip the restart marker (RSTn).
|
||||
huffman_stream.skip_byte();
|
||||
}
|
||||
}
|
||||
|
||||
if (auto result = build_macroblocks(context, macroblocks, hcursor, vcursor); result.is_error()) {
|
||||
if constexpr (JPEG_DEBUG) {
|
||||
dbgln("Failed to build Macroblock {}: {}", i, result.error());
|
||||
dbgln("Huffman stream byte offset {}", huffman_stream.byte_offset);
|
||||
dbgln("Huffman stream bit offset {}", huffman_stream.bit_offset);
|
||||
dbgln("Huffman stream byte offset {}", huffman_stream.byte_offset());
|
||||
}
|
||||
return result.release_error();
|
||||
}
|
||||
|
@ -720,7 +777,6 @@ static ErrorOr<void> read_start_of_scan(Stream& stream, JPEGLoadingContext& cont
|
|||
u8 const component_count = TRY(stream.read_value<u8>());
|
||||
|
||||
Scan current_scan;
|
||||
current_scan.huffman_stream.stream.ensure_capacity(50 * KiB);
|
||||
|
||||
Optional<u8> last_read;
|
||||
u8 component_read = 0;
|
||||
|
@ -775,6 +831,7 @@ static ErrorOr<void> read_start_of_scan(Stream& stream, JPEGLoadingContext& cont
|
|||
return Error::from_string_literal("Spectral selection is not [0,63] or successive approximation is not null");
|
||||
}
|
||||
|
||||
current_scan.huffman_stream = TRY(HuffmanStream::create(*context.stream));
|
||||
context.current_scan = move(current_scan);
|
||||
|
||||
return {};
|
||||
|
@ -1640,41 +1697,6 @@ static ErrorOr<void> parse_header(Stream& stream, JPEGLoadingContext& context)
|
|||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
static ErrorOr<void> scan_huffman_stream(AK::SeekableStream& stream, HuffmanStream& huffman_stream)
|
||||
{
|
||||
u8 last_byte;
|
||||
u8 current_byte = TRY(stream.read_value<u8>());
|
||||
|
||||
for (;;) {
|
||||
last_byte = current_byte;
|
||||
current_byte = TRY(stream.read_value<u8>());
|
||||
|
||||
if (last_byte == 0xFF) {
|
||||
if (current_byte == 0xFF)
|
||||
continue;
|
||||
if (current_byte == 0x00) {
|
||||
current_byte = TRY(stream.read_value<u8>());
|
||||
huffman_stream.stream.append(last_byte);
|
||||
continue;
|
||||
}
|
||||
Marker marker = 0xFF00 | current_byte;
|
||||
if (marker >= JPEG_RST0 && marker <= JPEG_RST7) {
|
||||
huffman_stream.stream.append(marker);
|
||||
current_byte = TRY(stream.read_value<u8>());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Rollback the marker we just read
|
||||
TRY(stream.seek(-2, AK::SeekMode::FromCurrentPosition));
|
||||
return {};
|
||||
} else {
|
||||
huffman_stream.stream.append(last_byte);
|
||||
}
|
||||
}
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
static ErrorOr<void> decode_header(JPEGLoadingContext& context)
|
||||
{
|
||||
if (context.state < JPEGLoadingContext::State::HeaderDecoded) {
|
||||
|
@ -1711,7 +1733,6 @@ static ErrorOr<Vector<Macroblock>> construct_macroblocks(JPEGLoadingContext& con
|
|||
TRY(handle_miscellaneous_or_table(*context.stream, context, marker));
|
||||
} else if (marker == JPEG_SOS) {
|
||||
TRY(read_start_of_scan(*context.stream, context));
|
||||
TRY(scan_huffman_stream(*context.stream, context.current_scan.huffman_stream));
|
||||
TRY(decode_huffman_stream(context, macroblocks));
|
||||
} else if (marker == JPEG_EOI) {
|
||||
return macroblocks;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue