From 259f8541fcd6bc147c9fb4c57b16cd840700af59 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Mon, 31 Aug 2020 13:22:38 +0100 Subject: [PATCH] LibGfx: implement GIF RestorePrevious frame disposal mode --- Base/res/html/misc/gifsuite.html | 9 +++ ...ansparent_firstframerestoreprev_loop-0.png | Bin 0 -> 479 bytes ...ansparent_firstframerestoreprev_loop-1.png | Bin 0 -> 315 bytes ...ansparent_firstframerestoreprev_loop-2.png | Bin 0 -> 316 bytes ...ansparent_firstframerestoreprev_loop-3.png | Bin 0 -> 315 bytes ...transparent_firstframerestoreprev_loop.gif | Bin 0 -> 536 bytes Libraries/LibGfx/GIFLoader.cpp | 58 +++++++++++------- 7 files changed, 45 insertions(+), 22 deletions(-) create mode 100644 Base/res/html/misc/gifsuite_files/animated_transparent_firstframerestoreprev_loop-0.png create mode 100644 Base/res/html/misc/gifsuite_files/animated_transparent_firstframerestoreprev_loop-1.png create mode 100644 Base/res/html/misc/gifsuite_files/animated_transparent_firstframerestoreprev_loop-2.png create mode 100644 Base/res/html/misc/gifsuite_files/animated_transparent_firstframerestoreprev_loop-3.png create mode 100644 Base/res/html/misc/gifsuite_files/animated_transparent_firstframerestoreprev_loop.gif diff --git a/Base/res/html/misc/gifsuite.html b/Base/res/html/misc/gifsuite.html index 4122c22be01..a1c17663929 100644 --- a/Base/res/html/misc/gifsuite.html +++ b/Base/res/html/misc/gifsuite.html @@ -104,6 +104,15 @@ Transparent gif with 4 frames, loops forever, restore previous + + + + + + + Transparent gif with 4 frames, loops forever, first frame restore previous + + diff --git a/Base/res/html/misc/gifsuite_files/animated_transparent_firstframerestoreprev_loop-0.png b/Base/res/html/misc/gifsuite_files/animated_transparent_firstframerestoreprev_loop-0.png new file mode 100644 index 0000000000000000000000000000000000000000..302a26a4a37f0a972750667bd73f82d97968ae22 GIT binary patch literal 479 zcmeAS@N?(olHy`uVBq!ia0vp^DIm14Ba#1H&(%P{RubhEf9thF1v;3|2E37{m+a>!K@q1v!y%8T_oCKJfbJ@1hB z!@x(0OGHkFURttqlHyO{$RG>BhdUnhOKqN({&L>$+K=|@_fJ&2s=wBwwC#)0>gf}W z%2VUYwHf-~N|&~{7x(^}yjAn{wiTI&nWpS@_{MqDx$B=&b^jZ0*Ymr|YZzWOJgQ;| z+jA1=Db*6!h?11Vl2ohYqEsNoU}Ruqple{EYit-|XlP|e)ers1!`dMboFyt=akR{00}guQ~&?~ literal 0 HcmV?d00001 diff --git a/Base/res/html/misc/gifsuite_files/animated_transparent_firstframerestoreprev_loop-1.png b/Base/res/html/misc/gifsuite_files/animated_transparent_firstframerestoreprev_loop-1.png new file mode 100644 index 0000000000000000000000000000000000000000..191d9d176ccc2a8d1bd6c7c7c36aaedc49a61931 GIT binary patch literal 315 zcmeAS@N?(olHy`uVBq!ia0vp^DIm757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f-}G;#I=7<|NjM5|Ns9F z4cC{w6Ju=yLn{M=j>~*AQ8eV{r(~v8;@0r9Z<{Jm1B0il KpUXO@geCy5eNuP; literal 0 HcmV?d00001 diff --git a/Base/res/html/misc/gifsuite_files/animated_transparent_firstframerestoreprev_loop-2.png b/Base/res/html/misc/gifsuite_files/animated_transparent_firstframerestoreprev_loop-2.png new file mode 100644 index 0000000000000000000000000000000000000000..545c1c087adee7ae36cc6e2b4360c2bbb858c669 GIT binary patch literal 316 zcmeAS@N?(olHy`uVBq!ia0vp^DIm757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f-}G;#I=7``C$S;_|;n|He5GTpo-GwQQy9Q)0dx@v7EBg};c^+Zyy-xeOfI`ZiE{-7{ z$CDEzSQjUVC@^0Okl>J*DhYxQjesIOB@7Jdek~X4)1Slxm8+JxMwFx^mZVxG7o`Fz z1|tI_16>0PU1P%#LqjV=ODhv&Z39Cq1A~ssd^1rrmdK II;Vst0GBFP0RR91 literal 0 HcmV?d00001 diff --git a/Base/res/html/misc/gifsuite_files/animated_transparent_firstframerestoreprev_loop-3.png b/Base/res/html/misc/gifsuite_files/animated_transparent_firstframerestoreprev_loop-3.png new file mode 100644 index 0000000000000000000000000000000000000000..584aacf2826a66d14c826e7c741ac17d82f1bfe0 GIT binary patch literal 315 zcmeAS@N?(olHy`uVBq!ia0vp^DIm757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f-}G;#I=7<|A%Ki|NsBL zu)jJAD9Bh6jJzjW`LyS6~pswJ)wB`Jv|saDBF zsX&Us$iT=z*T6#8*f7M<(8|!#%EVaPz|hLTpyM*%OcV{d`6-!cmAEzh?AxXa)WG2B L>gTe~DWM4f_lj08 literal 0 HcmV?d00001 diff --git a/Base/res/html/misc/gifsuite_files/animated_transparent_firstframerestoreprev_loop.gif b/Base/res/html/misc/gifsuite_files/animated_transparent_firstframerestoreprev_loop.gif new file mode 100644 index 0000000000000000000000000000000000000000..cbec14901209b731124ba90e395b1e81bfe3dfee GIT binary patch literal 536 zcmZ?wbhEHbOkqf2_`tx>zo(ypfkE*n3vUVoBZK09Za>$MU}whwS0g)4th{z6izI?Yi## z>-YNi&lx377_PXU=rLnrg~yQ{?H_McD6X6llBwA^Q*tAx<-?ONMY`C%UX@jLty;P3 z*3DaKT(i}zqmN~Ib5C6T?Ns&d-`sx`Y;2<5JndeybWhCHH$1Gedrf?ETdLQFtd2c$ zH+L$pT%K*+w_Lv9iThg5{oD3$U7n)7d_2E@|Nb54|L+T2FJSG$%-Q#Q!aIL+LGI51 zn?DOYGO{>vgTwi-w!5CL!K!t0b|oo@oY3rd)ALCAbYuAxUayTJ$KNcq&omP4TiI#6 z{M=ld>R*32O frame_buffer; size_t current_frame { 0 }; + RefPtr prev_frame_buffer; }; RefPtr load_gif(const StringView& path) @@ -266,6 +267,12 @@ private: Vector m_output {}; }; +static void copy_frame_buffer(Bitmap& dest, const Bitmap& src) +{ + ASSERT(dest.size_in_bytes() == src.size_in_bytes()); + memcpy(dest.scanline(0), src.scanline(0), dest.size_in_bytes()); +} + static bool decode_frame(GIFLoadingContext& context, size_t frame_index) { if (frame_index >= context.images.size()) { @@ -280,6 +287,7 @@ static bool decode_frame(GIFLoadingContext& context, size_t frame_index) 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 }); + context.prev_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; } @@ -288,29 +296,39 @@ static bool decode_frame(GIFLoadingContext& context, size_t frame_index) 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()); + const auto previous_image_disposal_method = i > 0 ? context.images.at(i - 1).disposal_method : ImageDescriptor::DisposalMethod::None; + + if (i == 0) { + context.frame_buffer->fill(Color::Transparent); + } else if (i > 0 && image.disposal_method == ImageDescriptor::DisposalMethod::RestorePrevious + && previous_image_disposal_method != ImageDescriptor::DisposalMethod::RestorePrevious) { + // This marks the start of a run of frames that once disposed should be restored to the + // previous underlying image contents. Therefore we make a copy of the current frame + // buffer so that it can be restored later. + copy_frame_buffer(*context.prev_frame_buffer, *context.frame_buffer); + } + + if (previous_image_disposal_method == ImageDescriptor::DisposalMethod::RestoreBackground) { + // Note: RestoreBackground could be interpreted either as restoring the underlying + // background of the entire image (e.g. container element's background-color), or the + // background color of the GIF itself. It appears that all major browsers and most other + // GIF decoders adhere to the former interpretation, therefore we will do the same by + // clearing the entire frame buffer to transparent. + Painter painter(*context.frame_buffer); + painter.clear_rect(context.images.at(i - 1).rect(), Color::Transparent); + } else if (i > 0 && previous_image_disposal_method == ImageDescriptor::DisposalMethod::RestorePrevious) { + // Previous frame indicated that once disposed, it should be restored to *its* previous + // underlying image contents, therefore we restore the saved previous frame buffer. + copy_frame_buffer(*context.frame_buffer, *context.prev_frame_buffer); + } + 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& color_map = image.use_global_color_map ? context.logical_screen.color_map : image.color_map; - - auto background_color = color_map[context.background_color_index]; - - if (i == 0) { - 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 (previous_image_disposal_method == ImageDescriptor::DisposalMethod::RestoreBackground) { - Painter painter(*context.frame_buffer); - painter.clear_rect(context.images.at(i - 1).rect(), Color::Transparent); - } else if (i > 1 && previous_image_disposal_method == ImageDescriptor::DisposalMethod::RestorePrevious) { - // TODO: tricky as it potentially requires remembering _all_ previous frames. - // Luckily it seems GIFs with this mode are rare in the wild. - } + const auto& color_map = image.use_global_color_map ? context.logical_screen.color_map : image.color_map; int pixel_index = 0; int row = 0; @@ -337,11 +355,7 @@ static bool decode_frame(GIFLoadingContext& context, size_t frame_index) int x = pixel_index % image.width + image.x; int y = row + image.y; - if (image.transparent && color == image.transparency_index) { - c.set_alpha(0); - } - - if (!image.transparent || previous_image_disposal_method == ImageDescriptor::DisposalMethod::None || color != image.transparency_index) { + if (!image.transparent || color != image.transparency_index) { context.frame_buffer->set_pixel(x, y, c); }