LibGfx/GIF: Add support for colors

To determine the palette of colors we use the median cut algorithm.
While being a correct implementation, enhancements are obviously
existing on both the median cut algorithm and the encoding side.
This commit is contained in:
Lucas CHOLLET 2024-05-14 13:10:04 -04:00 committed by Andreas Kling
parent 1ba8a6f80f
commit a0401b0d86
Notes: sideshowbarker 2024-07-17 05:05:51 +09:00
2 changed files with 13 additions and 16 deletions

View file

@ -112,11 +112,8 @@ TEST_CASE(test_bmp)
TEST_CASE(test_gif)
{
auto bitmap = TRY_OR_FAIL(create_test_rgb_bitmap());
// We only support grayscale and non-animated images at the moment - convert bitmap to grayscale.
for (auto& argb : *bitmap)
argb = Color::from_argb(argb).to_grayscale().value();
// Let's limit the size of the image so every color can fit in a color table of 256 elements.
auto bitmap = TRY_OR_FAIL(TRY_OR_FAIL(create_test_rgb_bitmap())->cropped({ 0, 0, 16, 16 }));
auto encoded_bitmap = TRY_OR_FAIL((encode_bitmap<Gfx::GIFWriter>(bitmap)));
auto decoder = TRY_OR_FAIL(Gfx::GIFImageDecoderPlugin::create(encoded_bitmap));

View file

@ -8,6 +8,7 @@
#include <LibCompress/Lzw.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/ImageFormats/GIFWriter.h>
#include <LibGfx/MedianCut.h>
namespace Gfx {
@ -49,29 +50,27 @@ ErrorOr<void> write_logical_descriptor(BigEndianOutputBitStream& stream, Bitmap
return {};
}
ErrorOr<void> write_global_color_table(Stream& stream)
ErrorOr<void> write_global_color_table(Stream& stream, ColorPalette const& palette)
{
// 19. Global Color Table
// FIXME: The color table should include color specific to the image
for (u16 i = 0; i < 256; ++i) {
TRY(stream.write_value<u8>(i));
TRY(stream.write_value<u8>(i));
TRY(stream.write_value<u8>(i));
auto const color = i < palette.palette().size() ? palette.palette()[i] : Color::NamedColor::White;
TRY(stream.write_value<u8>(color.red()));
TRY(stream.write_value<u8>(color.green()));
TRY(stream.write_value<u8>(color.blue()));
}
return {};
}
ErrorOr<void> write_image_data(Stream& stream, Bitmap const& bitmap)
ErrorOr<void> write_image_data(Stream& stream, Bitmap const& bitmap, ColorPalette const& palette)
{
// 22. Table Based Image Data
auto const pixel_number = static_cast<u32>(bitmap.width() * bitmap.height());
auto indexes = TRY(ByteBuffer::create_uninitialized(pixel_number));
for (u32 i = 0; i < pixel_number; ++i) {
auto const color = Color::from_argb(*(bitmap.begin() + i));
if (color.red() != color.green() || color.green() != color.blue())
return Error::from_string_literal("Non grayscale images are unsupported.");
indexes[i] = Color::from_argb(*(bitmap.begin() + i)).red(); // Any channel is correct
indexes[i] = palette.index_of_closest_color(color);
}
constexpr u8 lzw_minimum_code_size = 8;
@ -132,15 +131,16 @@ ErrorOr<void> write_trailer(Stream& stream)
ErrorOr<void> GIFWriter::encode(Stream& stream, Bitmap const& bitmap)
{
auto const palette = TRY(median_cut(bitmap, 256));
TRY(write_header(stream));
BigEndianOutputBitStream bit_stream { MaybeOwned<Stream> { stream } };
TRY(write_logical_descriptor(bit_stream, bitmap));
TRY(write_global_color_table(bit_stream));
TRY(write_global_color_table(bit_stream, palette));
// Write a Table-Based Image
TRY(write_image_descriptor(bit_stream, bitmap));
TRY(write_image_data(stream, bitmap));
TRY(write_image_data(stream, bitmap, palette));
TRY(write_trailer(bit_stream));