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 4122c22be0..a1c1766392 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!ia0y~yU`PRBMrH;E1}`0UaRvqkmUKs7M+SzC{oH>NSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@I2DSj75LX5U zhWSXIP$e9@Gv;^w%dMUnRn>&gG{?t^@2Oi8&^AdbFsm{*fSRcy=2tz9xf>L7 zf-*D|*<=E_x#u17ei-;Daf!&u&`V2pPE!0S92sOG_;AOAeyPpV(qGQ|UHj2~{r-t+ zSM}F=l(u~_T0MQDQF&@yxi&-pTj|mk_u}4PlecQV-nJt1Fw>O14&OL$I(Pk3s_uW| z?RtJ!c@4wMhDTK_VS7$8Ffgc=xJHzuB$lLFB^RY8mZUNm85kMp8d&HW8-^GfS{Yhe snHXyu7+M(^bX?|}iJ~DlKP5A*61Rq*ecMzS7#J8lUHx3vIVCg!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!ia0y~yU`PRBCT5UGPwjCA1_qXNM_)$E)e-c?47#I|i zJ%W507^>757#dm_7=AG@Ff_biU???UV0e|lz+g3lfkC`r&aOZk1_lPs0G|-o{yqKw z7gYWK|35TbSC)Z+fw3gWFPOpM*^M+1C&}C0g(;1@24pXLiKnkC`x6d%9%1dhPW!qT z7#Ng1T^vI^jwdHbusR%&;$mEEwAe^FHch0bDbRq+?Vv^jGef$$TVSGFxG)0)gKCLu zL`h0wNvc(HQEFmIDua=Mk%6v(g|4w-h@qjCp{13Hv9^Jsm4QLWWxkmx8glbfGSez? WYxvo>O_hOxfx*+&&t;ucLK6V5eNuP; 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!ia0y~yU`PRBCT5UGPwjCA1_qXNM_)$E)e-c?47#I|i zJ%W507^>757#dm_7=AG@Ff_biU???UV0e|lz+g3lfkC`r&aOZk1_lPs0G|-o{yqKe z=U)H+|9|3*w=oP142&f~e!&b5&u*lFI7!~_E=+0MH6VN0OFVsD*`IL8^9XD2b=ud( zz`&sF>EamTaXdLef^~6%hywGq00|C>sgfZ0&}eajNKXj^L%Ltf#rpIo@eB+MswJ)w zB`Jv|saDBFsfi`23`Pb<2D%0oy2gefhK5##mR2Ul+6IPJ1_m9M`DUVM$jwj5OsmAL V;b-4ARR#tI22WQ%mvv4FO#qiFR{;P3 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!ia0y~yU`PRBCT5UGPwjCA1_qXNM_)$E)e-c?47#I|i zJ%W507^>757#dm_7=AG@Ff_biU???UV0e|lz+g3lfkC`r&aOZk1_lPs0G|-o{yqI4 zp7s3y|Np}N>L>;V2F8*gzhDN3XE)M7oFs2|7p64s8j!v0C7!;n>`yr4d4#q1I_>LX zU|>-4ba4#vIG&s!!MZp>q^Bv+fUBT6fP+I~s^nD3mqyBh3=D5q`Ahe{xogY7z@S>< z8c~vxSdwa$T$GwvlFDFYU}T_cV4-Vl7-DE>WoT(-VytaoXk}o~ahY!>iiX_$l+3hB X+!}uNZBu1nU|{fc^>bP0l+XkK_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}whwS0gK{VPwuYnJYbx%!5ORd%n5Z*EKV+K|<; zNABiM<(12`t^1bC7d&xa>$!j1{;kVXw3m!AJ%r)(=}MNZqBYG1(6e){cd_5DW7gEpTg_4QRMiWrS_RdqJ1kn zjhCOBYg7H}52uMMBPdLHQa}O7#lXcN#~{bh@P9!S0|Nt-Obhm4e06GDHj*}w77Yds thKBZYuR+?>acg_Wef*Qp^>4k;f1OiiMObXZU<0xE!?PZc3HU9x1^_DI@yh@J literal 0 HcmV?d00001 diff --git a/Libraries/LibGfx/GIFLoader.cpp b/Libraries/LibGfx/GIFLoader.cpp index 12f0d376a1..b8897a8f9d 100644 --- a/Libraries/LibGfx/GIFLoader.cpp +++ b/Libraries/LibGfx/GIFLoader.cpp @@ -93,6 +93,7 @@ struct GIFLoadingContext { size_t loops { 1 }; RefPtr 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); }