@@ -41,6 +41,7 @@ using json = nlohmann::json;
4141#include " icons.h"
4242#include " overlay_utils.h"
4343#include " image_webp.h"
44+ #include " image_gif.h"
4445
4546namespace {
4647 Point ChatTextSquare () {
@@ -201,17 +202,18 @@ void ChatOverlay::Draw(Bitmap& dst) {
201202 // semitransparent bg
202203 int yidx = lidx + 1 + input_row_offset;
203204 int line_height = text_height;
205+ bool emojis_only = false ;
204206
205207 // begin override line height for components
206208
207209 // expand messages with only emojis
208- if (std::all_of (line->cbegin (), line->cend (), [](const std::shared_ptr<ChatComponent>& comp) { return bool (comp->Downcast <ChatComponents::Emoji>()); })) {
209- line_height = OverlayUtils::LargeScreen () ? 56 : 18 ;
210- }
211-
212210 if (std::any_of (line->cbegin (), line->cend (), [](const std::shared_ptr<ChatComponent>& comp) { return bool (comp->Downcast <ChatComponents::Screenshot>()); })) {
213211 line_height = ChatScreenshot::sizer ().y ;
214212 }
213+ else if (std::all_of (line->cbegin (), line->cend (), [](const std::shared_ptr<ChatComponent>& comp) { return bool (comp->Downcast <ChatComponents::Emoji>()); })) {
214+ emojis_only = true ;
215+ line_height = OverlayUtils::LargeScreen () ? 56 : 18 ;
216+ }
215217
216218 // end override line height
217219
@@ -247,22 +249,21 @@ void ChatOverlay::Draw(Bitmap& dst) {
247249 }
248250 else if (auto emoji = span->Downcast <ChatComponents::Emoji>()) {
249251 Point dims = emoji->GetSize ();
252+ if (emojis_only) dims.x = dims.y = line_height;
250253 int comp_y = y + (baseline ? baseline - text_height : 0 );
251254 if (emoji->bitmap ) {
252- double zoom = emoji->bitmap ->GetRect ().y / (double )text_height;
253- bitmap->StretchBlit ({offset, comp_y, line_height, line_height}, *emoji->bitmap , emoji->bitmap ->GetRect (), 255 );
255+ bitmap->StretchBlit ({offset, comp_y, dims.x , dims.y }, *emoji->bitmap , emoji->bitmap ->GetRect (), 255 );
254256 }
255- else {
257+ else if (!emoji-> HasAnimation ()) {
256258 // the emoji is still live, request it now
257259 emoji->RequestBitmap (this );
258- bitmap->FillRect ({ offset, comp_y, line_height, line_height }, offwhite);
260+ bitmap->FillRect ({ offset, comp_y, dims. y , dims. y }, offwhite);
259261 }
260- offset += line_height ;
262+ offset += dims. x ;
261263 }
262264 else if (auto screenshot = span->Downcast <ChatComponents::Screenshot>()) {
263265 Point dims = screenshot->GetSize ();
264266 if (screenshot->bitmap ) {
265- double zoom = screenshot->bitmap ->GetRect ().y / (double )text_height;
266267 bitmap->StretchBlit ({offset, y, dims.x , dims.y }, *screenshot->bitmap , screenshot->bitmap ->GetRect (), 255 );
267268 }
268269 else {
@@ -608,6 +609,8 @@ void ChatEmoji::RequestBitmap(ChatOverlay* parent_) {
608609 if (emoji.empty ()) return ;
609610 parent = parent_;
610611
612+ if (request) return ;
613+
611614 auto req = AsyncHandler::RequestFile (" ../images/ynomoji" , emoji);
612615 req->SetGraphicFile (true );
613616 req->SetParentScope (true );
@@ -627,6 +630,7 @@ void ChatEmoji::RequestBitmap(ChatOverlay* parent_) {
627630 }
628631
629632 request = req->Bind ([this , extension](FileRequestResult* result) {
633+ request.reset (); // Clear the request after processing
630634 if (!result->success ) {
631635 emoji.clear ();
632636 Output::Debug (" failed: {}" , result->file );
@@ -650,25 +654,51 @@ void ChatEmoji::RequestBitmap(ChatOverlay* parent_) {
650654}
651655
652656void ChatEmoji::DecodeGif (const std::string& filePath) {
653- // Use a GIF decoding library to load frames and delays
654- // Example: giflib or similar library
655- // Pseudo-code:
657+ constexpr int minimum_frame_delay = 64 ;
656658 frames.clear ();
657659 frameDelays.clear ();
658- // Load GIF file and extract frames and delays
659- // for each frame in GIF:
660- // Convert frame to Bitmap and store in gifFrames
661- // Store delay in frameDelays
662- // Set currentFrame to 0
660+ auto fs = FileFinder::OpenImage (" ../images/ynomoji" , filePath);
661+ if (!fs) return ;
662+
663+ ImageGif::Decoder dec (fs);
664+ if (!dec) return ;
665+
666+ ImageOut image{};
667+ GifTimingInfo timing{};
668+ while (dec.ReadNext (image, timing)) {
669+ auto bitmap = Bitmap::Create (image.pixels , image.width , image.height , 0 , format_R8G8B8A8_a ().format ());
670+ if (!bitmap) {
671+ if (image.pixels ) delete[] image.pixels ;
672+ continue ;
673+ }
674+ bitmap->SetBilinear ();
675+ frames.push_back (std::move (bitmap));
676+
677+ if (!timing.delay )
678+ timing.delay = 100 ;
679+
680+ int delay = std::min (minimum_frame_delay, timing.delay );
681+ frameDelays.push_back (delay);
682+ loopLength += frameDelays.back ();
683+ }
684+
663685 currentFrame = 0 ;
664- lastFrameTime = std::chrono::steady_clock::now ();
686+ if (loopLength && frames.size () > 1 ) {
687+ lastFrameTime = std::chrono::steady_clock::now ();
688+ int loopDelta = std::chrono::duration_cast<std::chrono::milliseconds>(lastFrameTime.time_since_epoch ()).count () % loopLength;
689+ while (currentFrame < frameDelays.size () && loopDelta >= frameDelays[currentFrame]) {
690+ loopDelta -= frameDelays[currentFrame];
691+ currentFrame = (currentFrame + 1 ) % frames.size ();
692+ }
693+ }
665694}
666695
667696void ChatEmoji::DecodeWebP (const std::string& filePath) {
668697 // Use a WebP decoding library to load the image
669698 // Example: libwebp or similar library
670699 // Pseudo-code:
671700 frames.clear ();
701+ frameDelays.clear ();
672702 auto fs = FileFinder::OpenImage (" ../images/ynomoji" , filePath);
673703 if (!fs) return ;
674704
@@ -714,7 +744,8 @@ void ChatEmoji::DecodeWebP(const std::string& filePath) {
714744}
715745
716746void ChatEmoji::UpdateAnimation () {
717- if (frames.size () < 2 || frameDelays.empty ()) return ; // No animation to update
747+ if (frames.size () == 1 ) bitmap = frames[0 ]; // If there's only one frame, just display it
748+ else if (frames.size () < 2 || frameDelays.empty ()) return ; // No animation to update
718749
719750 auto now = std::chrono::steady_clock::now ();
720751 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastFrameTime).count ();
0 commit comments