LibGfx/WebPWriter: Implement pixel bundling for color indexing

If an image has <= 16 colors, WebP lossless files pack multiple
color table indexes into a single pixel's green channel, further
reducing file size. This adds support for that.

My current test files all have more than 16 colors. For a 16x16
black-and-white bitmap that contains a little smiley face in the
middle, this reduces the output size from 128B to 54B.
This commit is contained in:
Nico Weber 2024-05-29 18:24:41 -04:00 committed by Jelle Raaijmakers
parent cae672e1f9
commit e212c20228
Notes: sideshowbarker 2024-07-16 23:55:09 +09:00

View file

@ -353,12 +353,6 @@ static ErrorOr<NonnullRefPtr<Bitmap>> maybe_write_color_indexing_transform(Littl
if (color_table_size <= 1 || color_table_size > 256) if (color_table_size <= 1 || color_table_size > 256)
return bitmap; return bitmap;
if (color_table_size <= 16) {
// FIXME: Implement pixel bundling.
dbgln_if(WEBP_DEBUG, "WebP: FIXME: Not writing color index because pixel bundling is not yet implemented");
return bitmap;
}
TRY(bit_stream.write_bits(1u, 1u)); // Transform present. TRY(bit_stream.write_bits(1u, 1u)); // Transform present.
TRY(bit_stream.write_bits(static_cast<unsigned>(COLOR_INDEXING_TRANSFORM), 2u)); TRY(bit_stream.write_bits(static_cast<unsigned>(COLOR_INDEXING_TRANSFORM), 2u));
@ -383,14 +377,33 @@ static ErrorOr<NonnullRefPtr<Bitmap>> maybe_write_color_indexing_transform(Littl
HashMap<ARGB32, u8> color_index_map; HashMap<ARGB32, u8> color_index_map;
for (unsigned i = 0; i < color_table_size; ++i) for (unsigned i = 0; i < color_table_size; ++i)
color_index_map.set(colors[i], i); color_index_map.set(colors[i], i);
auto new_bitmap = TRY(Bitmap::create(BitmapFormat::BGRx8888, bitmap->size()));
// "When the color table is small (equal to or less than 16 colors), several pixels are bundled into a single pixel.
// The pixel bundling packs several (2, 4, or 8) pixels into a single pixel, reducing the image width respectively."
int width_bits;
if (color_table_size <= 2)
width_bits = 3;
else if (color_table_size <= 4)
width_bits = 2;
else if (color_table_size <= 16)
width_bits = 1;
else
width_bits = 0;
int pixels_per_pixel = 1 << width_bits;
int image_width = ceil_div(bitmap->width(), pixels_per_pixel);
auto new_bitmap = TRY(Bitmap::create(BitmapFormat::BGRx8888, { image_width, bitmap->height() }));
unsigned bits_per_pixel = 8 / pixels_per_pixel;
for (int y = 0; y < bitmap->height(); ++y) { for (int y = 0; y < bitmap->height(); ++y) {
for (int x = 0; x < bitmap->width(); ++x) { for (int x = 0, new_x = 0; x < bitmap->width(); x += pixels_per_pixel, ++new_x) {
auto pixel = bitmap->get_pixel(x, y); u8 indexes = 0;
auto result = color_index_map.get(pixel.value()); for (int i = 0; i < pixels_per_pixel && x + i < bitmap->width(); ++i) {
VERIFY(result.has_value()); auto pixel = bitmap->get_pixel(x + i, y);
// "The indexing is done based on the green component of the ARGB color." auto result = color_index_map.get(pixel.value());
new_bitmap->set_pixel(x, y, Color(0, result.value(), 0)); VERIFY(result.has_value());
indexes |= result.value() << (i * bits_per_pixel);
}
new_bitmap->set_pixel(new_x, y, Color(0, indexes, 0, 0));
} }
} }