diff options
Diffstat (limited to 'Libraries/LibGfx')
-rw-r--r-- | Libraries/LibGfx/GIFLoader.cpp | 359 |
1 files changed, 227 insertions, 132 deletions
diff --git a/Libraries/LibGfx/GIFLoader.cpp b/Libraries/LibGfx/GIFLoader.cpp index 41503f18aa..6af06e353b 100644 --- a/Libraries/LibGfx/GIFLoader.cpp +++ b/Libraries/LibGfx/GIFLoader.cpp @@ -36,21 +36,59 @@ namespace Gfx { -static bool load_gif_impl(GIFLoadingContext&); +static bool load_gif_frame_descriptors(GIFLoadingContext&); + +struct RGB { + u8 r; + u8 g; + u8 b; +}; + +struct ImageDescriptor { + u16 x; + u16 y; + u16 width; + u16 height; + bool use_global_color_map; + RGB color_map[256]; + u8 lzw_min_code_size; + Vector<u8> lzw_encoded_bytes; + RefPtr<Gfx::Bitmap> bitmap; + + // Fields from optional graphic control extension block + enum DisposalMethod : u8 { + None = 0, + InPlace = 1, + RestoreBackground = 2, + RestorePrevious = 3, + }; + DisposalMethod disposal_method { None }; + u8 transparency_index { 0 }; + u16 duration { 0 }; + bool transparent { false }; + bool user_input { false }; +}; + +struct LogicalScreen { + u16 width; + u16 height; + RGB color_map[256]; +}; struct GIFLoadingContext { enum State { NotDecoded = 0, Error, - HeaderDecoded, - BitmapDecoded, + FrameDescriptorsLoaded, }; State state { NotDecoded }; + size_t frames_decoded { 0 }; const u8* data { nullptr }; size_t data_size { 0 }; - int width { -1 }; - int height { -1 }; - Vector<RefPtr<Gfx::Bitmap>> frames {}; + LogicalScreen logical_screen {}; + u8 background_color_index { 0 }; + NonnullOwnPtrVector<ImageDescriptor> images {}; + size_t loops { 1 }; }; RefPtr<Gfx::Bitmap> load_gif(const StringView& path) @@ -79,29 +117,6 @@ enum class GIFFormat { GIF89a, }; -struct RGB { - u8 r; - u8 g; - u8 b; -}; - -struct LogicalScreen { - u16 width; - u16 height; - RGB color_map[256]; -}; - -struct ImageDescriptor { - u16 x; - u16 y; - u16 width; - u16 height; - bool use_global_color_map; - RGB color_map[256]; - u8 lzw_min_code_size; - Vector<u8> lzw_encoded_bytes; -}; - Optional<GIFFormat> decode_gif_header(BufferStream& stream) { static const char valid_header_87[] = "GIF87a"; @@ -247,7 +262,75 @@ private: Vector<u8> m_output {}; }; -bool load_gif_impl(GIFLoadingContext& context) +bool decode_frames_up_to_index(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) { + 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 + const int clear_code = decoder.add_control_code(); + const int end_of_information_code = decoder.add_control_code(); + + auto background_rgb = context.logical_screen.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 }); + image.bitmap->fill(background_color); + if (i > 0 && image.disposal_method == ImageDescriptor::DisposalMethod::InPlace) { + for (int y = 0; y < image.bitmap->height(); ++y) { + for (int x = 0; x < image.bitmap->width(); ++x) { + image.bitmap->set_pixel(x, y, context.images.at(i - 1).bitmap->get_pixel(x, y)); + } + } + } + + int pixel_index = 0; + while (true) { + Optional<u16> code = decoder.next_code(); + if (!code.has_value()) { + dbg() << "Unexpectedly reached end of gif frame data"; + return false; + } + + if (code.value() == clear_code) { + decoder.reset(); + continue; + } else if (code.value() == end_of_information_code) { + break; + } + + auto colors = decoder.get_output(); + + for (const auto& color : colors) { + if (!image.transparent || color != image.transparency_index) { + auto rgb = context.logical_screen.color_map[color]; + + int x = pixel_index % image.width + image.x; + int y = pixel_index / image.width + image.y; + + Color c = Color(rgb.r, rgb.g, rgb.b); + image.bitmap->set_pixel(x, y, c); + } + ++pixel_index; + } + } + + ++context.frames_decoded; + } + + return true; +} + +bool load_gif_frame_descriptors(GIFLoadingContext& context) { if (context.data_size < 32) return false; @@ -262,15 +345,11 @@ bool load_gif_impl(GIFLoadingContext& context) printf("Format is %s\n", format.value() == GIFFormat::GIF89a ? "GIF89a" : "GIF87a"); - LogicalScreen logical_screen; - stream >> logical_screen.width; - stream >> logical_screen.height; + stream >> context.logical_screen.width; + stream >> context.logical_screen.height; if (stream.handle_read_failure()) return false; - context.width = logical_screen.width; - context.height = logical_screen.height; - u8 gcm_info = 0; stream >> gcm_info; @@ -281,17 +360,16 @@ bool load_gif_impl(GIFLoadingContext& context) u8 bits_per_pixel = (gcm_info & 7) + 1; u8 bits_of_color_resolution = (gcm_info >> 4) & 7; - printf("LogicalScreen: %dx%d\n", logical_screen.width, logical_screen.height); + printf("LogicalScreen: %dx%d\n", context.logical_screen.width, context.logical_screen.height); printf("global_color_map_follows_descriptor: %u\n", global_color_map_follows_descriptor); printf("bits_per_pixel: %u\n", bits_per_pixel); printf("bits_of_color_resolution: %u\n", bits_of_color_resolution); - u8 background_color = 0; - stream >> background_color; + stream >> context.background_color_index; if (stream.handle_read_failure()) return false; - printf("background_color: %u\n", background_color); + printf("background_color: %u\n", context.background_color_index); u8 pixel_aspect_ratio = 0; stream >> pixel_aspect_ratio; @@ -305,21 +383,20 @@ bool load_gif_impl(GIFLoadingContext& context) printf("color_map_entry_count: %d\n", color_map_entry_count); for (int i = 0; i < color_map_entry_count; ++i) { - stream >> logical_screen.color_map[i].r; - stream >> logical_screen.color_map[i].g; - stream >> logical_screen.color_map[i].b; + stream >> context.logical_screen.color_map[i].r; + stream >> context.logical_screen.color_map[i].g; + stream >> context.logical_screen.color_map[i].b; } if (stream.handle_read_failure()) return false; for (int i = 0; i < color_map_entry_count; ++i) { - auto& rgb = logical_screen.color_map[i]; + auto& rgb = context.logical_screen.color_map[i]; printf("[%02x]: %s\n", i, Color(rgb.r, rgb.g, rgb.b).to_string().characters()); } - NonnullOwnPtrVector<ImageDescriptor> images; - + NonnullOwnPtr<ImageDescriptor> current_image = make<ImageDescriptor>(); for (;;) { u8 sentinel = 0; stream >> sentinel; @@ -335,6 +412,7 @@ bool load_gif_impl(GIFLoadingContext& context) u8 sub_block_length = 0; + Vector<u8> sub_block {}; for (;;) { stream >> sub_block_length; @@ -345,18 +423,58 @@ bool load_gif_impl(GIFLoadingContext& context) break; u8 dummy; - for (u16 i = 0; i < sub_block_length; ++i) + for (u16 i = 0; i < sub_block_length; ++i) { stream >> dummy; + sub_block.append(dummy); + } if (stream.handle_read_failure()) return false; } + + if (extension_type == 0xF9) { + if (sub_block.size() != 4) { + dbg() << "Unexpected graphic control size"; + continue; + } + + u8 disposal_method = (sub_block[0] & 0x1C) >> 2; + current_image->disposal_method = (ImageDescriptor::DisposalMethod)disposal_method; + + u8 user_input = (sub_block[0] & 0x2) >> 1; + current_image->user_input = user_input == 1; + + u8 transparent = sub_block[0] & 1; + current_image->transparent = transparent == 1; + + u16 duration = sub_block[1] + ((u16)sub_block[2] >> 8); + current_image->duration = duration; + + current_image->transparency_index = sub_block[3]; + } + + if (extension_type == 0xFF) { + if (sub_block.size() != 14) { + dbg() << "Unexpected application extension size: " << sub_block.size(); + continue; + } + + if (sub_block[11] != 1) { + dbg() << "Unexpected application extension format"; + continue; + } + + u16 loops = sub_block[12] + (sub_block[13] << 8); + context.loops = loops; + } + continue; } if (sentinel == 0x2c) { - images.append(make<ImageDescriptor>()); - auto& image = images.last(); + context.images.append(move(current_image)); + auto& image = context.images.last(); + u8 packed_fields { 0 }; stream >> image.x; stream >> image.y; @@ -394,6 +512,8 @@ bool load_gif_impl(GIFLoadingContext& context) image.lzw_encoded_bytes.append(buffer[i]); } } + + current_image = make<ImageDescriptor>(); continue; } @@ -405,59 +525,7 @@ bool load_gif_impl(GIFLoadingContext& context) return false; } - // We exited the block loop after finding a trailer. We should have everything needed. - printf("Image count: %zu\n", images.size()); - if (images.is_empty()) - return false; - - for (size_t i = 0; i < images.size(); ++i) { - auto& image = 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()); - - LZWDecoder decoder(image.lzw_encoded_bytes, image.lzw_min_code_size); - - // Add GIF-specific control codes - const int clear_code = decoder.add_control_code(); - const int end_of_information_code = decoder.add_control_code(); - - auto bitmap = Bitmap::create_purgeable(BitmapFormat::RGBA32, { image.width, image.height }); - - int pixel_index = 0; - while (true) { - Optional<u16> code = decoder.next_code(); - if (!code.has_value()) { - dbg() << "Unexpectedly reached end of gif frame data"; - return false; - } - - if (code.value() == clear_code) { - decoder.reset(); - continue; - } else if (code.value() == end_of_information_code) { - break; - } - - auto colors = decoder.get_output(); - - for (const auto& color : colors) { - auto rgb = logical_screen.color_map[color]; - - int x = pixel_index % image.width; - int y = pixel_index / image.width; - - Color c = Color(rgb.r, rgb.g, rgb.b); - bitmap->set_pixel(x, y, c); - ++pixel_index; - } - } - - context.frames.append(bitmap); - - // FIXME: for now only decode the first frame. - break; - } - - context.state = GIFLoadingContext::State::BitmapDecoded; + context.state = GIFLoadingContext::State::FrameDescriptorsLoaded; return true; } @@ -476,52 +544,37 @@ Size GIFImageDecoderPlugin::size() return {}; } - if (m_context->state < GIFLoadingContext::State::BitmapDecoded) { - if (!load_gif_impl(*m_context)) { + if (m_context->state < GIFLoadingContext::State::FrameDescriptorsLoaded) { + if (!load_gif_frame_descriptors(*m_context)) { m_context->state = GIFLoadingContext::State::Error; return {}; } } - return { m_context->width, m_context->height }; + return { m_context->logical_screen.width, m_context->logical_screen.height }; } RefPtr<Gfx::Bitmap> GIFImageDecoderPlugin::bitmap() { - if (m_context->state == GIFLoadingContext::State::Error) { - return nullptr; - } - - if (m_context->state < GIFLoadingContext::State::BitmapDecoded) { - if (!load_gif_impl(*m_context)) { - m_context->state = GIFLoadingContext::State::Error; - return nullptr; - } - } - - // FIXME: for now only return the first frame. - if (m_context->frames.is_empty()) { - return nullptr; - } - return m_context->frames.first(); + return frame(0).image; } void GIFImageDecoderPlugin::set_volatile() { - for (auto& frame : m_context->frames) { - frame->set_volatile(); + for (size_t i = 0; i < m_context->frames_decoded; ++i) { + m_context->images.at(i).bitmap->set_volatile(); } } bool GIFImageDecoderPlugin::set_nonvolatile() { - if (m_context->frames.is_empty()) { + if (m_context->images.is_empty()) { return false; } bool success = true; - for (auto& frame : m_context->frames) { - success &= frame->set_nonvolatile(); + for (size_t i = 0; i < m_context->frames_decoded; ++i) { + success &= m_context->images.at(i).bitmap->set_nonvolatile(); } return success; } @@ -535,25 +588,67 @@ bool GIFImageDecoderPlugin::sniff() bool GIFImageDecoderPlugin::is_animated() { - return false; + if (m_context->state < GIFLoadingContext::State::FrameDescriptorsLoaded) { + if (!load_gif_frame_descriptors(*m_context)) { + m_context->state = GIFLoadingContext::State::Error; + return false; + } + } + + return m_context->images.size() > 1; } size_t GIFImageDecoderPlugin::loop_count() { - return 0; + if (m_context->state < GIFLoadingContext::State::FrameDescriptorsLoaded) { + if (!load_gif_frame_descriptors(*m_context)) { + m_context->state = GIFLoadingContext::State::Error; + return 0; + } + } + + return m_context->loops; } size_t GIFImageDecoderPlugin::frame_count() { - return 1; + if (m_context->state < GIFLoadingContext::State::FrameDescriptorsLoaded) { + if (!load_gif_frame_descriptors(*m_context)) { + m_context->state = GIFLoadingContext::State::Error; + return 1; + } + } + + return m_context->images.size(); } ImageFrameDescriptor GIFImageDecoderPlugin::frame(size_t i) { - if (i > 0) { - return { bitmap(), 0 }; + if (m_context->state == GIFLoadingContext::State::Error) { + return {}; } - return {}; + + if (m_context->state < GIFLoadingContext::State::FrameDescriptorsLoaded) { + if (!load_gif_frame_descriptors(*m_context)) { + m_context->state = GIFLoadingContext::State::Error; + return {}; + } + } + + if (!decode_frames_up_to_index(*m_context, i)) { + m_context->state = GIFLoadingContext::State::Error; + return {}; + } + + ImageFrameDescriptor frame {}; + frame.image = m_context->images.at(i).bitmap; + frame.duration = m_context->images.at(i).duration * 10; + + if (frame.duration <= 10) { + frame.duration = 100; + } + + return frame; } } |