소스 검색

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.
Liav A 2 년 전
부모
커밋
2f2d808869
1개의 변경된 파일45개의 추가작업 그리고 2개의 파일을 삭제
  1. 45 2
      Userland/Libraries/LibGfx/TGALoader.cpp

+ 45 - 2
Userland/Libraries/LibGfx/TGALoader.cpp

@@ -51,6 +51,11 @@ union [[gnu::packed]] TGAPixel {
     u32 data;
     u32 data;
 };
 };
 
 
+struct TGAPixelPacket {
+    bool raw;
+    u8 pixels_count;
+};
+
 static_assert(AssertSize<TGAPixel, 4>());
 static_assert(AssertSize<TGAPixel, 4>());
 
 
 class TGAReader {
 class TGAReader {
@@ -92,6 +97,19 @@ public:
         return read_i16() | read_i16() << 16;
         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)
     ALWAYS_INLINE TGAPixel read_pixel(u8 bits_per_pixel)
     {
     {
         auto pixel = TGAPixel();
         auto pixel = TGAPixel();
@@ -186,7 +204,7 @@ bool TGAImageDecoderPlugin::decode_tga_header()
 
 
     auto bytes_remaining = reader->data().size() - reader->index();
     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;
         return false;
 
 
     if (m_context->header.bits_per_pixel < 8 || m_context->header.bits_per_pixel > 32)
     if (m_context->header.bits_per_pixel < 8 || m_context->header.bits_per_pixel > 32)
@@ -274,9 +292,34 @@ ErrorOr<ImageFrameDescriptor> TGAImageDecoderPlugin::frame(size_t index)
         }
         }
         break;
         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:
     default:
         // FIXME: Implement other TGA data types
         // 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);
     VERIFY(m_context->bitmap);