Skip to content

Commit

Permalink
LibGfx/WebP: Add CanonicalCode::write_symbol(), use it in writer
Browse files Browse the repository at this point in the history
We still construct the code length codes manually, and now we also
construct a PrefixCodeGroup manually that assigns 8 bits to all
symbols (except for fully-opaque alpha channels, and for the
unused distance codes, like before). But now we use the CanonicalCodes
from that PrefixCodeGroup for writing.

No behavior change at all, the output is bit-for-bit identical to
before. But this is a step towards actually huffman-coding symbols.

This is however a pretty big perf regression. For
`image -o test.webp test.bmp` (where test.bmp is retro-sunset.png
re-encoded as bmp), time goes from 23.7 ms to 33.2 ms.

`animation -o wow.webp giphy.gif` goes from 85.5 ms to 127.7 ms.

`animation -o wow.webp 7z7c.gif` goes from 12.6 ms to 16.5 ms.
  • Loading branch information
nico committed May 18, 2024
1 parent ffdee43 commit a59f5ff
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 11 deletions.
8 changes: 8 additions & 0 deletions Userland/Libraries/LibGfx/ImageFormats/WebPLosslessShared.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,12 @@ ErrorOr<u32> CanonicalCode::read_symbol(LittleEndianInputBitStream& bit_stream)
[&bit_stream](Compress::CanonicalCode const& code) { return code.read_symbol(bit_stream); }));
}

ErrorOr<void> CanonicalCode::write_symbol(LittleEndianOutputBitStream& bit_stream, u32 symbol) const
{
TRY(m_code.visit(
[&](u32 single_code) -> ErrorOr<void> { VERIFY(symbol == single_code); return {};},
[&](Compress::CanonicalCode const& code) { return code.write_symbol(bit_stream, symbol); }));
return {};
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class CanonicalCode {

static ErrorOr<CanonicalCode> from_bytes(ReadonlyBytes);
ErrorOr<u32> read_symbol(LittleEndianInputBitStream&) const;
ErrorOr<void> write_symbol(LittleEndianOutputBitStream&, u32) const;

private:
explicit CanonicalCode(u32 single_symbol)
Expand Down
32 changes: 21 additions & 11 deletions Userland/Libraries/LibGfx/ImageFormats/WebPWriterLossless.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <AK/MemoryStream.h>
#include <LibCompress/DeflateTables.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/ImageFormats/WebPLosslessShared.h>
#include <LibGfx/ImageFormats/WebPWriterLossless.h>

namespace Gfx {
Expand All @@ -25,7 +26,7 @@ static bool are_all_pixels_opaque(Bitmap const& bitmap)
return true;
}

NEVER_INLINE static ErrorOr<void> write_image_data(LittleEndianOutputBitStream& bit_stream, Bitmap const& bitmap, bool all_pixels_are_opaque)
NEVER_INLINE static ErrorOr<void> write_image_data(LittleEndianOutputBitStream& bit_stream, Bitmap const& bitmap, PrefixCodeGroup const& prefix_code_group)
{
// This is currently the hot loop. Keep performance in mind when you change it.
for (ARGB32 pixel : bitmap) {
Expand All @@ -34,15 +35,10 @@ NEVER_INLINE static ErrorOr<void> write_image_data(LittleEndianOutputBitStream&
u8 g = pixel >> 8;
u8 b = pixel;

// We wrote a huffman table that gives every symbol 8 bits. That means we can write the image data
// out uncompressed –- but we do need to reverse the bit order of the bytes.
TRY(bit_stream.write_bits(Compress::reverse8_lookup_table[g], 8u));
TRY(bit_stream.write_bits(Compress::reverse8_lookup_table[r], 8u));
TRY(bit_stream.write_bits(Compress::reverse8_lookup_table[b], 8u));

// If all pixels are opaque, we wrote a one-element huffman table for alpha, which needs 0 bits per element.
if (!all_pixels_are_opaque)
TRY(bit_stream.write_bits(Compress::reverse8_lookup_table[a], 8u));
TRY(prefix_code_group[0].write_symbol(bit_stream, g));
TRY(prefix_code_group[1].write_symbol(bit_stream, r));
TRY(prefix_code_group[2].write_symbol(bit_stream, b));
TRY(prefix_code_group[3].write_symbol(bit_stream, a));
}
return {};
}
Expand Down Expand Up @@ -92,6 +88,7 @@ static ErrorOr<void> write_VP8L_image_data(Stream& stream, Bitmap const& bitmap)

bool all_pixels_are_opaque = are_all_pixels_opaque(bitmap);

PrefixCodeGroup prefix_code_group;
int number_of_full_channels = all_pixels_are_opaque ? 3 : 4;
for (int i = 0; i < number_of_full_channels; ++i) {
TRY(bit_stream.write_bits(0u, 1u)); // Normal code length code.
Expand Down Expand Up @@ -119,6 +116,11 @@ static ErrorOr<void> write_VP8L_image_data(Stream& stream, Bitmap const& bitmap)
TRY(bit_stream.write_bits(254u, 8u)); // max_symbol = 2 + 254
}

Array<u8, 256> bits_per_symbol;
for (int i = 0; i < 256; ++i)
bits_per_symbol[i] = 8;
prefix_code_group[i] = MUST(CanonicalCode::from_bytes(bits_per_symbol));

// The code length codes only contain a single entry for '8'. WebP streams with a single element store 0 bits per element.
// (This is different from deflate, which needs 1 bit per element.)
}
Expand All @@ -129,16 +131,24 @@ static ErrorOr<void> write_VP8L_image_data(Stream& stream, Bitmap const& bitmap)
TRY(bit_stream.write_bits(0u, 1u)); // num_symbols - 1
TRY(bit_stream.write_bits(1u, 1u)); // is_first_8bits
TRY(bit_stream.write_bits(255u, 8u)); // symbol0
Array<u8, 256> bits_per_symbol {};
bits_per_symbol[255] = 1; // XXX comment
prefix_code_group[3] = MUST(CanonicalCode::from_bytes(bits_per_symbol));
}

// For code #5, use a simple empty code, since we don't use this yet.
// "Note: Another special case is when all prefix code lengths are zeros (an empty prefix code). [...]
// empty prefix codes can be coded as those containing a single symbol 0."
TRY(bit_stream.write_bits(1u, 1u)); // Simple code length code.
TRY(bit_stream.write_bits(0u, 1u)); // num_symbols - 1
TRY(bit_stream.write_bits(0u, 1u)); // is_first_8bits
TRY(bit_stream.write_bits(0u, 1u)); // symbol0
Array<u8, 256> bits_per_symbol {};
bits_per_symbol[0] = 1; // XXX comment
prefix_code_group[4] = MUST(CanonicalCode::from_bytes(bits_per_symbol));

// Image data.
TRY(write_image_data(bit_stream, bitmap, all_pixels_are_opaque));
TRY(write_image_data(bit_stream, bitmap, prefix_code_group));

// FIXME: Make ~LittleEndianOutputBitStream do this, or make it VERIFY() that it has happened at least.
TRY(bit_stream.align_to_byte_boundary());
Expand Down

0 comments on commit a59f5ff

Please sign in to comment.