LibGfx: Fix handling of partially corrupt GIFs

GIF loader was completely failing when encountering errors with
frame descriptors or individual frames, even when some frames were
successfully loaded. Now we attempt to decode at least some frames
and fail only when no frames can be decoded at all.
This commit is contained in:
aplefull 2025-03-20 13:59:01 +01:00 committed by Jelle Raaijmakers
parent 54351e7327
commit 57d0c563e0
Notes: github-actions[bot] 2025-03-20 15:13:57 +00:00
4 changed files with 26 additions and 14 deletions

View file

@ -468,29 +468,31 @@ size_t GIFImageDecoderPlugin::first_animated_frame_index()
ErrorOr<ImageFrameDescriptor> GIFImageDecoderPlugin::frame(size_t index, Optional<IntSize>)
{
if (m_context->error_state >= GIFLoadingContext::ErrorState::FailedToDecodeAnyFrame) {
if (m_context->error_state == GIFLoadingContext::ErrorState::FailedToDecodeAnyFrame) {
return Error::from_string_literal("GIFImageDecoderPlugin: Decoding failed");
}
if (m_context->state < GIFLoadingContext::State::FrameDescriptorsLoaded) {
if (auto result = load_gif_frame_descriptors(*m_context); result.is_error()) {
m_context->error_state = GIFLoadingContext::ErrorState::FailedToLoadFrameDescriptors;
return result.release_error();
// If we failed to load frame descriptors but we have some images, we can still try to decode them.
if (m_context->images.is_empty()) {
return result.release_error();
}
}
}
if (m_context->error_state == GIFLoadingContext::ErrorState::NoError) {
if (auto result = decode_frame(*m_context, index); result.is_error()) {
if (m_context->state < GIFLoadingContext::State::FrameComplete) {
m_context->error_state = GIFLoadingContext::ErrorState::FailedToDecodeAnyFrame;
return result.release_error();
}
if (auto result = decode_frame(*m_context, 0); result.is_error()) {
m_context->error_state = GIFLoadingContext::ErrorState::FailedToDecodeAnyFrame;
return result.release_error();
}
m_context->error_state = GIFLoadingContext::ErrorState::FailedToDecodeAllFrames;
if (auto result = decode_frame(*m_context, index); result.is_error()) {
if (m_context->state < GIFLoadingContext::State::FrameComplete) {
m_context->error_state = GIFLoadingContext::ErrorState::FailedToDecodeAnyFrame;
return result.release_error();
}
if (auto result = decode_frame(*m_context, 0); result.is_error()) {
m_context->error_state = GIFLoadingContext::ErrorState::FailedToDecodeAnyFrame;
return result.release_error();
}
m_context->error_state = GIFLoadingContext::ErrorState::FailedToDecodeAllFrames;
}
ImageFrameDescriptor frame {};

View file

@ -93,7 +93,7 @@ TEST_CASE(test_ico_malformed_frame)
TEST_CASE(test_gif)
{
auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("download-animation.gif"sv)));
auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("gif/download-animation.gif"sv)));
EXPECT(Gfx::GIFImageDecoderPlugin::sniff(file->bytes()));
auto plugin_decoder = TRY_OR_FAIL(Gfx::GIFImageDecoderPlugin::create(file->bytes()));
@ -105,6 +105,16 @@ TEST_CASE(test_gif)
EXPECT(frame.duration == 400);
}
TEST_CASE(test_corrupted_gif)
{
auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("gif/corrupted.gif"sv)));
EXPECT(Gfx::GIFImageDecoderPlugin::sniff(file->bytes()));
auto plugin_decoder = TRY_OR_FAIL(Gfx::GIFImageDecoderPlugin::create(file->bytes()));
auto frame = TRY_OR_FAIL(plugin_decoder->frame(0));
EXPECT_EQ(plugin_decoder->frame_count(), 1u);
}
TEST_CASE(test_gif_without_global_color_table)
{
Array<u8, 35> gif_data {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB