LibGfx: Add frame_duration() to ImageDecoderPlugin

Add a virtual method to query frame durations without decoding pixel
data. This is needed by the ImageDecoder service to extract timing
metadata upfront for streaming animation decode.

Implement the method for GIF, PNG, WebP, AVIF, and JPEGXL decoders.
For WebP, extract durations from the demuxer during header decode so
they are available before any frames are decoded.
This commit is contained in:
Andreas Kling 2026-02-13 14:59:27 +01:00 committed by Andreas Kling
parent 01334c4481
commit 2d811fb432
11 changed files with 79 additions and 0 deletions

View file

@ -186,6 +186,13 @@ ErrorOr<ImageFrameDescriptor> AVIFImageDecoderPlugin::frame(size_t index, Option
return m_context->frame_descriptors[index];
}
int AVIFImageDecoderPlugin::frame_duration(size_t index)
{
if (index >= m_context->frame_descriptors.size())
return 0;
return m_context->frame_descriptors[index].duration;
}
ErrorOr<Optional<ReadonlyBytes>> AVIFImageDecoderPlugin::icc_data()
{
if (m_context->state < AVIFLoadingContext::State::HeaderDecoded)

View file

@ -27,6 +27,7 @@ public:
virtual size_t frame_count() override;
virtual size_t first_animated_frame_index() override;
virtual ErrorOr<ImageFrameDescriptor> frame(size_t index, Optional<IntSize> ideal_size = {}) override;
virtual int frame_duration(size_t index) override;
virtual ErrorOr<Optional<ReadonlyBytes>> icc_data() override;
private:

View file

@ -469,6 +469,27 @@ size_t GIFImageDecoderPlugin::first_animated_frame_index()
return 0;
}
int GIFImageDecoderPlugin::frame_duration(size_t index)
{
if (m_context->error_state != GIFLoadingContext::ErrorState::NoError)
return 0;
if (m_context->state < GIFLoadingContext::State::FrameDescriptorsLoaded) {
if (load_gif_frame_descriptors(*m_context).is_error()) {
m_context->error_state = GIFLoadingContext::ErrorState::FailedToLoadFrameDescriptors;
return 0;
}
}
if (index >= m_context->images.size())
return 0;
int duration = m_context->images[index]->duration * 10;
if (duration <= 10)
duration = 100;
return duration;
}
ErrorOr<ImageFrameDescriptor> GIFImageDecoderPlugin::frame(size_t index, Optional<IntSize>)
{
if (m_context->error_state == GIFLoadingContext::ErrorState::FailedToDecodeAnyFrame) {

View file

@ -29,6 +29,7 @@ public:
virtual size_t frame_count() override;
virtual size_t first_animated_frame_index() override;
virtual ErrorOr<ImageFrameDescriptor> frame(size_t index, Optional<IntSize> ideal_size = {}) override;
virtual int frame_duration(size_t index) override;
private:
GIFImageDecoderPlugin(FixedMemoryStream);

View file

@ -83,6 +83,14 @@ public:
virtual ErrorOr<ImageFrameDescriptor> frame(size_t index, Optional<IntSize> ideal_size = {}) = 0;
// Returns the duration of a frame in milliseconds without decoding pixel data.
// Not all formats support this; the default returns 0.
virtual int frame_duration(size_t index)
{
(void)index;
return 0;
}
virtual Optional<Metadata const&> metadata() { return OptionalNone {}; }
virtual ErrorOr<Optional<Media::CodingIndependentCodePoints>> cicp() { return OptionalNone {}; }
@ -110,6 +118,7 @@ public:
size_t first_animated_frame_index() const { return m_plugin->first_animated_frame_index(); }
ErrorOr<ImageFrameDescriptor> frame(size_t index, Optional<IntSize> ideal_size = {}) const { return m_plugin->frame(index, ideal_size); }
int frame_duration(size_t index) const { return m_plugin->frame_duration(index); }
Optional<Metadata const&> metadata() const { return m_plugin->metadata(); }
ErrorOr<ColorSpace> color_space();

View file

@ -266,6 +266,13 @@ ErrorOr<ImageFrameDescriptor> JPEGXLImageDecoderPlugin::frame(size_t index, Opti
return m_context->frame_descriptors()[index];
}
int JPEGXLImageDecoderPlugin::frame_duration(size_t index)
{
if (index >= m_context->frame_descriptors().size())
return 0;
return m_context->frame_descriptors()[index].duration;
}
ErrorOr<Optional<ReadonlyBytes>> JPEGXLImageDecoderPlugin::icc_data()
{
return OptionalNone {};

View file

@ -27,6 +27,7 @@ public:
virtual ErrorOr<Optional<ReadonlyBytes>> icc_data() override;
virtual ErrorOr<ImageFrameDescriptor> frame(size_t index, Optional<IntSize> ideal_size = {}) override;
virtual int frame_duration(size_t index) override;
private:
JPEGXLImageDecoderPlugin(OwnPtr<JPEGXLLoadingContext>);

View file

@ -104,6 +104,13 @@ size_t PNGImageDecoderPlugin::frame_count()
return m_context->frame_count;
}
int PNGImageDecoderPlugin::frame_duration(size_t index)
{
if (index >= m_context->frame_descriptors.size())
return 0;
return m_context->frame_descriptors[index].duration;
}
ErrorOr<ImageFrameDescriptor> PNGImageDecoderPlugin::frame(size_t index, Optional<IntSize>)
{
if (index >= m_context->frame_descriptors.size())

View file

@ -26,6 +26,7 @@ public:
virtual size_t frame_count() override;
virtual size_t first_animated_frame_index() override;
virtual ErrorOr<ImageFrameDescriptor> frame(size_t index, Optional<IntSize> ideal_size = {}) override;
virtual int frame_duration(size_t index) override;
virtual Optional<Metadata const&> metadata() override;
virtual ErrorOr<Optional<Media::CodingIndependentCodePoints>> cicp() override;
virtual ErrorOr<Optional<ReadonlyBytes>> icc_data() override;

View file

@ -32,6 +32,7 @@ struct WebPLoadingContext {
size_t frame_count;
size_t loop_count;
ByteBuffer icc_data;
Vector<int> frame_durations;
Vector<ImageFrameDescriptor> frame_descriptors;
};
@ -91,6 +92,19 @@ static ErrorOr<void> decode_webp_header(WebPLoadingContext& context)
context.frame_count = anim_info.frame_count;
context.loop_count = anim_info.loop_count;
// Extract frame durations from the demuxer without decoding pixels.
auto* demux = WebPDemux(&webp_data);
if (demux) {
WebPIterator iter {};
if (WebPDemuxGetFrame(demux, 1, &iter)) {
do {
context.frame_durations.append(iter.duration);
} while (WebPDemuxNextFrame(&iter));
WebPDemuxReleaseIterator(&iter);
}
WebPDemuxDelete(demux);
}
}
WebPData webp_data { .bytes = context.data.data(), .size = context.data.size() };
@ -222,6 +236,15 @@ ErrorOr<ImageFrameDescriptor> WebPImageDecoderPlugin::frame(size_t index, Option
return m_context->frame_descriptors[index];
}
int WebPImageDecoderPlugin::frame_duration(size_t index)
{
if (index < m_context->frame_durations.size())
return m_context->frame_durations[index];
if (index < m_context->frame_descriptors.size())
return m_context->frame_descriptors[index].duration;
return 0;
}
ErrorOr<Optional<ReadonlyBytes>> WebPImageDecoderPlugin::icc_data()
{
if (m_context->state < WebPLoadingContext::State::HeaderDecoded)

View file

@ -26,6 +26,7 @@ public:
virtual size_t frame_count() override;
virtual size_t first_animated_frame_index() override;
virtual ErrorOr<ImageFrameDescriptor> frame(size_t index, Optional<IntSize> ideal_size = {}) override;
virtual int frame_duration(size_t index) override;
virtual ErrorOr<Optional<ReadonlyBytes>> icc_data() override;
private: