diff --git a/Tests/LibGfx/TestICCProfile.cpp b/Tests/LibGfx/TestICCProfile.cpp index 816d3d8e40..bae5202dd1 100644 --- a/Tests/LibGfx/TestICCProfile.cpp +++ b/Tests/LibGfx/TestICCProfile.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #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))); diff --git a/Tests/LibGfx/test-inputs/extended-lossless.webp b/Tests/LibGfx/test-inputs/extended-lossless.webp new file mode 100644 index 0000000000..9f20fced92 Binary files /dev/null and b/Tests/LibGfx/test-inputs/extended-lossless.webp differ diff --git a/Tests/LibGfx/test-inputs/extended-lossy.webp b/Tests/LibGfx/test-inputs/extended-lossy.webp new file mode 100644 index 0000000000..87f2ad936f Binary files /dev/null and b/Tests/LibGfx/test-inputs/extended-lossy.webp differ diff --git a/Userland/Libraries/LibGfx/WebPLoader.cpp b/Userland/Libraries/LibGfx/WebPLoader.cpp index 19755e6003..4d3c7bb766 100644 --- a/Userland/Libraries/LibGfx/WebPLoader.cpp +++ b/Userland/Libraries/LibGfx/WebPLoader.cpp @@ -110,6 +110,7 @@ static ErrorOr decode_webp_header(WebPLoadingContext& context) return {}; } +// https://developers.google.com/speed/webp/docs/riff_container#riff_file_format static ErrorOr decode_webp_chunk_header(WebPLoadingContext& context, ReadonlyBytes chunks) { if (chunks.size() < sizeof(ChunkHeader)) { @@ -128,10 +129,29 @@ static ErrorOr 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 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; }