1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 07:17:35 +00:00

LibGfx/JPEG: Avoid the copy of each scan

Inside each scan, raw data is read with the following rules:
- Each `0x00` that is preceded by `0xFF` should be discarded
- If multiple `0xFF` are following, only consider one.

That, plus the fact that we don't know the size of the scan beforehand
made us put a prepared stream in a vector for an easy, later on, usage.

This patch remove this duplication by realizing these operations in a
stream-friendly way.
This commit is contained in:
Lucas CHOLLET 2023-06-01 23:00:24 -04:00 committed by Andreas Kling
parent 9a317267de
commit 5a0d702f21

View file

@ -312,6 +312,11 @@ public:
return m_saved_marker; return m_saved_marker;
} }
u64 byte_offset() const
{
return m_byte_offset;
}
private: private:
JPEGStream(NonnullOwnPtr<Stream> stream, Vector<u8> buffer) JPEGStream(NonnullOwnPtr<Stream> stream, Vector<u8> buffer)
: m_stream(move(stream)) : m_stream(move(stream))
@ -342,116 +347,127 @@ private:
class HuffmanStream { class HuffmanStream {
public: public:
static ErrorOr<HuffmanStream> create(JPEGStream& stream)
{
HuffmanStream huffman {};
u8 last_byte {};
u8 current_byte = TRY(stream.read_u8());
for (;;) {
last_byte = current_byte;
current_byte = TRY(stream.read_u8());
if (last_byte == 0xFF) {
if (current_byte == 0xFF)
continue;
if (current_byte == 0x00) {
current_byte = TRY(stream.read_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_u8());
continue;
}
// Note: The only way to know that we reached the end of a segment is to read
// the marker of the following one. So we store it for later use.
stream.saved_marker({}) = marker;
return huffman;
}
huffman.m_stream.append(last_byte);
}
VERIFY_NOT_REACHED();
}
ErrorOr<u8> next_symbol(HuffmanTable const& table) ErrorOr<u8> next_symbol(HuffmanTable const& table)
{ {
u16 const code = peek_bits(HuffmanTable::maximum_bits_per_code); u16 const code = TRY(peek_bits(HuffmanTable::maximum_bits_per_code));
auto const symbol_and_size = TRY(table.symbol_from_code(code)); auto const symbol_and_size = TRY(table.symbol_from_code(code));
discard_bits(symbol_and_size.size); TRY(discard_bits(symbol_and_size.size));
return symbol_and_size.symbol; return symbol_and_size.symbol;
} }
ErrorOr<u16> read_bits(u8 count = 1) ErrorOr<u16> read_bits(u8 count = 1)
{ {
if (count > NumericLimits<u16>::digits()) { if (count > NumericLimits<u16>::digits()) {
dbgln_if(JPEG_DEBUG, "Can't read {} bits at once!", count); dbgln_if(JPEG_DEBUG, "Can't read {} bits at once!", count);
return Error::from_string_literal("Reading too much huffman bits at once"); return Error::from_string_literal("Reading too much huffman bits at once");
} }
u16 const value = peek_bits(count); u16 const value = TRY(peek_bits(count));
discard_bits(count); TRY(discard_bits(count));
return value; return value;
} }
u16 peek_bits(u8 count) const ErrorOr<u16> peek_bits(u8 count)
{ {
using BufferType = u32; if (count == 0)
return 0;
constexpr static auto max = NumericLimits<BufferType>::max(); if (count + m_bit_offset > bits_in_reservoir)
TRY(refill_reservoir());
auto const mask = max >> (8 + m_bit_offset); auto const mask = NumericLimits<u16>::max() >> (NumericLimits<u16>::digits() - count);
BufferType msb_buffer {}; return static_cast<u16>((m_bit_reservoir >> (bits_in_reservoir - m_bit_offset - count)) & mask);
if (m_byte_offset + 0 < m_stream.size())
msb_buffer |= (static_cast<BufferType>(m_stream[m_byte_offset + 0]) << (2 * 8));
if (m_byte_offset + 1 < m_stream.size())
msb_buffer |= (static_cast<BufferType>(m_stream[m_byte_offset + 1]) << (1 * 8));
if (m_byte_offset + 2 < m_stream.size())
msb_buffer |= (static_cast<BufferType>(m_stream[m_byte_offset + 2]) << (0 * 8));
return (mask & msb_buffer) >> (3 * 8 - m_bit_offset - count);
} }
void discard_bits(u8 count) ErrorOr<void> discard_bits(u8 count)
{ {
m_bit_offset += count; m_bit_offset += count;
auto const carry = m_bit_offset / 8;
m_bit_offset -= 8 * carry;
m_byte_offset += carry;
}
void advance_to_byte_boundary() if (m_bit_offset > bits_in_reservoir) {
{ // FIXME: I can't find a test case for that so let's leave it for later
if (m_bit_offset > 0) { // instead of inserting an hard-to-find bug.
m_bit_offset = 0; TODO();
m_byte_offset++;
} }
return {};
} }
void skip_byte() ErrorOr<void> advance_to_byte_boundary()
{ {
m_byte_offset++; if (auto remainder = m_bit_offset % 8; remainder != 0)
TRY(discard_bits(bits_per_byte - remainder));
return {};
} }
u64 byte_offset() const HuffmanStream(JPEGStream& stream)
: jpeg_stream(stream)
{ {
return m_byte_offset;
} }
private: private:
Vector<u8> m_stream; ErrorOr<void> refill_reservoir()
u8 m_bit_offset { 0 }; {
u64 m_byte_offset { 0 }; auto const bytes_needed = m_bit_offset / bits_per_byte;
u8 bytes_added {};
auto const append_byte = [&](u8 byte) {
m_last_byte_was_ff = false;
m_bit_reservoir <<= 8;
m_bit_reservoir |= byte;
m_bit_offset -= 8;
bytes_added++;
};
do {
// Note: We fake zeroes when we have reached another segment
// It allows us to continue peeking seamlessly.
u8 const next_byte = jpeg_stream.saved_marker({}).has_value() ? 0 : TRY(jpeg_stream.read_u8());
if (m_last_byte_was_ff) {
if (next_byte == 0xFF)
continue;
if (next_byte == 0x00) {
append_byte(0xFF);
continue;
}
Marker const marker = 0xFF00 | next_byte;
if (marker < JPEG_RST0 || marker > JPEG_RST7) {
// Note: The only way to know that we reached the end of a segment is to read
// the marker of the following one. So we store it for later use.
jpeg_stream.saved_marker({}) = marker;
m_last_byte_was_ff = false;
continue;
}
}
if (next_byte == 0xFF) {
m_last_byte_was_ff = true;
continue;
}
append_byte(next_byte);
} while (bytes_added < bytes_needed);
return {};
}
JPEGStream& jpeg_stream;
using Reservoir = u64;
static constexpr auto bits_per_byte = 8;
static constexpr auto bits_in_reservoir = sizeof(Reservoir) * bits_per_byte;
Reservoir m_bit_reservoir {};
u8 m_bit_offset { bits_in_reservoir };
bool m_last_byte_was_ff { false };
}; };
struct ICCMultiChunkState { struct ICCMultiChunkState {
@ -460,6 +476,11 @@ struct ICCMultiChunkState {
}; };
struct Scan { struct Scan {
Scan(HuffmanStream stream)
: huffman_stream(stream)
{
}
// B.2.3 - Scan header syntax // B.2.3 - Scan header syntax
Vector<ScanComponent, 4> components; Vector<ScanComponent, 4> components;
@ -834,17 +855,17 @@ static ErrorOr<void> decode_huffman_stream(JPEGLoadingContext& context, Vector<M
// Restart markers are stored in byte boundaries. Advance the huffman stream cursor to // Restart markers are stored in byte boundaries. Advance the huffman stream cursor to
// the 0th bit of the next byte. // the 0th bit of the next byte.
huffman_stream.advance_to_byte_boundary(); TRY(huffman_stream.advance_to_byte_boundary());
// Skip the restart marker (RSTn). // Skip the restart marker (RSTn).
huffman_stream.skip_byte(); TRY(huffman_stream.discard_bits(8));
} }
} }
if (auto result = build_macroblocks(context, macroblocks, hcursor, vcursor); result.is_error()) { if (auto result = build_macroblocks(context, macroblocks, hcursor, vcursor); result.is_error()) {
if constexpr (JPEG_DEBUG) { if constexpr (JPEG_DEBUG) {
dbgln("Failed to build Macroblock {}: {}", i, result.error()); dbgln("Failed to build Macroblock {}: {}", i, result.error());
dbgln("Huffman stream byte offset {}", huffman_stream.byte_offset()); dbgln("Huffman stream byte offset {}", context.stream.byte_offset());
} }
return result.release_error(); return result.release_error();
} }
@ -936,7 +957,7 @@ static ErrorOr<void> read_start_of_scan(JPEGStream& stream, JPEGLoadingContext&
[[maybe_unused]] u16 const bytes_to_read = TRY(read_effective_chunk_size(stream)); [[maybe_unused]] u16 const bytes_to_read = TRY(read_effective_chunk_size(stream));
u8 const component_count = TRY(stream.read_u8()); u8 const component_count = TRY(stream.read_u8());
Scan current_scan; Scan current_scan(HuffmanStream { context.stream });
Optional<u8> last_read; Optional<u8> last_read;
u8 component_read = 0; u8 component_read = 0;
@ -991,7 +1012,6 @@ static ErrorOr<void> read_start_of_scan(JPEGStream& stream, JPEGLoadingContext&
return Error::from_string_literal("Spectral selection is not [0,63] or successive approximation is not null"); return Error::from_string_literal("Spectral selection is not [0,63] or successive approximation is not null");
} }
current_scan.huffman_stream = TRY(HuffmanStream::create(stream));
context.current_scan = move(current_scan); context.current_scan = move(current_scan);
return {}; return {};