Преглед на файлове

LibGfx/ILBMLoader: Add HAM6/HAM8 support

Nicolas Ramz преди 1 година
родител
ревизия
8d68f94282
променени са 3 файла, в които са добавени 67 реда и са изтрити 6 реда
  1. 11 0
      Tests/LibGfx/TestImageDecoder.cpp
  2. BIN
      Tests/LibGfx/test-inputs/ilbm/ham6.iff
  3. 56 6
      Userland/Libraries/LibGfx/ImageFormats/ILBMLoader.cpp

+ 11 - 0
Tests/LibGfx/TestImageDecoder.cpp

@@ -138,6 +138,17 @@ TEST_CASE(test_ilbm_uncompressed)
     EXPECT_EQ(frame.image->get_pixel(8, 0), Gfx::Color(0xee, 0xbb, 0, 255));
 }
 
+TEST_CASE(test_ilbm_ham6)
+{
+    auto file = MUST(Core::MappedFile::map(TEST_INPUT("ilbm/ham6.iff"sv)));
+    EXPECT(Gfx::ILBMImageDecoderPlugin::sniff(file->bytes()));
+    auto plugin_decoder = MUST(Gfx::ILBMImageDecoderPlugin::create(file->bytes()));
+
+    auto frame = expect_single_frame_of_size(*plugin_decoder, { 256, 256 });
+
+    EXPECT_EQ(frame.image->get_pixel(77, 107), Gfx::Color(0xf0, 0x40, 0x40, 0xff));
+}
+
 TEST_CASE(test_ilbm_malformed_header)
 {
     Array test_inputs = {

BIN
Tests/LibGfx/test-inputs/ilbm/ham6.iff


+ 56 - 6
Userland/Libraries/LibGfx/ImageFormats/ILBMLoader.cpp

@@ -8,6 +8,7 @@
 #include <AK/Debug.h>
 #include <AK/Endian.h>
 #include <AK/FixedArray.h>
+#include <AK/IntegralMath.h>
 #include <LibGfx/FourCC.h>
 #include <LibGfx/ImageFormats/ILBMLoader.h>
 
@@ -89,6 +90,9 @@ struct ILBMLoadingContext {
 
     Vector<Color> color_table;
 
+    // number of bits needed to describe current palette
+    u8 cmap_bits;
+
     RefPtr<Gfx::Bitmap> bitmap;
 
     BMHDHeader bm_header;
@@ -132,9 +136,32 @@ static ErrorOr<RefPtr<Gfx::Bitmap>> chunky_to_bitmap(ILBMLoadingContext& context
     dbgln_if(ILBM_DEBUG, "created Bitmap {}x{}", width, height);
 
     for (int row = 0; row < height; ++row) {
+        // Keep color: in HAM mode, current color
+        // may be based on previous color instead of coming from
+        // the palette.
+        Color color = Color::Black;
         for (int col = 0; col < width; ++col) {
             u8 index = chunky[(width * row) + col];
-            bitmap->set_pixel(col, row, context.color_table[index]);
+            if (index < context.color_table.size()) {
+                color = context.color_table[index];
+            } else if (has_flag(context.viewport_mode, ViewportMode::HAM)) {
+                // Get the control bit which will tell use how current pixel should be calculated
+                u8 control = (index >> context.cmap_bits) & 0x3;
+                // Since we only have (cmap_bits - 2) bits to define the component,
+                // we need to pad it to 8 bits.
+                u8 component = (index % context.color_table.size()) << (8 - context.cmap_bits);
+
+                if (control == 1) {
+                    color.set_blue(component);
+                } else if (control == 2) {
+                    color.set_red(component);
+                } else {
+                    color.set_green(component);
+                }
+            } else {
+                return Error::from_string_literal("Color map index out of bounds but HAM bit not set");
+            }
+            bitmap->set_pixel(col, row, color);
         }
     }
 
@@ -237,6 +264,26 @@ static ErrorOr<void> extend_ehb_palette(ILBMLoadingContext& context)
     return {};
 }
 
+static ErrorOr<void> reduce_ham_palette(ILBMLoadingContext& context)
+{
+    u8 bits = context.cmap_bits;
+
+    dbgln_if(ILBM_DEBUG, "reduce palette planes={} bits={}", context.bm_header.planes, context.cmap_bits);
+
+    if (bits > context.bm_header.planes) {
+        dbgln_if(ILBM_DEBUG, "need to reduce palette");
+        bits -= (bits - context.bm_header.planes) + 2;
+        // bits shouldn't theorically be less than 4 bits in HAM mode.
+        if (bits < 4)
+            return Error::from_string_literal("Error while reducing CMAP for HAM: bits too small");
+
+        context.color_table.resize((context.color_table.size() >> bits));
+        context.cmap_bits = bits;
+    }
+
+    return {};
+}
+
 static ErrorOr<void> decode_body_chunk(Chunk body_chunk, ILBMLoadingContext& context)
 {
     dbgln_if(ILBM_DEBUG, "decode_body_chunk {}", body_chunk.data.size());
@@ -250,12 +297,14 @@ static ErrorOr<void> decode_body_chunk(Chunk body_chunk, ILBMLoadingContext& con
         pixel_data = TRY(planar_to_chunky(body_chunk.data, context));
     }
 
-    // Some files already have 64 colours defined in the palette,
-    // maybe for upward compatibility with 256 colours software/hardware.
-    // DPaint 4 & previous files only have 32 colours so the
+    // Some files already have 64 colors defined in the palette,
+    // maybe for upward compatibility with 256 colors software/hardware.
+    // DPaint 4 & previous files only have 32 colors so the
     // palette needs to be extended only for these files.
     if (has_flag(context.viewport_mode, ViewportMode::EHB) && context.color_table.size() < 64) {
         TRY(extend_ehb_palette(context));
+    } else if (has_flag(context.viewport_mode, ViewportMode::HAM)) {
+        TRY(reduce_ham_palette(context));
     }
 
     context.bitmap = TRY(chunky_to_bitmap(context, pixel_data));
@@ -303,10 +352,11 @@ static ErrorOr<void> decode_iff_chunks(ILBMLoadingContext& context)
     while (!chunks.is_empty()) {
         auto chunk = TRY(decode_iff_advance_chunk(chunks));
         if (chunk.type == FourCC("CMAP")) {
-            if (chunk.data.size() != (1ul << context.bm_header.planes) * 3)
-                return Error::from_string_literal("Invalid CMAP chunk size");
+            // Some files (HAM mainly) have CMAP chunks larger than the planes they advertise: I'm not sure
+            // why but we should not return an error in this case.
 
             context.color_table = TRY(decode_cmap_chunk(chunk));
+            context.cmap_bits = AK::ceil_log2(context.color_table.size());
         } else if (chunk.type == FourCC("BODY")) {
             if (context.color_table.is_empty())
                 return Error::from_string_literal("Decoding BODY chunk without a color map is not currently supported");