summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Nelson <peter@peterdn.com>2020-08-29 17:48:17 +0100
committerAndreas Kling <kling@serenityos.org>2020-08-30 10:27:36 +0200
commitc8eccc00b14c133154974aba784d11403e01936a (patch)
treeefbfa8a0211c43efad48e7f485ae9ad30f63384c
parent786872e4c9f3fea255af763b914058c1bcc434b5 (diff)
downloadserenity-c8eccc00b14c133154974aba784d11403e01936a.zip
LibGfx: only cache last decoded GIF frame
GIFLoader now uses a single frame buffer to cache the last decoded frame. This drastically reduces memory usage at the small expense of re-decoding frames on each loop.
-rw-r--r--Libraries/LibGfx/GIFLoader.cpp76
1 files changed, 37 insertions, 39 deletions
diff --git a/Libraries/LibGfx/GIFLoader.cpp b/Libraries/LibGfx/GIFLoader.cpp
index 773f3e7283..b849080206 100644
--- a/Libraries/LibGfx/GIFLoader.cpp
+++ b/Libraries/LibGfx/GIFLoader.cpp
@@ -56,7 +56,6 @@ struct ImageDescriptor {
RGB color_map[256];
u8 lzw_min_code_size { 0 };
Vector<u8> lzw_encoded_bytes;
- RefPtr<Gfx::Bitmap> bitmap;
// Fields from optional graphic control extension block
enum DisposalMethod : u8 {
@@ -83,15 +82,17 @@ struct GIFLoadingContext {
NotDecoded = 0,
Error,
FrameDescriptorsLoaded,
+ FrameComplete,
};
State state { NotDecoded };
- size_t frames_decoded { 0 };
const u8* data { nullptr };
size_t data_size { 0 };
LogicalScreen logical_screen {};
u8 background_color_index { 0 };
NonnullOwnPtrVector<ImageDescriptor> images {};
size_t loops { 1 };
+ RefPtr<Gfx::Bitmap> frame_buffer;
+ size_t current_frame { 0 };
};
RefPtr<Gfx::Bitmap> load_gif(const StringView& path)
@@ -265,27 +266,28 @@ private:
Vector<u8> m_output {};
};
-static void copy_frame(Bitmap& destination, Bitmap& source)
-{
- for (int y = 0; y < source.height(); ++y) {
- for (int x = 0; x < source.width(); ++x) {
- destination.set_pixel(x, y, source.get_pixel(x, y));
- }
- }
-}
-
-static bool decode_frames_up_to_index(GIFLoadingContext& context, size_t frame_index)
+static bool decode_frame(GIFLoadingContext& context, size_t frame_index)
{
if (frame_index >= context.images.size()) {
return false;
}
- for (size_t i = context.frames_decoded; i <= frame_index; ++i) {
+ if (context.state >= GIFLoadingContext::State::FrameComplete && frame_index == context.current_frame) {
+ return true;
+ }
+
+ size_t start_frame = context.current_frame + 1;
+ if (context.state < GIFLoadingContext::State::FrameComplete) {
+ start_frame = 0;
+ context.frame_buffer = Bitmap::create_purgeable(BitmapFormat::RGBA32, { context.logical_screen.width, context.logical_screen.height });
+ } else if (frame_index < context.current_frame) {
+ start_frame = 0;
+ }
+
+ for (size_t i = start_frame; i <= frame_index; ++i) {
auto& image = context.images.at(i);
printf("Image %zu: %d,%d %dx%d %zu bytes LZW-encoded\n", i, image.x, image.y, image.width, image.height, image.lzw_encoded_bytes.size());
- dbg() << "Decoding frame: " << i + 1 << " of " << context.images.size();
-
LZWDecoder decoder(image.lzw_encoded_bytes, image.lzw_min_code_size);
// Add GIF-specific control codes
@@ -297,20 +299,17 @@ static bool decode_frames_up_to_index(GIFLoadingContext& context, size_t frame_i
auto background_rgb = color_map[context.background_color_index];
Color background_color = Color(background_rgb.r, background_rgb.g, background_rgb.b);
- image.bitmap = Bitmap::create_purgeable(BitmapFormat::RGBA32, { context.logical_screen.width, context.logical_screen.height });
-
if (i == 0) {
- image.bitmap->fill(background_color);
+ context.frame_buffer->fill(background_color);
}
const auto previous_image_disposal_method = i > 0 ? context.images.at(i - 1).disposal_method : ImageDescriptor::DisposalMethod::None;
- if (i > 0 && (previous_image_disposal_method == ImageDescriptor::DisposalMethod::None || previous_image_disposal_method == ImageDescriptor::DisposalMethod::InPlace)) {
- copy_frame(*image.bitmap, *context.images.at(i - 1).bitmap);
- } else if (previous_image_disposal_method == ImageDescriptor::DisposalMethod::RestoreBackground) {
- image.bitmap->fill(Color(Color::NamedColor::Transparent));
+ if (previous_image_disposal_method == ImageDescriptor::DisposalMethod::RestoreBackground) {
+ context.frame_buffer->fill(Color(Color::NamedColor::Transparent));
} else if (i > 1 && previous_image_disposal_method == ImageDescriptor::DisposalMethod::RestorePrevious) {
- copy_frame(*image.bitmap, *context.images.at(i - 2).bitmap);
+ // TODO: tricky as it potentially requires remembering _all_ previous frames.
+ // Luckily it seems GIFs with this mode are rare in the wild.
}
int pixel_index = 0;
@@ -345,7 +344,7 @@ static bool decode_frames_up_to_index(GIFLoadingContext& context, size_t frame_i
}
if (!image.transparent || previous_image_disposal_method == ImageDescriptor::DisposalMethod::None || color != image.transparency_index) {
- image.bitmap->set_pixel(x, y, c);
+ context.frame_buffer->set_pixel(x, y, c);
}
++pixel_index;
@@ -363,10 +362,11 @@ static bool decode_frames_up_to_index(GIFLoadingContext& context, size_t frame_i
}
}
}
-
- ++context.frames_decoded;
}
+ context.current_frame = frame_index;
+ context.state = GIFLoadingContext::State::FrameComplete;
+
return true;
}
@@ -610,27 +610,25 @@ IntSize GIFImageDecoderPlugin::size()
RefPtr<Gfx::Bitmap> GIFImageDecoderPlugin::bitmap()
{
- return frame(0).image;
+ if (m_context->state < GIFLoadingContext::State::FrameComplete) {
+ return frame(0).image;
+ }
+ return m_context->frame_buffer;
}
void GIFImageDecoderPlugin::set_volatile()
{
- for (size_t i = 0; i < m_context->frames_decoded; ++i) {
- m_context->images.at(i).bitmap->set_volatile();
+ if (m_context->frame_buffer) {
+ m_context->frame_buffer->set_volatile();
}
}
bool GIFImageDecoderPlugin::set_nonvolatile()
{
- if (m_context->images.is_empty()) {
- return false;
- }
-
- bool success = true;
- for (size_t i = 0; i < m_context->frames_decoded; ++i) {
- success &= m_context->images.at(i).bitmap->set_nonvolatile();
+ if (!m_context->frame_buffer) {
+ return true;
}
- return success;
+ return m_context->frame_buffer->set_nonvolatile();
}
bool GIFImageDecoderPlugin::sniff()
@@ -689,13 +687,13 @@ ImageFrameDescriptor GIFImageDecoderPlugin::frame(size_t i)
}
}
- if (!decode_frames_up_to_index(*m_context, i)) {
+ if (!decode_frame(*m_context, i)) {
m_context->state = GIFLoadingContext::State::Error;
return {};
}
ImageFrameDescriptor frame {};
- frame.image = m_context->images.at(i).bitmap;
+ frame.image = m_context->frame_buffer;
frame.duration = m_context->images.at(i).duration * 10;
if (frame.duration <= 10) {