Prechádzať zdrojové kódy

LibGfx+Tests: Add test for webp ICC loading and fix bug

I drew the two webp files in Photoshop and saved them using the
"Save a Copy..." dialog, with ICC profile and all other boxes checked.

(I also tried saving with all the boxes unchecked, but it still wrote an
extended webp instead of a basic file.)

The lossless file exposed a bug: I didn't handle chunk padding
correctly before this patch.
Nico Weber 2 rokov pred
rodič
commit
14c0bae704

+ 25 - 0
Tests/LibGfx/TestICCProfile.cpp

@@ -9,6 +9,7 @@
 #include <LibGfx/ICC/Profile.h>
 #include <LibGfx/JPEGLoader.h>
 #include <LibGfx/PNGLoader.h>
+#include <LibGfx/WebPLoader.h>
 #include <LibTest/TestCase.h>
 
 #ifdef AK_OS_SERENITY
@@ -41,6 +42,30 @@ TEST_CASE(jpg)
     EXPECT(icc_profile->is_v4());
 }
 
+TEST_CASE(webp_extended_lossless)
+{
+    auto file = MUST(Core::MappedFile::map(TEST_INPUT("extended-lossless.webp"sv)));
+    auto webp = MUST(Gfx::WebPImageDecoderPlugin::create(file->bytes()));
+    EXPECT(webp->initialize());
+    auto icc_bytes = MUST(webp->icc_data());
+    EXPECT(icc_bytes.has_value());
+
+    auto icc_profile = MUST(Gfx::ICC::Profile::try_load_from_externally_owned_memory(icc_bytes.value()));
+    EXPECT(icc_profile->is_v2());
+}
+
+TEST_CASE(webp_extended_lossy)
+{
+    auto file = MUST(Core::MappedFile::map(TEST_INPUT("extended-lossy.webp"sv)));
+    auto webp = MUST(Gfx::WebPImageDecoderPlugin::create(file->bytes()));
+    EXPECT(webp->initialize());
+    auto icc_bytes = MUST(webp->icc_data());
+    EXPECT(icc_bytes.has_value());
+
+    auto icc_profile = MUST(Gfx::ICC::Profile::try_load_from_externally_owned_memory(icc_bytes.value()));
+    EXPECT(icc_profile->is_v2());
+}
+
 TEST_CASE(serialize_icc)
 {
     auto file = MUST(Core::MappedFile::map(TEST_INPUT("p3-v4.icc"sv)));

BIN
Tests/LibGfx/test-inputs/extended-lossless.webp


BIN
Tests/LibGfx/test-inputs/extended-lossy.webp


+ 20 - 0
Userland/Libraries/LibGfx/WebPLoader.cpp

@@ -110,6 +110,7 @@ static ErrorOr<void> decode_webp_header(WebPLoadingContext& context)
     return {};
 }
 
+// https://developers.google.com/speed/webp/docs/riff_container#riff_file_format
 static ErrorOr<Chunk> decode_webp_chunk_header(WebPLoadingContext& context, ReadonlyBytes chunks)
 {
     if (chunks.size() < sizeof(ChunkHeader)) {
@@ -128,10 +129,29 @@ static ErrorOr<Chunk> decode_webp_chunk_header(WebPLoadingContext& context, Read
     return Chunk { header.chunk_type, { chunks.data() + sizeof(ChunkHeader), header.chunk_size } };
 }
 
+// https://developers.google.com/speed/webp/docs/riff_container#riff_file_format
 static ErrorOr<Chunk> decode_webp_advance_chunk(WebPLoadingContext& context, ReadonlyBytes& chunks)
 {
     auto chunk = TRY(decode_webp_chunk_header(context, chunks));
+
+    // "Chunk Size: 32 bits (uint32)
+    //      The size of the chunk in bytes, not including this field, the chunk identifier or padding.
+    //  Chunk Payload: Chunk Size bytes
+    //      The data payload. If Chunk Size is odd, a single padding byte -- that MUST be 0 to conform with RIFF -- is added."
     chunks = chunks.slice(sizeof(ChunkHeader) + chunk.data.size());
+
+    if (chunk.data.size() % 2 != 0) {
+        if (chunks.is_empty()) {
+            context.state = WebPLoadingContext::State::Error;
+            return Error::from_string_literal("Missing data for padding byte");
+        }
+        if (*chunks.data() != 0) {
+            context.state = WebPLoadingContext::State::Error;
+            return Error::from_string_literal("Padding byte is not 0");
+        }
+        chunks = chunks.slice(1);
+    }
+
     return chunk;
 }