mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-29 12:19:54 +00:00
LibGfx: Only include frames with fcTL chunks in the animation
Some checks are pending
CI / Lagom (arm64, Sanitizer_CI, false, macos-15, macOS, Clang) (push) Waiting to run
CI / Lagom (x86_64, Fuzzers_CI, false, ubuntu-24.04, Linux, Clang) (push) Waiting to run
CI / Lagom (x86_64, Sanitizer_CI, false, ubuntu-24.04, Linux, GNU) (push) Waiting to run
CI / Lagom (x86_64, Sanitizer_CI, true, ubuntu-24.04, Linux, Clang) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (arm64, macos-15, macOS, macOS-universal2) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (x86_64, ubuntu-24.04, Linux, Linux-x86_64) (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run
Some checks are pending
CI / Lagom (arm64, Sanitizer_CI, false, macos-15, macOS, Clang) (push) Waiting to run
CI / Lagom (x86_64, Fuzzers_CI, false, ubuntu-24.04, Linux, Clang) (push) Waiting to run
CI / Lagom (x86_64, Sanitizer_CI, false, ubuntu-24.04, Linux, GNU) (push) Waiting to run
CI / Lagom (x86_64, Sanitizer_CI, true, ubuntu-24.04, Linux, Clang) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (arm64, macos-15, macOS, macOS-universal2) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (x86_64, ubuntu-24.04, Linux, Linux-x86_64) (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run
Before this change, IDAT data was mistakenly always included in the animation. Now we only include frames with explicit fcTL chunks. As per the PNG spec (third edition): "The static image may be included as the first frame of the animation by the presence of a single fcTL chunk before IDAT. Otherwise, the static image is not part of the animation." We also fall back to the IDAT data when APNG has acTL but no fcTL chunks. Test image is 062.png from fDAT-inherits-cICP.html from WPT.
This commit is contained in:
parent
b77643a2e8
commit
71a4e18bf8
Notes:
github-actions[bot]
2025-05-01 08:31:00 +00:00
Author: https://github.com/aplefull
Commit: 71a4e18bf8
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/4534
Reviewed-by: https://github.com/LucasChollet
Reviewed-by: https://github.com/gmta ✅
4 changed files with 33 additions and 7 deletions
|
@ -267,6 +267,7 @@ ErrorOr<size_t> PNGLoadingContext::read_frames(png_structp png_ptr, png_infop in
|
||||||
// Conceptually, at the beginning of each play the output buffer must be completely initialized to a fully transparent black rectangle, with width and height dimensions from the `IHDR` chunk.
|
// Conceptually, at the beginning of each play the output buffer must be completely initialized to a fully transparent black rectangle, with width and height dimensions from the `IHDR` chunk.
|
||||||
auto output_buffer = TRY(Bitmap::create(BitmapFormat::BGRA8888, AlphaType::Unpremultiplied, size));
|
auto output_buffer = TRY(Bitmap::create(BitmapFormat::BGRA8888, AlphaType::Unpremultiplied, size));
|
||||||
auto painter = Painter::create(output_buffer);
|
auto painter = Painter::create(output_buffer);
|
||||||
|
size_t animation_frame_count = 0;
|
||||||
|
|
||||||
for (size_t frame_index = 0; frame_index < frame_count; ++frame_index) {
|
for (size_t frame_index = 0; frame_index < frame_count; ++frame_index) {
|
||||||
png_read_frame_head(png_ptr, info_ptr);
|
png_read_frame_head(png_ptr, info_ptr);
|
||||||
|
@ -289,7 +290,8 @@ ErrorOr<size_t> PNGLoadingContext::read_frames(png_structp png_ptr, png_infop in
|
||||||
return static_cast<int>(unsigned_duration_ms);
|
return static_cast<int>(unsigned_duration_ms);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_fcTL)) {
|
bool has_fcTL = png_get_valid(png_ptr, info_ptr, PNG_INFO_fcTL);
|
||||||
|
if (has_fcTL) {
|
||||||
png_get_next_frame_fcTL(png_ptr, info_ptr, &width, &height, &x, &y, &delay_num, &delay_den, &dispose_op, &blend_op);
|
png_get_next_frame_fcTL(png_ptr, info_ptr, &width, &height, &x, &y, &delay_num, &delay_den, &dispose_op, &blend_op);
|
||||||
} else {
|
} else {
|
||||||
width = png_get_image_width(png_ptr, info_ptr);
|
width = png_get_image_width(png_ptr, info_ptr);
|
||||||
|
@ -316,7 +318,10 @@ ErrorOr<size_t> PNGLoadingContext::read_frames(png_structp png_ptr, png_infop in
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
frame_descriptors.append({ TRY(output_buffer->clone()), duration_ms() });
|
if (has_fcTL) {
|
||||||
|
animation_frame_count++;
|
||||||
|
frame_descriptors.append({ TRY(output_buffer->clone()), duration_ms() });
|
||||||
|
}
|
||||||
|
|
||||||
switch (dispose_op) {
|
switch (dispose_op) {
|
||||||
case PNG_DISPOSE_OP_NONE:
|
case PNG_DISPOSE_OP_NONE:
|
||||||
|
@ -334,6 +339,15 @@ ErrorOr<size_t> PNGLoadingContext::read_frames(png_structp png_ptr, png_infop in
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frame_count = animation_frame_count;
|
||||||
|
|
||||||
|
// If we didn't find any valid animation frames with fcTL chunks, fall back to using the base IDAT data as a single frame.
|
||||||
|
if (frame_count == 0) {
|
||||||
|
auto frame_bitmap = TRY(decode_frame(size));
|
||||||
|
frame_descriptors.append({ move(frame_bitmap), 0 });
|
||||||
|
frame_count = 1;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// This is a single-frame PNG.
|
// This is a single-frame PNG.
|
||||||
frame_count = 1;
|
frame_count = 1;
|
||||||
|
@ -342,7 +356,7 @@ ErrorOr<size_t> PNGLoadingContext::read_frames(png_structp png_ptr, png_infop in
|
||||||
auto decoded_frame_bitmap = TRY(decode_frame(size));
|
auto decoded_frame_bitmap = TRY(decode_frame(size));
|
||||||
frame_descriptors.append({ move(decoded_frame_bitmap), 0 });
|
frame_descriptors.append({ move(decoded_frame_bitmap), 0 });
|
||||||
}
|
}
|
||||||
return this->frame_count;
|
return frame_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
PNGImageDecoderPlugin::~PNGImageDecoderPlugin() = default;
|
PNGImageDecoderPlugin::~PNGImageDecoderPlugin() = default;
|
||||||
|
|
|
@ -363,6 +363,22 @@ TEST_CASE(test_png)
|
||||||
TRY_OR_FAIL(expect_single_frame(*plugin_decoder));
|
TRY_OR_FAIL(expect_single_frame(*plugin_decoder));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE(test_apng)
|
||||||
|
{
|
||||||
|
auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("png/apng-1-frame.png"sv)));
|
||||||
|
EXPECT(Gfx::PNGImageDecoderPlugin::sniff(file->bytes()));
|
||||||
|
auto plugin_decoder = TRY_OR_FAIL(Gfx::PNGImageDecoderPlugin::create(file->bytes()));
|
||||||
|
|
||||||
|
EXPECT_EQ(plugin_decoder->frame_count(), 1u);
|
||||||
|
EXPECT_EQ(plugin_decoder->loop_count(), 0u);
|
||||||
|
|
||||||
|
auto frame = TRY_OR_FAIL(plugin_decoder->frame(0));
|
||||||
|
|
||||||
|
EXPECT_EQ(frame.duration, 1000);
|
||||||
|
EXPECT_EQ(frame.image->get_pixel(64, 32), Gfx::Color(117, 252, 76));
|
||||||
|
EXPECT_EQ(frame.image->size(), Gfx::IntSize(128, 64));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE(test_exif)
|
TEST_CASE(test_exif)
|
||||||
{
|
{
|
||||||
auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("png/exif.png"sv)));
|
auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("png/exif.png"sv)));
|
||||||
|
|
BIN
Tests/LibGfx/test-inputs/png/apng-1-frame.png
Normal file
BIN
Tests/LibGfx/test-inputs/png/apng-1-frame.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 445 B |
|
@ -323,7 +323,3 @@ Text/input/WebSocket/echo.html
|
||||||
|
|
||||||
; Times out due to us not implementing auto-commit the correct way.
|
; Times out due to us not implementing auto-commit the correct way.
|
||||||
Text/input/wpt-import/IndexedDB/idbfactory_open.any.html
|
Text/input/wpt-import/IndexedDB/idbfactory_open.any.html
|
||||||
|
|
||||||
; Inconsistently fails on CI - we seem to wrongly include the non-animated data in the animation.
|
|
||||||
; https://github.com/LadybirdBrowser/ladybird/issues/4519
|
|
||||||
Ref/input/wpt-import/png/apng/fDAT-inherits-cICP.html
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue