LibGfx: Tolerate missing optional WebP ICC chunks
Some WebP files advertise ICC metadata in their VP8X feature flags even though the ICCP chunk is absent or malformed. We accepted the bitstream header and could decode the pixels, but the follow-up mux metadata lookup failed during header decoding and made sniffing reject the image entirely. Treat mux and ICC metadata extraction as best-effort after libwebp accepts the header. Images without readable optional ICC metadata now decode normally and simply report no ICC profile.
This commit is contained in:
parent
b024e45c2c
commit
8c06273dbf
2 changed files with 64 additions and 14 deletions
|
|
@ -119,21 +119,15 @@ static ErrorOr<void> decode_webp_header(WebPLoadingContext& context)
|
|||
}
|
||||
|
||||
WebPData webp_data { .bytes = context.data.data(), .size = context.data.size() };
|
||||
WebPMux* mux = WebPMuxCreate(&webp_data, 0);
|
||||
ScopeGuard guard { [=]() { WebPMuxDelete(mux); } };
|
||||
if (WebPMux* mux = WebPMuxCreate(&webp_data, 0)) {
|
||||
ScopeGuard guard { [=]() { WebPMuxDelete(mux); } };
|
||||
|
||||
uint32_t flag = 0;
|
||||
WebPMuxError err = WebPMuxGetFeatures(mux, &flag);
|
||||
if (err != WEBP_MUX_OK)
|
||||
return Error::from_string_literal("Failed to get webp features");
|
||||
|
||||
if (flag & ICCP_FLAG) {
|
||||
WebPData icc_profile;
|
||||
err = WebPMuxGetChunk(mux, "ICCP", &icc_profile);
|
||||
if (err != WEBP_MUX_OK)
|
||||
return Error::from_string_literal("Failed to get ICCP chunk of webp");
|
||||
|
||||
context.icc_data = TRY(context.icc_data.copy(icc_profile.bytes, icc_profile.size));
|
||||
uint32_t flag = 0;
|
||||
if (WebPMuxGetFeatures(mux, &flag) == WEBP_MUX_OK && (flag & ICCP_FLAG)) {
|
||||
WebPData icc_profile {};
|
||||
if (WebPMuxGetChunk(mux, "ICCP", &icc_profile) == WEBP_MUX_OK)
|
||||
context.icc_data = TRY(context.icc_data.copy(icc_profile.bytes, icc_profile.size));
|
||||
}
|
||||
}
|
||||
|
||||
context.state = WebPLoadingContext::State::HeaderDecoded;
|
||||
|
|
|
|||
|
|
@ -42,6 +42,48 @@ static ErrorOr<Gfx::ImageFrameDescriptor> expect_single_frame_of_size(Gfx::Image
|
|||
return frame;
|
||||
}
|
||||
|
||||
static ErrorOr<ByteBuffer> make_webp_with_declared_but_missing_iccp_chunk(ReadonlyBytes simple_webp)
|
||||
{
|
||||
VERIFY(simple_webp.size() >= 20);
|
||||
VERIFY(simple_webp.slice(0, 4) == "RIFF"sv.bytes());
|
||||
VERIFY(simple_webp.slice(8, 4) == "WEBP"sv.bytes());
|
||||
VERIFY(simple_webp.slice(12, 4) == "VP8 "sv.bytes());
|
||||
|
||||
auto vp8_chunk = simple_webp.slice(12);
|
||||
|
||||
ByteBuffer extended_webp;
|
||||
auto append_u32_le = [&](u32 value) -> ErrorOr<void> {
|
||||
TRY(extended_webp.try_append(static_cast<u8>(value)));
|
||||
TRY(extended_webp.try_append(static_cast<u8>(value >> 8)));
|
||||
TRY(extended_webp.try_append(static_cast<u8>(value >> 16)));
|
||||
TRY(extended_webp.try_append(static_cast<u8>(value >> 24)));
|
||||
return {};
|
||||
};
|
||||
auto append_u24_le = [&](u32 value) -> ErrorOr<void> {
|
||||
VERIFY(value <= 0xffffff);
|
||||
TRY(extended_webp.try_append(static_cast<u8>(value)));
|
||||
TRY(extended_webp.try_append(static_cast<u8>(value >> 8)));
|
||||
TRY(extended_webp.try_append(static_cast<u8>(value >> 16)));
|
||||
return {};
|
||||
};
|
||||
|
||||
TRY(extended_webp.try_append("RIFF"sv.bytes()));
|
||||
TRY(append_u32_le(4 + 18 + vp8_chunk.size()));
|
||||
TRY(extended_webp.try_append("WEBP"sv.bytes()));
|
||||
|
||||
TRY(extended_webp.try_append("VP8X"sv.bytes()));
|
||||
TRY(append_u32_le(10));
|
||||
TRY(extended_webp.try_append(0x20)); // ICCP flag, without a corresponding ICCP chunk.
|
||||
TRY(extended_webp.try_append(0));
|
||||
TRY(extended_webp.try_append(0));
|
||||
TRY(extended_webp.try_append(0));
|
||||
TRY(append_u24_le(239)); // simple-vp8.webp is 240x240, encoded as dimension - 1.
|
||||
TRY(append_u24_le(239));
|
||||
|
||||
TRY(extended_webp.try_append(vp8_chunk));
|
||||
return extended_webp;
|
||||
}
|
||||
|
||||
TEST_CASE(test_bmp)
|
||||
{
|
||||
auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("bmp/rgba32-1.bmp"sv)));
|
||||
|
|
@ -637,6 +679,20 @@ TEST_CASE(test_webp_simple_lossy)
|
|||
EXPECT_EQ(frame.image->get_pixel(198, 202), Gfx::Color(0x7a, 0xaa, 0xd5, 255));
|
||||
}
|
||||
|
||||
TEST_CASE(test_webp_extended_missing_declared_icc_chunk)
|
||||
{
|
||||
auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("webp/simple-vp8.webp"sv)));
|
||||
auto malformed_webp = TRY_OR_FAIL(make_webp_with_declared_but_missing_iccp_chunk(file->bytes()));
|
||||
|
||||
EXPECT(Gfx::WebPImageDecoderPlugin::sniff(malformed_webp));
|
||||
auto plugin_decoder = TRY_OR_FAIL(Gfx::WebPImageDecoderPlugin::create(malformed_webp));
|
||||
EXPECT(!TRY_OR_FAIL(plugin_decoder->icc_data()).has_value());
|
||||
|
||||
auto frame = TRY_OR_FAIL(expect_single_frame_of_size(*plugin_decoder, { 240, 240 }));
|
||||
EXPECT_EQ(frame.image->get_pixel(120, 232), Gfx::Color(0xf1, 0xef, 0xf0, 255));
|
||||
EXPECT_EQ(frame.image->get_pixel(198, 202), Gfx::Color(0x7a, 0xaa, 0xd5, 255));
|
||||
}
|
||||
|
||||
TEST_CASE(test_webp_simple_lossless)
|
||||
{
|
||||
auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("webp/simple-vp8l.webp"sv)));
|
||||
|
|
|
|||
Loading…
Reference in a new issue