From 2f2d808869df1a7cdccdd4d86bf44c42a5bdd0f3 Mon Sep 17 00:00:00 2001 From: Liav A Date: Sat, 7 Jan 2023 17:01:41 +0200 Subject: [PATCH] LibGfx: Add support for RLE compressed TGA images RLE is an old technique being used for decades, as is known as Run-Length-Encoding, which means that for repeating sequence of bytes, we keep an indicator for the length of the sequence and only one sample of it, to save storage space. GIMP can generate lossless-compressed TGA images, with RLE compression being used. It means that for a compressed image, the data is no longer arranged in sequence of pixels, but a sequence of pixel packets. There are two possible pixel packets: - RLE packets, which are encoded with one byte for indicating the run-length and another one pixel (3 bytes for TrueColor pixel), so essentially in runtime, the TGA decoder will use the length to plot the same pixel in multiple pixels of the output pixel bitmap. - Raw packets, which are encoded with one byte as indicator for the length of the whole pixel sequence and N-length pixel sequence afterwards. This is not used for any sort of compression by the TGA format, but still needed to be supported for full compatibility with TGA images that uses the RLE compression. --- Userland/Libraries/LibGfx/TGALoader.cpp | 47 +++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/Userland/Libraries/LibGfx/TGALoader.cpp b/Userland/Libraries/LibGfx/TGALoader.cpp index 46f2ff4f30..71cea324a6 100644 --- a/Userland/Libraries/LibGfx/TGALoader.cpp +++ b/Userland/Libraries/LibGfx/TGALoader.cpp @@ -51,6 +51,11 @@ union [[gnu::packed]] TGAPixel { u32 data; }; +struct TGAPixelPacket { + bool raw; + u8 pixels_count; +}; + static_assert(AssertSize()); class TGAReader { @@ -92,6 +97,19 @@ public: return read_i16() | read_i16() << 16; } + ALWAYS_INLINE TGAPixelPacket read_packet_type() + { + auto pixel_packet_type = read_u8(); + auto pixel_packet = TGAPixelPacket(); + pixel_packet.raw = !(pixel_packet_type & 0x80); + pixel_packet.pixels_count = (pixel_packet_type & 0x7f); + + // NOTE: Run-length-encoded/Raw pixel packets cannot encode zero pixels, + // so value 0 stands for 1 pixel, 1 stands for 2, etc... + pixel_packet.pixels_count++; + return pixel_packet; + } + ALWAYS_INLINE TGAPixel read_pixel(u8 bits_per_pixel) { auto pixel = TGAPixel(); @@ -186,7 +204,7 @@ bool TGAImageDecoderPlugin::decode_tga_header() auto bytes_remaining = reader->data().size() - reader->index(); - if (bytes_remaining < (m_context->header.width * m_context->header.height * (m_context->header.bits_per_pixel / 8))) + if (m_context->header.data_type_code == TGADataType::UncompressedRGB && bytes_remaining < (m_context->header.width * m_context->header.height * (m_context->header.bits_per_pixel / 8))) return false; if (m_context->header.bits_per_pixel < 8 || m_context->header.bits_per_pixel > 32) @@ -274,9 +292,34 @@ ErrorOr TGAImageDecoderPlugin::frame(size_t index) } break; } + case TGADataType::RunLengthEncodedRGB: { + size_t pixel_index = 0; + size_t pixel_count = height * width; + while (pixel_index < pixel_count) { + auto packet_type = m_context->reader->read_packet_type(); + VERIFY(packet_type.pixels_count > 0); + TGAPixel pixel = m_context->reader->read_pixel(bits_per_pixel); + auto max_pixel_index = min(pixel_index + packet_type.pixels_count, pixel_count); + for (size_t current_pixel_index = pixel_index; current_pixel_index < max_pixel_index; ++current_pixel_index) { + int row = current_pixel_index / width; + int col = current_pixel_index % width; + auto actual_row = row; + if (y_origin < height) + actual_row = height - 1 - row; + auto actual_col = col; + if (x_origin > width) + actual_col = width - 1 - col; + m_context->bitmap->scanline(actual_row)[actual_col] = pixel.data; + if (packet_type.raw && (current_pixel_index + 1) < max_pixel_index) + pixel = m_context->reader->read_pixel(bits_per_pixel); + } + pixel_index += packet_type.pixels_count; + } + break; + } default: // FIXME: Implement other TGA data types - return Error::from_string_literal("TGAImageDecoderPlugin: Can currently only handle the UncompressedRGB data type"); + return Error::from_string_literal("TGAImageDecoderPlugin: Can currently only handle the UncompressedRGB or CompressedRGB data type"); } VERIFY(m_context->bitmap);