diff options
author | Peter Nelson <peter@peterdn.com> | 2020-04-25 12:59:11 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-04-25 16:49:09 +0200 |
commit | 9c2a675c3849c2e6edee0baee61a148ab54a8bd4 (patch) | |
tree | d84e01e89aa08513732daedc00cee7f3487add8c | |
parent | cba9550f1c8b0ecf4e8384424690fd4fc39e3d6d (diff) | |
download | serenity-9c2a675c3849c2e6edee0baee61a148ab54a8bd4.zip |
LibGfx: Implement GIF LZW decoding
Add an LZWDecoder class that can decode GIF LZW data.
-rw-r--r-- | Libraries/LibGfx/GIFLoader.cpp | 122 |
1 files changed, 122 insertions, 0 deletions
diff --git a/Libraries/LibGfx/GIFLoader.cpp b/Libraries/LibGfx/GIFLoader.cpp index a0c8647929..9dc16d3cf1 100644 --- a/Libraries/LibGfx/GIFLoader.cpp +++ b/Libraries/LibGfx/GIFLoader.cpp @@ -30,6 +30,7 @@ #include <AK/MappedFile.h> #include <AK/NonnullOwnPtrVector.h> #include <LibGfx/GIFLoader.h> +#include <LibM/math.h> #include <stdio.h> #include <string.h> @@ -84,6 +85,127 @@ struct ImageDescriptor { Vector<u8> lzw_encoded_bytes; }; +class LZWDecoder { +public: + struct CodeTableEntry { + Vector<u8> colors; + u16 code; + }; + + explicit LZWDecoder(const Vector<u8>& lzw_bytes, u8 min_code_size) + : m_lzw_bytes(lzw_bytes) + , m_code_size(min_code_size) + , m_original_code_size(min_code_size) + { + init_code_table(); + } + + u16 add_control_code() + { + const u16 control_code = m_code_table.size(); + m_code_table.append({ {}, control_code }); + m_original_code_table.append({ {}, control_code }); + if ((int)m_code_table.size() >= pow(2, m_code_size) && m_code_size < 12) { + ++m_code_size; + ++m_original_code_size; + } + return control_code; + } + + void reset() + { + m_code_table.clear(); + m_code_table.append(m_original_code_table); + m_code_size = m_original_code_size; + m_output.clear(); + } + + Optional<u16> next_code() + { + size_t current_byte_index = m_current_bit_index / 8; + if (current_byte_index >= m_lzw_bytes.size()) { + return {}; + } + + // Read code bits using a 32-bit mask. If current code size > 9 bits + // then it's possible the code spans 3 bytes. + u8 current_bit_offset = m_current_bit_index % 8; + u32 mask = (u32)(pow(2, m_code_size) - 1) << current_bit_offset; + + // Make sure that we don't read bytes past the end of the data. + int bytes_past_end = current_byte_index + sizeof(mask) - m_lzw_bytes.size(); + u8 bits_past_end = 0; + if (bytes_past_end > 0) { + current_byte_index -= bytes_past_end; + mask <<= bytes_past_end * 8; + bits_past_end = bytes_past_end * 8; + } + + ASSERT(current_byte_index + sizeof(mask) - 1 < m_lzw_bytes.size()); + const u32* addr = (const u32*)&m_lzw_bytes.at(current_byte_index); + m_current_code = (*addr & mask) >> (current_bit_offset + bits_past_end); + + if (m_current_code > m_code_table.size()) { + dbg() << "Corrupted LZW stream, invalid code: " << m_current_code << " at bit index: " + << m_current_bit_index << ", code table size: " << m_code_table.size(); + return {}; + } + + m_current_bit_index += m_code_size; + + return m_current_code; + } + + Vector<u8> get_output() + { + ASSERT(m_current_code <= m_code_table.size()); + if (m_current_code < m_code_table.size()) { + Vector<u8> new_entry = m_output; + m_output = m_code_table.at(m_current_code).colors; + new_entry.append(m_output[0]); + extend_code_table(new_entry); + } else if (m_current_code == m_code_table.size()) { + m_output.append(m_output[0]); + extend_code_table(m_output); + } + return m_output; + } + +private: + void init_code_table() + { + const int initial_table_size = pow(2, m_code_size); + m_code_table.clear(); + for (u16 i = 0; i < initial_table_size; ++i) { + m_code_table.append({ { (u8)i }, i }); + } + m_original_code_table = m_code_table; + } + + void extend_code_table(Vector<u8> entry) + { + if (entry.size() > 1 && m_code_table.size() < 4096) { + m_code_table.append({ entry, (u16)m_code_table.size() }); + if ((int)m_code_table.size() >= pow(2, m_code_size) && m_code_size < 12) { + ++m_code_size; + } + } + } + + const Vector<u8>& m_lzw_bytes; + + int m_current_bit_index { 0 }; + + Vector<CodeTableEntry> m_code_table {}; + Vector<CodeTableEntry> m_original_code_table {}; + + u8 m_code_size { 0 }; + u8 m_original_code_size { 0 }; + + u16 m_current_code { 0 }; + Vector<u8> m_output {}; +}; + RefPtr<Gfx::Bitmap> load_gif_impl(const u8* data, size_t data_size) { if (data_size < 32) |