mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 02:37:35 +00:00
LibGfx: Implement GIFImageDecoderPlugin animation methods
GIFImageDecoderPlugin now lazily decodes GIF frames as they are requested.
This commit is contained in:
parent
ddc4eb7be0
commit
b1fee13904
1 changed files with 227 additions and 132 deletions
|
@ -36,21 +36,59 @@
|
||||||
|
|
||||||
namespace Gfx {
|
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 {
|
struct GIFLoadingContext {
|
||||||
enum State {
|
enum State {
|
||||||
NotDecoded = 0,
|
NotDecoded = 0,
|
||||||
Error,
|
Error,
|
||||||
HeaderDecoded,
|
FrameDescriptorsLoaded,
|
||||||
BitmapDecoded,
|
|
||||||
};
|
};
|
||||||
State state { NotDecoded };
|
State state { NotDecoded };
|
||||||
|
size_t frames_decoded { 0 };
|
||||||
const u8* data { nullptr };
|
const u8* data { nullptr };
|
||||||
size_t data_size { 0 };
|
size_t data_size { 0 };
|
||||||
int width { -1 };
|
LogicalScreen logical_screen {};
|
||||||
int height { -1 };
|
u8 background_color_index { 0 };
|
||||||
Vector<RefPtr<Gfx::Bitmap>> frames {};
|
NonnullOwnPtrVector<ImageDescriptor> images {};
|
||||||
|
size_t loops { 1 };
|
||||||
};
|
};
|
||||||
|
|
||||||
RefPtr<Gfx::Bitmap> load_gif(const StringView& path)
|
RefPtr<Gfx::Bitmap> load_gif(const StringView& path)
|
||||||
|
@ -79,29 +117,6 @@ enum class GIFFormat {
|
||||||
GIF89a,
|
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)
|
Optional<GIFFormat> decode_gif_header(BufferStream& stream)
|
||||||
{
|
{
|
||||||
static const char valid_header_87[] = "GIF87a";
|
static const char valid_header_87[] = "GIF87a";
|
||||||
|
@ -247,7 +262,75 @@ private:
|
||||||
Vector<u8> m_output {};
|
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)
|
if (context.data_size < 32)
|
||||||
return false;
|
return false;
|
||||||
|
@ -262,15 +345,11 @@ bool load_gif_impl(GIFLoadingContext& context)
|
||||||
|
|
||||||
printf("Format is %s\n", format.value() == GIFFormat::GIF89a ? "GIF89a" : "GIF87a");
|
printf("Format is %s\n", format.value() == GIFFormat::GIF89a ? "GIF89a" : "GIF87a");
|
||||||
|
|
||||||
LogicalScreen logical_screen;
|
stream >> context.logical_screen.width;
|
||||||
stream >> logical_screen.width;
|
stream >> context.logical_screen.height;
|
||||||
stream >> logical_screen.height;
|
|
||||||
if (stream.handle_read_failure())
|
if (stream.handle_read_failure())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
context.width = logical_screen.width;
|
|
||||||
context.height = logical_screen.height;
|
|
||||||
|
|
||||||
u8 gcm_info = 0;
|
u8 gcm_info = 0;
|
||||||
stream >> gcm_info;
|
stream >> gcm_info;
|
||||||
|
|
||||||
|
@ -281,17 +360,16 @@ bool load_gif_impl(GIFLoadingContext& context)
|
||||||
u8 bits_per_pixel = (gcm_info & 7) + 1;
|
u8 bits_per_pixel = (gcm_info & 7) + 1;
|
||||||
u8 bits_of_color_resolution = (gcm_info >> 4) & 7;
|
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("global_color_map_follows_descriptor: %u\n", global_color_map_follows_descriptor);
|
||||||
printf("bits_per_pixel: %u\n", bits_per_pixel);
|
printf("bits_per_pixel: %u\n", bits_per_pixel);
|
||||||
printf("bits_of_color_resolution: %u\n", bits_of_color_resolution);
|
printf("bits_of_color_resolution: %u\n", bits_of_color_resolution);
|
||||||
|
|
||||||
u8 background_color = 0;
|
stream >> context.background_color_index;
|
||||||
stream >> background_color;
|
|
||||||
if (stream.handle_read_failure())
|
if (stream.handle_read_failure())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
printf("background_color: %u\n", background_color);
|
printf("background_color: %u\n", context.background_color_index);
|
||||||
|
|
||||||
u8 pixel_aspect_ratio = 0;
|
u8 pixel_aspect_ratio = 0;
|
||||||
stream >> pixel_aspect_ratio;
|
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);
|
printf("color_map_entry_count: %d\n", color_map_entry_count);
|
||||||
|
|
||||||
for (int i = 0; i < color_map_entry_count; ++i) {
|
for (int i = 0; i < color_map_entry_count; ++i) {
|
||||||
stream >> logical_screen.color_map[i].r;
|
stream >> context.logical_screen.color_map[i].r;
|
||||||
stream >> logical_screen.color_map[i].g;
|
stream >> context.logical_screen.color_map[i].g;
|
||||||
stream >> logical_screen.color_map[i].b;
|
stream >> context.logical_screen.color_map[i].b;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream.handle_read_failure())
|
if (stream.handle_read_failure())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
for (int i = 0; i < color_map_entry_count; ++i) {
|
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());
|
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 (;;) {
|
for (;;) {
|
||||||
u8 sentinel = 0;
|
u8 sentinel = 0;
|
||||||
stream >> sentinel;
|
stream >> sentinel;
|
||||||
|
@ -335,6 +412,7 @@ bool load_gif_impl(GIFLoadingContext& context)
|
||||||
|
|
||||||
u8 sub_block_length = 0;
|
u8 sub_block_length = 0;
|
||||||
|
|
||||||
|
Vector<u8> sub_block {};
|
||||||
for (;;) {
|
for (;;) {
|
||||||
stream >> sub_block_length;
|
stream >> sub_block_length;
|
||||||
|
|
||||||
|
@ -345,18 +423,58 @@ bool load_gif_impl(GIFLoadingContext& context)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
u8 dummy;
|
u8 dummy;
|
||||||
for (u16 i = 0; i < sub_block_length; ++i)
|
for (u16 i = 0; i < sub_block_length; ++i) {
|
||||||
stream >> dummy;
|
stream >> dummy;
|
||||||
|
sub_block.append(dummy);
|
||||||
|
}
|
||||||
|
|
||||||
if (stream.handle_read_failure())
|
if (stream.handle_read_failure())
|
||||||
return false;
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sentinel == 0x2c) {
|
if (sentinel == 0x2c) {
|
||||||
images.append(make<ImageDescriptor>());
|
context.images.append(move(current_image));
|
||||||
auto& image = images.last();
|
auto& image = context.images.last();
|
||||||
|
|
||||||
u8 packed_fields { 0 };
|
u8 packed_fields { 0 };
|
||||||
stream >> image.x;
|
stream >> image.x;
|
||||||
stream >> image.y;
|
stream >> image.y;
|
||||||
|
@ -394,6 +512,8 @@ bool load_gif_impl(GIFLoadingContext& context)
|
||||||
image.lzw_encoded_bytes.append(buffer[i]);
|
image.lzw_encoded_bytes.append(buffer[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
current_image = make<ImageDescriptor>();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,59 +525,7 @@ bool load_gif_impl(GIFLoadingContext& context)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We exited the block loop after finding a trailer. We should have everything needed.
|
context.state = GIFLoadingContext::State::FrameDescriptorsLoaded;
|
||||||
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;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -476,52 +544,37 @@ Size GIFImageDecoderPlugin::size()
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_context->state < GIFLoadingContext::State::BitmapDecoded) {
|
if (m_context->state < GIFLoadingContext::State::FrameDescriptorsLoaded) {
|
||||||
if (!load_gif_impl(*m_context)) {
|
if (!load_gif_frame_descriptors(*m_context)) {
|
||||||
m_context->state = GIFLoadingContext::State::Error;
|
m_context->state = GIFLoadingContext::State::Error;
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { m_context->width, m_context->height };
|
return { m_context->logical_screen.width, m_context->logical_screen.height };
|
||||||
}
|
}
|
||||||
|
|
||||||
RefPtr<Gfx::Bitmap> GIFImageDecoderPlugin::bitmap()
|
RefPtr<Gfx::Bitmap> GIFImageDecoderPlugin::bitmap()
|
||||||
{
|
{
|
||||||
if (m_context->state == GIFLoadingContext::State::Error) {
|
return frame(0).image;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GIFImageDecoderPlugin::set_volatile()
|
void GIFImageDecoderPlugin::set_volatile()
|
||||||
{
|
{
|
||||||
for (auto& frame : m_context->frames) {
|
for (size_t i = 0; i < m_context->frames_decoded; ++i) {
|
||||||
frame->set_volatile();
|
m_context->images.at(i).bitmap->set_volatile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GIFImageDecoderPlugin::set_nonvolatile()
|
bool GIFImageDecoderPlugin::set_nonvolatile()
|
||||||
{
|
{
|
||||||
if (m_context->frames.is_empty()) {
|
if (m_context->images.is_empty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool success = true;
|
bool success = true;
|
||||||
for (auto& frame : m_context->frames) {
|
for (size_t i = 0; i < m_context->frames_decoded; ++i) {
|
||||||
success &= frame->set_nonvolatile();
|
success &= m_context->images.at(i).bitmap->set_nonvolatile();
|
||||||
}
|
}
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
@ -535,25 +588,67 @@ bool GIFImageDecoderPlugin::sniff()
|
||||||
|
|
||||||
bool GIFImageDecoderPlugin::is_animated()
|
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()
|
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()
|
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)
|
ImageFrameDescriptor GIFImageDecoderPlugin::frame(size_t i)
|
||||||
{
|
{
|
||||||
if (i > 0) {
|
if (m_context->state == GIFLoadingContext::State::Error) {
|
||||||
return { bitmap(), 0 };
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue