Skip to content

Commit aba435a

Browse files
LucasCholletawesomekling
authored andcommitted
LibGfx/JPEGXL: Add support for animated images
1 parent d9a2c62 commit aba435a

File tree

1 file changed

+72
-21
lines changed

1 file changed

+72
-21
lines changed

Userland/Libraries/LibGfx/ImageFormats/JPEGXLLoader.cpp

Lines changed: 72 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,30 @@ class JPEGXLLoadingContext {
5353
return m_size;
5454
}
5555

56-
RefPtr<Bitmap> bitmap() const
56+
Vector<ImageFrameDescriptor> const& frame_descriptors() const
5757
{
58-
return m_bitmap;
58+
return m_frame_descriptors;
59+
}
60+
61+
bool is_animated() const
62+
{
63+
return m_animated;
64+
}
65+
66+
u32 loop_count() const
67+
{
68+
return m_loop_count;
69+
}
70+
71+
u32 frame_count() const
72+
{
73+
return m_frame_count;
5974
}
6075

6176
private:
6277
ErrorOr<void> run_state_machine_until(State requested_state)
6378
{
79+
Optional<u32> frame_duration;
6480
for (;;) {
6581
auto const status = JxlDecoderProcessInput(m_decoder);
6682

@@ -75,19 +91,36 @@ class JPEGXLLoadingContext {
7591
return {};
7692
}
7793

94+
if (status == JXL_DEC_FRAME) {
95+
JxlFrameHeader header;
96+
if (auto res = JxlDecoderGetFrameHeader(m_decoder, &header);
97+
res != JXL_DEC_SUCCESS)
98+
return Error::from_string_literal("JPEGXLImageDecoderPlugin: Unable to retrieve frame header.");
99+
100+
frame_duration = header.duration;
101+
continue;
102+
}
103+
78104
if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
79-
TRY(set_output_buffer());
105+
if (!frame_duration.has_value())
106+
return Error::from_string_literal("JPEGXLImageDecoderPlugin: No frame header was read.");
107+
108+
TRY(set_output_buffer(*frame_duration));
80109
continue;
81110
}
82111

83112
if (status == JXL_DEC_FULL_IMAGE) {
84-
// Called once per frame, let's return for now
85-
return {};
113+
m_frame_count++;
114+
continue;
86115
}
87116

88-
if (status == JXL_DEC_SUCCESS)
117+
if (status == JXL_DEC_SUCCESS) {
118+
if (m_state != State::Error)
119+
m_state = State::FrameDecoded;
89120
return {};
121+
}
90122

123+
warnln("JPEGXLImageDecoderPlugin: Unknown event.");
91124
return Error::from_string_literal("JPEGXLImageDecoderPlugin: Unknown event.");
92125
}
93126
}
@@ -101,34 +134,44 @@ class JPEGXLLoadingContext {
101134

102135
m_size = { info.xsize, info.ysize };
103136

137+
m_animated = static_cast<bool>(info.have_animation);
138+
139+
if (m_animated)
140+
m_loop_count = info.animation.num_loops;
141+
104142
m_state = State::HeaderDecoded;
105143
return {};
106144
}
107145

108-
ErrorOr<void> set_output_buffer()
146+
ErrorOr<void> set_output_buffer(u32 duration)
109147
{
110-
auto result = [this]() -> ErrorOr<void> {
148+
auto result = [this, duration]() -> ErrorOr<void> {
111149
if (JxlDecoderProcessInput(m_decoder) != JXL_DEC_NEED_IMAGE_OUT_BUFFER)
112150
return Error::from_string_literal("JPEGXLImageDecoderPlugin: Decoder is in an unexpected state.");
113151

114-
m_bitmap = TRY(Bitmap::create(Gfx::BitmapFormat::RGBA8888, m_size));
152+
auto bitmap = TRY(Bitmap::create(Gfx::BitmapFormat::RGBA8888, m_size));
153+
TRY(m_frame_descriptors.try_empend(bitmap, static_cast<int>(duration)));
115154

116155
JxlPixelFormat format = { 4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0 };
117156

118157
size_t needed_size = 0;
119158
JxlDecoderImageOutBufferSize(m_decoder, &format, &needed_size);
120159

121-
if (needed_size != m_bitmap->size_in_bytes())
160+
if (needed_size != bitmap->size_in_bytes())
122161
return Error::from_string_literal("JPEGXLImageDecoderPlugin: Expected bitmap size is wrong.");
123162

124-
if (auto res = JxlDecoderSetImageOutBuffer(m_decoder, &format, m_bitmap->begin(), m_bitmap->size_in_bytes());
163+
if (auto res = JxlDecoderSetImageOutBuffer(m_decoder, &format, bitmap->begin(), bitmap->size_in_bytes());
125164
res != JXL_DEC_SUCCESS)
126165
return Error::from_string_literal("JPEGXLImageDecoderPlugin: Unable to decode frame.");
127166

128167
return {};
129168
}();
130169

131-
m_state = result.is_error() ? State::Error : State::FrameDecoded;
170+
if (result.is_error()) {
171+
m_state = State::Error;
172+
warnln("{}", result.error());
173+
}
174+
132175
return result;
133176
}
134177

@@ -137,7 +180,11 @@ class JPEGXLLoadingContext {
137180
JxlDecoder* m_decoder;
138181

139182
IntSize m_size;
140-
RefPtr<Bitmap> m_bitmap;
183+
Vector<ImageFrameDescriptor> m_frame_descriptors;
184+
185+
bool m_animated { false };
186+
u32 m_loop_count { 0 };
187+
u32 m_frame_count { 0 };
141188
};
142189

143190
JPEGXLImageDecoderPlugin::JPEGXLImageDecoderPlugin(OwnPtr<JPEGXLLoadingContext> context)
@@ -164,7 +211,8 @@ ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> JPEGXLImageDecoderPlugin::create(Read
164211
if (!decoder)
165212
return Error::from_errno(ENOMEM);
166213

167-
if (auto res = JxlDecoderSubscribeEvents(decoder, JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE); res == JXL_DEC_ERROR)
214+
auto const events = JXL_DEC_BASIC_INFO | JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE;
215+
if (auto res = JxlDecoderSubscribeEvents(decoder, events); res == JXL_DEC_ERROR)
168216
return Error::from_string_literal("JPEGXLImageDecoderPlugin: Unable to subscribe to events.");
169217

170218
if (auto res = JxlDecoderSetInput(decoder, data.data(), data.size()); res == JXL_DEC_ERROR)
@@ -182,17 +230,21 @@ ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> JPEGXLImageDecoderPlugin::create(Read
182230

183231
bool JPEGXLImageDecoderPlugin::is_animated()
184232
{
185-
return false;
233+
return m_context->is_animated();
186234
}
187235

188236
size_t JPEGXLImageDecoderPlugin::loop_count()
189237
{
190-
return 0;
238+
return m_context->loop_count();
191239
}
192240

193241
size_t JPEGXLImageDecoderPlugin::frame_count()
194242
{
195-
return 1;
243+
// FIXME: There doesn't seem to be a way to have that information
244+
// before decoding all the frames.
245+
if (m_context->frame_count() == 0)
246+
(void)frame(0);
247+
return m_context->frame_count();
196248
}
197249

198250
size_t JPEGXLImageDecoderPlugin::first_animated_frame_index()
@@ -202,16 +254,15 @@ size_t JPEGXLImageDecoderPlugin::first_animated_frame_index()
202254

203255
ErrorOr<ImageFrameDescriptor> JPEGXLImageDecoderPlugin::frame(size_t index, Optional<IntSize>)
204256
{
205-
if (index > 0)
206-
return Error::from_string_literal("JPEGXLImageDecoderPlugin: Invalid frame index.");
207-
208257
if (m_context->state() == JPEGXLLoadingContext::State::Error)
209258
return Error::from_string_literal("JPEGXLImageDecoderPlugin: Decoding failed.");
210259

211260
if (m_context->state() < JPEGXLLoadingContext::State::FrameDecoded)
212261
TRY(m_context->decode_image());
213262

214-
return ImageFrameDescriptor { m_context->bitmap(), 0 };
263+
if (index >= m_context->frame_descriptors().size())
264+
return Error::from_string_literal("JPEGXLImageDecoderPlugin: Invalid frame index requested.");
265+
return m_context->frame_descriptors()[index];
215266
}
216267

217268
ErrorOr<Optional<ReadonlyBytes>> JPEGXLImageDecoderPlugin::icc_data()

0 commit comments

Comments
 (0)