mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2026-06-22 17:30:44 +00:00
LibWeb: Stream animated image frames on demand
Add AnimatedDecodedImageData which implements DecodedImageData with an 8-slot buffer pool instead of storing all frames in memory. Frames are requested on demand from the ImageDecoder service as the animation progresses. For a 344-frame animated image at 1920x1080, this reduces WebContent memory from ~1.3 GB to ~66 MB. The streaming class owns frame progression and synchronizes multiple callers (HTMLImageElement and ImageStyleValue) through notify_frame_advanced() returning the authoritative frame index. When a frame isn't in the pool, the last displayed frame is shown as a fallback (brief freeze rather than blank). Rename the old AnimatedBitmapDecodedImageData (which now only handles static/single-frame images) to BitmapDecodedImageData.
This commit is contained in:
parent
0f7df5f213
commit
97986f9739
Notes:
github-actions[bot]
2026-02-13 17:35:26 +00:00
Author: https://github.com/awesomekling
Commit: 97986f9739
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/7930
11 changed files with 459 additions and 89 deletions
|
|
@ -438,7 +438,7 @@ set(SOURCES
|
|||
HighResolutionTime/Performance.cpp
|
||||
HighResolutionTime/TimeOrigin.cpp
|
||||
HTML/AbstractWorker.cpp
|
||||
HTML/AnimatedBitmapDecodedImageData.cpp
|
||||
HTML/BitmapDecodedImageData.cpp
|
||||
HTML/AnimationFrameCallbackDriver.cpp
|
||||
HTML/AttributeNames.cpp
|
||||
HTML/AudioTrack.cpp
|
||||
|
|
@ -655,6 +655,7 @@ set(SOURCES
|
|||
HTML/SessionHistoryTraversalQueue.cpp
|
||||
HTML/ShadowRealmGlobalScope.cpp
|
||||
HTML/SharedResourceRequest.cpp
|
||||
HTML/AnimatedDecodedImageData.cpp
|
||||
HTML/SharedWorker.cpp
|
||||
HTML/SharedWorkerGlobalScope.cpp
|
||||
HTML/SourceSet.cpp
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ void ImageStyleValue::animate()
|
|||
return;
|
||||
|
||||
m_current_frame_index = (m_current_frame_index + 1) % image_data->frame_count();
|
||||
m_current_frame_index = image_data->notify_frame_advanced(m_current_frame_index);
|
||||
auto current_frame_duration = image_data->frame_duration(m_current_frame_index);
|
||||
|
||||
if (current_frame_duration != m_timer->interval())
|
||||
|
|
|
|||
|
|
@ -1,72 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibGC/Heap.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/ImmutableBitmap.h>
|
||||
#include <LibJS/Runtime/Realm.h>
|
||||
#include <LibWeb/HTML/AnimatedBitmapDecodedImageData.h>
|
||||
#include <LibWeb/Painting/DisplayListRecorder.h>
|
||||
#include <LibWeb/Painting/DisplayListRecordingContext.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(AnimatedBitmapDecodedImageData);
|
||||
|
||||
ErrorOr<GC::Ref<AnimatedBitmapDecodedImageData>> AnimatedBitmapDecodedImageData::create(JS::Realm& realm, Vector<Frame>&& frames, size_t loop_count, bool animated)
|
||||
{
|
||||
return realm.create<AnimatedBitmapDecodedImageData>(move(frames), loop_count, animated);
|
||||
}
|
||||
|
||||
AnimatedBitmapDecodedImageData::AnimatedBitmapDecodedImageData(Vector<Frame>&& frames, size_t loop_count, bool animated)
|
||||
: m_frames(move(frames))
|
||||
, m_loop_count(loop_count)
|
||||
, m_animated(animated)
|
||||
{
|
||||
}
|
||||
|
||||
AnimatedBitmapDecodedImageData::~AnimatedBitmapDecodedImageData() = default;
|
||||
|
||||
RefPtr<Gfx::ImmutableBitmap> AnimatedBitmapDecodedImageData::bitmap(size_t frame_index, Gfx::IntSize) const
|
||||
{
|
||||
if (frame_index >= m_frames.size())
|
||||
return nullptr;
|
||||
return m_frames[frame_index].bitmap;
|
||||
}
|
||||
|
||||
int AnimatedBitmapDecodedImageData::frame_duration(size_t frame_index) const
|
||||
{
|
||||
if (frame_index >= m_frames.size())
|
||||
return 0;
|
||||
return m_frames[frame_index].duration;
|
||||
}
|
||||
|
||||
Optional<CSSPixels> AnimatedBitmapDecodedImageData::intrinsic_width() const
|
||||
{
|
||||
return m_frames.first().bitmap->width();
|
||||
}
|
||||
|
||||
Optional<CSSPixels> AnimatedBitmapDecodedImageData::intrinsic_height() const
|
||||
{
|
||||
return m_frames.first().bitmap->height();
|
||||
}
|
||||
|
||||
Optional<CSSPixelFraction> AnimatedBitmapDecodedImageData::intrinsic_aspect_ratio() const
|
||||
{
|
||||
return CSSPixels(m_frames.first().bitmap->width()) / CSSPixels(m_frames.first().bitmap->height());
|
||||
}
|
||||
|
||||
Optional<Gfx::IntRect> AnimatedBitmapDecodedImageData::frame_rect(size_t frame_index) const
|
||||
{
|
||||
return m_frames[frame_index].bitmap->rect();
|
||||
}
|
||||
|
||||
void AnimatedBitmapDecodedImageData::paint(DisplayListRecordingContext& context, size_t frame_index, Gfx::IntRect dst_rect, Gfx::IntRect clip_rect, Gfx::ScalingMode scaling_mode) const
|
||||
{
|
||||
context.display_list_recorder().draw_scaled_immutable_bitmap(dst_rect, clip_rect, *m_frames[frame_index].bitmap, scaling_mode);
|
||||
}
|
||||
|
||||
}
|
||||
241
Libraries/LibWeb/HTML/AnimatedDecodedImageData.cpp
Normal file
241
Libraries/LibWeb/HTML/AnimatedDecodedImageData.cpp
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
/*
|
||||
* Copyright (c) 2026, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibGC/Heap.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/ImmutableBitmap.h>
|
||||
#include <LibJS/Runtime/Realm.h>
|
||||
#include <LibWeb/HTML/AnimatedDecodedImageData.h>
|
||||
#include <LibWeb/Painting/DisplayListRecorder.h>
|
||||
#include <LibWeb/Painting/DisplayListRecordingContext.h>
|
||||
#include <LibWeb/Platform/ImageCodecPlugin.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(AnimatedDecodedImageData);
|
||||
|
||||
HashMap<i64, GC::RawPtr<AnimatedDecodedImageData>>& AnimatedDecodedImageData::session_registry()
|
||||
{
|
||||
static HashMap<i64, GC::RawPtr<AnimatedDecodedImageData>> s_registry;
|
||||
return s_registry;
|
||||
}
|
||||
|
||||
void AnimatedDecodedImageData::install_frame_delivery_callback()
|
||||
{
|
||||
static bool s_installed = false;
|
||||
if (s_installed)
|
||||
return;
|
||||
s_installed = true;
|
||||
|
||||
Platform::ImageCodecPlugin::the().on_animation_frames_decoded = [](i64 session_id, Vector<NonnullRefPtr<Gfx::Bitmap>> bitmaps) {
|
||||
deliver_frames_for_session(session_id, move(bitmaps));
|
||||
};
|
||||
Platform::ImageCodecPlugin::the().on_animation_decode_failed = [](i64 session_id) {
|
||||
auto it = session_registry().find(session_id);
|
||||
if (it != session_registry().end()) {
|
||||
if (auto data = it->value)
|
||||
data->m_request_in_flight = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void AnimatedDecodedImageData::deliver_frames_for_session(i64 session_id, Vector<NonnullRefPtr<Gfx::Bitmap>> bitmaps)
|
||||
{
|
||||
auto it = session_registry().find(session_id);
|
||||
if (it == session_registry().end())
|
||||
return;
|
||||
if (auto data = it->value)
|
||||
data->receive_frames(move(bitmaps), data->m_last_requested_start_frame);
|
||||
}
|
||||
|
||||
GC::Ref<AnimatedDecodedImageData> AnimatedDecodedImageData::create(
|
||||
JS::Realm& realm,
|
||||
i64 session_id,
|
||||
u32 frame_count,
|
||||
u32 loop_count,
|
||||
Gfx::IntSize size,
|
||||
Gfx::ColorSpace color_space,
|
||||
Vector<u32> durations,
|
||||
Vector<NonnullRefPtr<Gfx::Bitmap>> initial_bitmaps)
|
||||
{
|
||||
auto data = realm.create<AnimatedDecodedImageData>(
|
||||
session_id, frame_count, loop_count, size, move(color_space), move(durations));
|
||||
|
||||
// Place initial bitmaps into the buffer pool.
|
||||
for (u32 i = 0; i < initial_bitmaps.size(); ++i) {
|
||||
auto& slot = data->m_buffer_slots[i % BUFFER_POOL_SIZE];
|
||||
slot.frame_index = i;
|
||||
slot.bitmap = Gfx::ImmutableBitmap::create(*initial_bitmaps[i], data->m_color_space);
|
||||
slot.generation = ++data->m_write_generation;
|
||||
}
|
||||
|
||||
data->m_highest_requested_frame = initial_bitmaps.size();
|
||||
|
||||
if (!initial_bitmaps.is_empty())
|
||||
data->m_last_displayed_bitmap = data->m_buffer_slots[0].bitmap;
|
||||
|
||||
install_frame_delivery_callback();
|
||||
session_registry().set(session_id, data.ptr());
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
AnimatedDecodedImageData::AnimatedDecodedImageData(
|
||||
i64 session_id,
|
||||
u32 frame_count,
|
||||
u32 loop_count,
|
||||
Gfx::IntSize size,
|
||||
Gfx::ColorSpace color_space,
|
||||
Vector<u32> durations)
|
||||
: m_session_id(session_id)
|
||||
, m_frame_count(frame_count)
|
||||
, m_loop_count(loop_count)
|
||||
, m_size(size)
|
||||
, m_color_space(move(color_space))
|
||||
, m_durations(move(durations))
|
||||
{
|
||||
}
|
||||
|
||||
AnimatedDecodedImageData::~AnimatedDecodedImageData() = default;
|
||||
|
||||
void AnimatedDecodedImageData::finalize()
|
||||
{
|
||||
Base::finalize();
|
||||
session_registry().remove(m_session_id);
|
||||
Platform::ImageCodecPlugin::the().stop_animation_decode(m_session_id);
|
||||
}
|
||||
|
||||
AnimatedDecodedImageData::BufferSlot const* AnimatedDecodedImageData::find_slot(u32 frame_index) const
|
||||
{
|
||||
for (auto const& slot : m_buffer_slots) {
|
||||
if (slot.frame_index == frame_index && slot.bitmap)
|
||||
return &slot;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AnimatedDecodedImageData::BufferSlot& AnimatedDecodedImageData::evict_oldest_slot()
|
||||
{
|
||||
BufferSlot* oldest = &m_buffer_slots[0];
|
||||
for (auto& slot : m_buffer_slots) {
|
||||
if (slot.generation < oldest->generation)
|
||||
oldest = &slot;
|
||||
}
|
||||
return *oldest;
|
||||
}
|
||||
|
||||
RefPtr<Gfx::ImmutableBitmap> AnimatedDecodedImageData::bitmap(size_t frame_index, Gfx::IntSize) const
|
||||
{
|
||||
if (frame_index >= m_frame_count)
|
||||
return m_last_displayed_bitmap;
|
||||
|
||||
if (auto const* slot = find_slot(frame_index)) {
|
||||
m_last_displayed_bitmap = slot->bitmap;
|
||||
return slot->bitmap;
|
||||
}
|
||||
|
||||
// Frame not in pool; return last displayed frame as fallback.
|
||||
return m_last_displayed_bitmap;
|
||||
}
|
||||
|
||||
int AnimatedDecodedImageData::frame_duration(size_t frame_index) const
|
||||
{
|
||||
if (frame_index >= m_durations.size())
|
||||
return 0;
|
||||
return m_durations[frame_index];
|
||||
}
|
||||
|
||||
Optional<CSSPixels> AnimatedDecodedImageData::intrinsic_width() const
|
||||
{
|
||||
return m_size.width();
|
||||
}
|
||||
|
||||
Optional<CSSPixels> AnimatedDecodedImageData::intrinsic_height() const
|
||||
{
|
||||
return m_size.height();
|
||||
}
|
||||
|
||||
Optional<CSSPixelFraction> AnimatedDecodedImageData::intrinsic_aspect_ratio() const
|
||||
{
|
||||
return CSSPixels(m_size.width()) / CSSPixels(m_size.height());
|
||||
}
|
||||
|
||||
Optional<Gfx::IntRect> AnimatedDecodedImageData::frame_rect(size_t) const
|
||||
{
|
||||
return Gfx::IntRect { {}, m_size };
|
||||
}
|
||||
|
||||
void AnimatedDecodedImageData::paint(DisplayListRecordingContext& context, size_t frame_index, Gfx::IntRect dst_rect, Gfx::IntRect clip_rect, Gfx::ScalingMode scaling_mode) const
|
||||
{
|
||||
auto immutable_bitmap = bitmap(frame_index);
|
||||
if (!immutable_bitmap)
|
||||
return;
|
||||
context.display_list_recorder().draw_scaled_immutable_bitmap(dst_rect, clip_rect, *immutable_bitmap, scaling_mode);
|
||||
}
|
||||
|
||||
void AnimatedDecodedImageData::receive_frames(Vector<NonnullRefPtr<Gfx::Bitmap>> bitmaps, u32 start_frame_index)
|
||||
{
|
||||
m_request_in_flight = false;
|
||||
|
||||
for (u32 i = 0; i < bitmaps.size(); ++i) {
|
||||
u32 frame_index = start_frame_index + i;
|
||||
if (frame_index >= m_frame_count)
|
||||
break;
|
||||
|
||||
// Check if this frame is already in the pool.
|
||||
if (find_slot(frame_index))
|
||||
continue;
|
||||
|
||||
auto& slot = evict_oldest_slot();
|
||||
slot.frame_index = frame_index;
|
||||
slot.bitmap = Gfx::ImmutableBitmap::create(*bitmaps[i], m_color_space);
|
||||
slot.generation = ++m_write_generation;
|
||||
}
|
||||
}
|
||||
|
||||
size_t AnimatedDecodedImageData::notify_frame_advanced(size_t caller_frame_index)
|
||||
{
|
||||
// We own the frame progression. Only advance when a caller reports
|
||||
// the expected next frame (this deduplicates multiple callers per tick).
|
||||
size_t expected_next = (m_current_frame_index + 1) % m_frame_count;
|
||||
if (caller_frame_index == expected_next) {
|
||||
m_current_frame_index = expected_next;
|
||||
maybe_request_more_frames(m_current_frame_index);
|
||||
}
|
||||
return m_current_frame_index;
|
||||
}
|
||||
|
||||
void AnimatedDecodedImageData::maybe_request_more_frames(size_t current_frame_index)
|
||||
{
|
||||
if (m_request_in_flight)
|
||||
return;
|
||||
|
||||
// Count how many frames ahead of current are in the pool.
|
||||
u32 frames_ahead = 0;
|
||||
for (u32 offset = 1; offset <= BUFFER_POOL_SIZE; ++offset) {
|
||||
u32 future_index = (current_frame_index + offset) % m_frame_count;
|
||||
if (find_slot(future_index))
|
||||
++frames_ahead;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
// Request more when buffer is less than half full, giving the decoder
|
||||
// time to respond while we still have frames to display.
|
||||
if (frames_ahead >= REQUEST_BATCH_SIZE)
|
||||
return;
|
||||
|
||||
// Determine which frame to request from.
|
||||
u32 request_start = (current_frame_index + frames_ahead + 1) % m_frame_count;
|
||||
u32 request_count = REQUEST_BATCH_SIZE;
|
||||
|
||||
m_request_in_flight = true;
|
||||
m_last_requested_start_frame = request_start;
|
||||
m_highest_requested_frame = max(m_highest_requested_frame, request_start + request_count);
|
||||
Platform::ImageCodecPlugin::the().request_animation_frames(m_session_id, request_start, request_count);
|
||||
}
|
||||
|
||||
}
|
||||
101
Libraries/LibWeb/HTML/AnimatedDecodedImageData.h
Normal file
101
Libraries/LibWeb/HTML/AnimatedDecodedImageData.h
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright (c) 2026, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Array.h>
|
||||
#include <AK/HashMap.h>
|
||||
#include <LibGC/Ptr.h>
|
||||
#include <LibGfx/ColorSpace.h>
|
||||
#include <LibGfx/Forward.h>
|
||||
#include <LibWeb/HTML/DecodedImageData.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
class AnimatedDecodedImageData final : public DecodedImageData {
|
||||
GC_CELL(AnimatedDecodedImageData, DecodedImageData);
|
||||
GC_DECLARE_ALLOCATOR(AnimatedDecodedImageData);
|
||||
|
||||
public:
|
||||
static constexpr bool OVERRIDES_FINALIZE = true;
|
||||
|
||||
static GC::Ref<AnimatedDecodedImageData> create(
|
||||
JS::Realm&,
|
||||
i64 session_id,
|
||||
u32 frame_count,
|
||||
u32 loop_count,
|
||||
Gfx::IntSize,
|
||||
Gfx::ColorSpace,
|
||||
Vector<u32> durations,
|
||||
Vector<NonnullRefPtr<Gfx::Bitmap>> initial_bitmaps);
|
||||
|
||||
virtual ~AnimatedDecodedImageData() override;
|
||||
virtual void finalize() override;
|
||||
|
||||
virtual RefPtr<Gfx::ImmutableBitmap> bitmap(size_t frame_index, Gfx::IntSize = {}) const override;
|
||||
virtual int frame_duration(size_t frame_index) const override;
|
||||
|
||||
virtual size_t frame_count() const override { return m_frame_count; }
|
||||
virtual size_t loop_count() const override { return m_loop_count; }
|
||||
virtual bool is_animated() const override { return true; }
|
||||
|
||||
virtual Optional<CSSPixels> intrinsic_width() const override;
|
||||
virtual Optional<CSSPixels> intrinsic_height() const override;
|
||||
virtual Optional<CSSPixelFraction> intrinsic_aspect_ratio() const override;
|
||||
|
||||
virtual Optional<Gfx::IntRect> frame_rect(size_t frame_index) const override;
|
||||
virtual void paint(DisplayListRecordingContext&, size_t frame_index, Gfx::IntRect dst_rect, Gfx::IntRect clip_rect, Gfx::ScalingMode) const override;
|
||||
|
||||
virtual size_t notify_frame_advanced(size_t caller_frame_index) override;
|
||||
|
||||
void receive_frames(Vector<NonnullRefPtr<Gfx::Bitmap>>, u32 start_frame_index);
|
||||
|
||||
i64 session_id() const { return m_session_id; }
|
||||
|
||||
static void deliver_frames_for_session(i64 session_id, Vector<NonnullRefPtr<Gfx::Bitmap>>);
|
||||
static void install_frame_delivery_callback();
|
||||
|
||||
private:
|
||||
static HashMap<i64, GC::RawPtr<AnimatedDecodedImageData>>& session_registry();
|
||||
|
||||
static constexpr u32 BUFFER_POOL_SIZE = 8;
|
||||
static constexpr u32 REQUEST_BATCH_SIZE = 4;
|
||||
|
||||
struct BufferSlot {
|
||||
Optional<u32> frame_index;
|
||||
RefPtr<Gfx::ImmutableBitmap> bitmap;
|
||||
u64 generation { 0 };
|
||||
};
|
||||
|
||||
AnimatedDecodedImageData(
|
||||
i64 session_id,
|
||||
u32 frame_count,
|
||||
u32 loop_count,
|
||||
Gfx::IntSize,
|
||||
Gfx::ColorSpace,
|
||||
Vector<u32> durations);
|
||||
|
||||
BufferSlot const* find_slot(u32 frame_index) const;
|
||||
BufferSlot& evict_oldest_slot();
|
||||
void maybe_request_more_frames(size_t current_frame_index);
|
||||
|
||||
i64 m_session_id;
|
||||
u32 m_frame_count;
|
||||
u32 m_loop_count;
|
||||
Gfx::IntSize m_size;
|
||||
Gfx::ColorSpace m_color_space;
|
||||
Vector<u32> m_durations;
|
||||
|
||||
Array<BufferSlot, BUFFER_POOL_SIZE> m_buffer_slots;
|
||||
mutable RefPtr<Gfx::ImmutableBitmap> m_last_displayed_bitmap;
|
||||
u64 m_write_generation { 0 };
|
||||
bool m_request_in_flight { false };
|
||||
u32 m_current_frame_index { 0 };
|
||||
u32 m_last_requested_start_frame { 0 };
|
||||
u32 m_highest_requested_frame { 0 };
|
||||
};
|
||||
|
||||
}
|
||||
72
Libraries/LibWeb/HTML/BitmapDecodedImageData.cpp
Normal file
72
Libraries/LibWeb/HTML/BitmapDecodedImageData.cpp
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibGC/Heap.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/ImmutableBitmap.h>
|
||||
#include <LibJS/Runtime/Realm.h>
|
||||
#include <LibWeb/HTML/BitmapDecodedImageData.h>
|
||||
#include <LibWeb/Painting/DisplayListRecorder.h>
|
||||
#include <LibWeb/Painting/DisplayListRecordingContext.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(BitmapDecodedImageData);
|
||||
|
||||
ErrorOr<GC::Ref<BitmapDecodedImageData>> BitmapDecodedImageData::create(JS::Realm& realm, Vector<Frame>&& frames, size_t loop_count, bool animated)
|
||||
{
|
||||
return realm.create<BitmapDecodedImageData>(move(frames), loop_count, animated);
|
||||
}
|
||||
|
||||
BitmapDecodedImageData::BitmapDecodedImageData(Vector<Frame>&& frames, size_t loop_count, bool animated)
|
||||
: m_frames(move(frames))
|
||||
, m_loop_count(loop_count)
|
||||
, m_animated(animated)
|
||||
{
|
||||
}
|
||||
|
||||
BitmapDecodedImageData::~BitmapDecodedImageData() = default;
|
||||
|
||||
RefPtr<Gfx::ImmutableBitmap> BitmapDecodedImageData::bitmap(size_t frame_index, Gfx::IntSize) const
|
||||
{
|
||||
if (frame_index >= m_frames.size())
|
||||
return nullptr;
|
||||
return m_frames[frame_index].bitmap;
|
||||
}
|
||||
|
||||
int BitmapDecodedImageData::frame_duration(size_t frame_index) const
|
||||
{
|
||||
if (frame_index >= m_frames.size())
|
||||
return 0;
|
||||
return m_frames[frame_index].duration;
|
||||
}
|
||||
|
||||
Optional<CSSPixels> BitmapDecodedImageData::intrinsic_width() const
|
||||
{
|
||||
return m_frames.first().bitmap->width();
|
||||
}
|
||||
|
||||
Optional<CSSPixels> BitmapDecodedImageData::intrinsic_height() const
|
||||
{
|
||||
return m_frames.first().bitmap->height();
|
||||
}
|
||||
|
||||
Optional<CSSPixelFraction> BitmapDecodedImageData::intrinsic_aspect_ratio() const
|
||||
{
|
||||
return CSSPixels(m_frames.first().bitmap->width()) / CSSPixels(m_frames.first().bitmap->height());
|
||||
}
|
||||
|
||||
Optional<Gfx::IntRect> BitmapDecodedImageData::frame_rect(size_t frame_index) const
|
||||
{
|
||||
return m_frames[frame_index].bitmap->rect();
|
||||
}
|
||||
|
||||
void BitmapDecodedImageData::paint(DisplayListRecordingContext& context, size_t frame_index, Gfx::IntRect dst_rect, Gfx::IntRect clip_rect, Gfx::ScalingMode scaling_mode) const
|
||||
{
|
||||
context.display_list_recorder().draw_scaled_immutable_bitmap(dst_rect, clip_rect, *m_frames[frame_index].bitmap, scaling_mode);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -11,9 +11,9 @@
|
|||
|
||||
namespace Web::HTML {
|
||||
|
||||
class AnimatedBitmapDecodedImageData final : public DecodedImageData {
|
||||
GC_CELL(AnimatedBitmapDecodedImageData, DecodedImageData);
|
||||
GC_DECLARE_ALLOCATOR(AnimatedBitmapDecodedImageData);
|
||||
class BitmapDecodedImageData final : public DecodedImageData {
|
||||
GC_CELL(BitmapDecodedImageData, DecodedImageData);
|
||||
GC_DECLARE_ALLOCATOR(BitmapDecodedImageData);
|
||||
|
||||
public:
|
||||
struct Frame {
|
||||
|
|
@ -21,8 +21,8 @@ public:
|
|||
int duration { 0 };
|
||||
};
|
||||
|
||||
static ErrorOr<GC::Ref<AnimatedBitmapDecodedImageData>> create(JS::Realm&, Vector<Frame>&&, size_t loop_count, bool animated);
|
||||
virtual ~AnimatedBitmapDecodedImageData() override;
|
||||
static ErrorOr<GC::Ref<BitmapDecodedImageData>> create(JS::Realm&, Vector<Frame>&&, size_t loop_count, bool animated);
|
||||
virtual ~BitmapDecodedImageData() override;
|
||||
|
||||
virtual RefPtr<Gfx::ImmutableBitmap> bitmap(size_t frame_index, Gfx::IntSize = {}) const override;
|
||||
virtual int frame_duration(size_t frame_index) const override;
|
||||
|
|
@ -39,7 +39,7 @@ public:
|
|||
virtual void paint(DisplayListRecordingContext&, size_t frame_index, Gfx::IntRect dst_rect, Gfx::IntRect clip_rect, Gfx::ScalingMode scaling_mode) const override;
|
||||
|
||||
private:
|
||||
AnimatedBitmapDecodedImageData(Vector<Frame>&&, size_t loop_count, bool animated);
|
||||
BitmapDecodedImageData(Vector<Frame>&&, size_t loop_count, bool animated);
|
||||
|
||||
Vector<Frame> m_frames;
|
||||
size_t m_loop_count { 0 };
|
||||
|
|
@ -32,6 +32,8 @@ public:
|
|||
virtual size_t loop_count() const = 0;
|
||||
virtual bool is_animated() const = 0;
|
||||
|
||||
virtual size_t notify_frame_advanced(size_t frame_index) { return frame_index; }
|
||||
|
||||
virtual Optional<CSSPixels> intrinsic_width() const = 0;
|
||||
virtual Optional<CSSPixels> intrinsic_height() const = 0;
|
||||
virtual Optional<CSSPixelFraction> intrinsic_aspect_ratio() const = 0;
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
#include <LibWeb/Fetch/Fetching/Fetching.h>
|
||||
#include <LibWeb/Fetch/Infrastructure/FetchController.h>
|
||||
#include <LibWeb/Fetch/Response.h>
|
||||
#include <LibWeb/HTML/AnimatedBitmapDecodedImageData.h>
|
||||
#include <LibWeb/HTML/BitmapDecodedImageData.h>
|
||||
#include <LibWeb/HTML/CORSSettingAttribute.h>
|
||||
#include <LibWeb/HTML/EventNames.h>
|
||||
#include <LibWeb/HTML/HTMLImageElement.h>
|
||||
|
|
@ -1356,6 +1356,7 @@ void HTMLImageElement::animate()
|
|||
}
|
||||
|
||||
m_current_frame_index = (m_current_frame_index + 1) % image_data->frame_count();
|
||||
m_current_frame_index = image_data->notify_frame_advanced(m_current_frame_index);
|
||||
auto current_frame_duration = image_data->frame_duration(m_current_frame_index);
|
||||
|
||||
if (current_frame_duration != m_animation_timer->interval()) {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
#include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
|
||||
#include <LibWeb/Fetch/Infrastructure/FetchController.h>
|
||||
#include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
|
||||
#include <LibWeb/HTML/AnimatedBitmapDecodedImageData.h>
|
||||
#include <LibWeb/HTML/BitmapDecodedImageData.h>
|
||||
#include <LibWeb/HTML/DecodedImageData.h>
|
||||
#include <LibWeb/HTML/ImageRequest.h>
|
||||
#include <LibWeb/HTML/ListOfAvailableImages.h>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@
|
|||
#include <LibWeb/Fetch/Infrastructure/HTTP/MIME.h>
|
||||
#include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
|
||||
#include <LibWeb/Fetch/Infrastructure/HTTP/Statuses.h>
|
||||
#include <LibWeb/HTML/AnimatedBitmapDecodedImageData.h>
|
||||
#include <LibWeb/HTML/AnimatedDecodedImageData.h>
|
||||
#include <LibWeb/HTML/BitmapDecodedImageData.h>
|
||||
#include <LibWeb/HTML/DecodedImageData.h>
|
||||
#include <LibWeb/HTML/SharedResourceRequest.h>
|
||||
#include <LibWeb/Page/Page.h>
|
||||
|
|
@ -159,14 +160,36 @@ void SharedResourceRequest::handle_successful_fetch(URL::URL const& url_string,
|
|||
}
|
||||
|
||||
auto handle_successful_bitmap_decode = [strong_this = GC::Root(*this)](Web::Platform::DecodedImage& result) -> ErrorOr<void> {
|
||||
Vector<AnimatedBitmapDecodedImageData::Frame> frames;
|
||||
for (auto& frame : result.frames) {
|
||||
frames.append(AnimatedBitmapDecodedImageData::Frame {
|
||||
.bitmap = Gfx::ImmutableBitmap::create(*frame.bitmap, result.color_space),
|
||||
.duration = static_cast<int>(frame.duration),
|
||||
});
|
||||
if (result.session_id != 0) {
|
||||
// Streaming animated decode: create AnimatedDecodedImageData.
|
||||
Vector<NonnullRefPtr<Gfx::Bitmap>> initial_bitmaps;
|
||||
initial_bitmaps.ensure_capacity(result.frames.size());
|
||||
for (auto& frame : result.frames)
|
||||
initial_bitmaps.unchecked_append(*frame.bitmap);
|
||||
|
||||
auto first_bitmap = result.frames.first().bitmap;
|
||||
auto size = first_bitmap->size();
|
||||
|
||||
strong_this->m_image_data = AnimatedDecodedImageData::create(
|
||||
strong_this->m_document->realm(),
|
||||
result.session_id,
|
||||
result.frame_count,
|
||||
result.loop_count,
|
||||
size,
|
||||
result.color_space,
|
||||
move(result.all_durations),
|
||||
move(initial_bitmaps));
|
||||
} else {
|
||||
// Single-shot decode: create BitmapDecodedImageData as before.
|
||||
Vector<BitmapDecodedImageData::Frame> frames;
|
||||
for (auto& frame : result.frames) {
|
||||
frames.append(BitmapDecodedImageData::Frame {
|
||||
.bitmap = Gfx::ImmutableBitmap::create(*frame.bitmap, result.color_space),
|
||||
.duration = static_cast<int>(frame.duration),
|
||||
});
|
||||
}
|
||||
strong_this->m_image_data = BitmapDecodedImageData::create(strong_this->m_document->realm(), move(frames), result.loop_count, result.is_animated).release_value_but_fixme_should_propagate_errors();
|
||||
}
|
||||
strong_this->m_image_data = AnimatedBitmapDecodedImageData::create(strong_this->m_document->realm(), move(frames), result.loop_count, result.is_animated).release_value_but_fixme_should_propagate_errors();
|
||||
strong_this->handle_successful_resource_load();
|
||||
return {};
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue