Skip to content

Commit c896edb

Browse files
committed
Fix animated emojis decoding, webview layout
1 parent c3d154b commit c896edb

11 files changed

Lines changed: 186 additions & 91 deletions

File tree

src/audio_generic.cpp

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,31 @@ void GenericAudio::Decode(uint8_t* output_buffer, int buffer_length) {
471471
channel_active = true;
472472
}
473473
}
474-
return {channel_active, total_volume, samples_per_frame};
474+
if (channel_active) {
475+
if (total_volume > 1.0) {
476+
float threshold = 0.8f;
477+
for (unsigned i = 0; i < (unsigned)(samples_per_frame * 2); i++) {
478+
float sample = mixer_buffer[i];
479+
float sign = (sample < 0) ? -1.0 : 1.0;
480+
sample /= sign;
481+
//dynamic range compression
482+
if (sample > threshold) {
483+
sample_buffer[i] = sign * 32768.0 * (threshold + (1.0 - threshold) * (sample - threshold) / (total_volume - threshold));
484+
} else {
485+
sample_buffer[i] = sign * sample * 32768.0;
486+
}
487+
}
488+
} else {
489+
//No dynamic range compression necessary
490+
for (unsigned i = 0; i < (unsigned)(samples_per_frame * 2); i++) {
491+
sample_buffer[i] = mixer_buffer[i] * 32768.0;
492+
}
493+
}
494+
495+
memcpy(output_buffer, sample_buffer.data(), buffer_length);
496+
} else {
497+
memset(output_buffer, '\0', buffer_length);
498+
}
475499
}
476500

477501
void GenericAudio::BgmChannel::Stop() {

src/image_gif.cpp

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ ImageGif::Decoder::Decoder(Filesystem_Stream::InputStream& is) noexcept : ImageG
2626
return;
2727
}
2828

29+
scratch.resize(gifFile->SWidth * gifFile->SHeight);
30+
std::fill(scratch.begin(), scratch.end(), gifFile->SBackGroundColor | 0xFF000000);
31+
2932
currentFrame = 0;
3033
}
3134

@@ -34,37 +37,35 @@ ImageGif::Decoder::~Decoder() noexcept {
3437
}
3538

3639
bool ImageGif::Decoder::ReadNext(ImageOut& output, GifTimingInfo& timing) {
37-
memset(&output, 0, sizeof(ImageOut));
38-
memset(&timing, 0, sizeof(GifTimingInfo));
39-
4040
if (!gifFile || currentFrame >= gifFile->ImageCount) {
4141
output.pixels = nullptr;
4242
return false;
4343
}
4444

4545
const SavedImage* frame = &gifFile->SavedImages[currentFrame];
46-
const GifImageDesc& image = frame->ImageDesc;
46+
const GifImageDesc& frameInfo = frame->ImageDesc;
4747

48-
ColorMapObject* colorMap = image.ColorMap ? image.ColorMap : gifFile->SColorMap;
48+
ColorMapObject* colorMap = frameInfo.ColorMap ? frameInfo.ColorMap : gifFile->SColorMap;
4949
if (!colorMap) {
5050
Output::Warning("ImageGif: No color map found for frame {}", currentFrame);
5151
output.pixels = nullptr;
5252
return false;
5353
}
5454

55-
output.width = image.Width;
56-
output.height = image.Height;
57-
output.bpp = 32;
58-
output.pixels = new uint32_t[image.Width * image.Height];
55+
const int fullWidth = gifFile->SWidth;
56+
const int fullHeight = gifFile->SHeight;
57+
output.width = fullWidth;
58+
output.height = fullHeight;
59+
output.bpp = 0;
5960

60-
const int left = image.Left;
61-
const int top = image.Top;
62-
const int width = image.Width;
63-
const int height = image.Height;
61+
const int left = frameInfo.Left;
62+
const int top = frameInfo.Top;
63+
const int width = frameInfo.Width;
64+
const int height = frameInfo.Height;
6465
const int bg = gifFile->SBackGroundColor;
66+
timing.delay = 0;
6567

6668
const GifPixelType* src = frame->RasterBits;
67-
// const GifPixelType* srcPrev = currentFrame > 0 ? gifFile->SavedImages[currentFrame - 1].RasterBits : nullptr;
6869

6970
int transparentIndex = -1;
7071
int disposalMethod = 0;
@@ -82,28 +83,28 @@ bool ImageGif::Decoder::ReadNext(ImageOut& output, GifTimingInfo& timing) {
8283
}
8384
}
8485

86+
if (disposalMethod == 2) {
87+
std::fill(scratch.begin(), scratch.end(), bg | 0xFF000000); // Fill with background color
88+
}
89+
8590
size_t srcIndex = 0;
86-
for (int y = 0; y < height; ++y) {
87-
for (int x = 0; x < width; ++x) {
91+
for (int y = top; y < top + height; ++y) {
92+
for (int x = left; x < left + width; ++x) {
8893
GifPixelType index = src[srcIndex++];
8994
if (index == transparentIndex) {
90-
((uint32_t*)output.pixels)[(top + y) * output.width + (left + x)] = 0; // Transparent pixel
91-
continue;
92-
}
93-
// if (disposalMethod == 2)
94-
// ((uint32_t*)output.pixels)[(top + y) * output.width + (left + x)] = bg;
95-
if (index < colorMap->ColorCount) {
95+
if (disposalMethod != 1)
96+
scratch[y * fullWidth + x] = 0; // Transparent pixel
97+
} else if (index < colorMap->ColorCount) {
9698
const GifColorType& color = colorMap->Colors[index];
97-
int dstX = left + x;
98-
int dstY = top + y;
99-
if (dstX < output.width && dstY < output.height) {
100-
((uint32_t*)output.pixels)[dstY * output.width + dstX] =
101-
color.Red | (color.Green << 8) | (color.Blue << 16) | 0xFF000000;
102-
}
99+
scratch[y * fullWidth + x] =
100+
color.Red | (color.Green << 8) | (color.Blue << 16) | 0xFF000000;
103101
}
104102
}
105103
}
106104

105+
output.pixels = new uint32_t[fullWidth * fullHeight];
106+
memcpy(output.pixels, scratch.data(), fullWidth * fullHeight * sizeof(uint32_t));
107+
107108
currentFrame++;
108109
return true;
109110
}

src/image_gif.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ namespace ImageGif {
3838
inline operator bool() const noexcept { return gifFile != nullptr; }
3939
private:
4040
explicit Decoder() noexcept = default;
41+
std::vector<uint32_t> scratch;
4142

4243
GifFileType* gifFile;
4344
int currentFrame{};

src/image_webp.cpp

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,38 +20,36 @@
2020
#include "output.h"
2121

2222
ImageWebP::Decoder::~Decoder() noexcept {
23-
// if (decoder) WebPAnimDecoderDelete(decoder);
24-
// WebPDataClear(&data);
23+
if (decoder) WebPAnimDecoderDelete(decoder);
24+
WebPDataClear(&data);
2525
}
2626

27-
std::optional<ImageWebP::Decoder> ImageWebP::Decoder::Create(Filesystem_Stream::InputStream& is) noexcept {
28-
ImageWebP::Decoder dec;
29-
27+
ImageWebP::Decoder::Decoder(Filesystem_Stream::InputStream& is) noexcept {
3028
// is.read(reinterpret_cast<char*>(&dec.data.bytes));
3129
// WebPMalloc
32-
dec.data.size = is.GetSize();
33-
dec.data.bytes = (uint8_t*)WebPMalloc(dec.data.size + 1);
34-
((char*)dec.data.bytes)[dec.data.size] = 0;
35-
is.read((char*)dec.data.bytes, dec.data.size);
36-
if (is.fail()) return {};
30+
data.size = is.GetSize();
31+
data.bytes = (uint8_t*)WebPMalloc(data.size + 1);
32+
((char*)data.bytes)[data.size] = 0;
33+
is.read((char*)data.bytes, data.size);
34+
if (is.fail()) return;
3735

38-
if (!WebPGetInfo(dec.data.bytes, dec.data.size, nullptr, nullptr)) {
36+
if (!WebPGetInfo(data.bytes, data.size, nullptr, nullptr)) {
3937
Output::Warning("ImageWebP: {} is not webp", is.GetName());
40-
return {};
38+
return;
4139
}
4240

43-
dec.decoder = WebPAnimDecoderNew(&dec.data, nullptr);
44-
if (!dec.decoder) {
41+
decoder = WebPAnimDecoderNew(&data, nullptr);
42+
if (!decoder) {
4543
Output::Warning("ImageWebP: Failed to create decoder for {}", is.GetName());
46-
return {};
44+
return;
4745
}
4846

49-
if (!WebPAnimDecoderGetInfo(dec.decoder, &dec.animData)) {
47+
if (!WebPAnimDecoderGetInfo(decoder, &animData)) {
48+
if (decoder) WebPAnimDecoderDelete(decoder);
49+
decoder = nullptr;
5050
Output::Warning("ImageWebP: Failed to get animation info for {}", is.GetName());
51-
return {};
51+
return;
5252
}
53-
54-
return dec;
5553
}
5654

5755

@@ -68,7 +66,6 @@ bool ImageWebP::Decoder::ReadNext(ImageOut& output, TimingInfo& timing) {
6866
}
6967
output.pixels = new uint32_t[animData.canvas_width * animData.canvas_height];
7068
memcpy(output.pixels, pixels, animData.canvas_width * animData.canvas_height * sizeof(uint32_t));
71-
output.bpp = 32; // WebP always uses 32 bits per pixel (RGBA)
7269
output.height = animData.canvas_height;
7370
output.width = animData.canvas_width;
7471

src/image_webp.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ namespace ImageWebP {
3333
~Decoder() noexcept;
3434

3535
bool ReadNext(ImageOut& output, TimingInfo& timing);
36-
static std::optional<Decoder> Create(Filesystem_Stream::InputStream& is) noexcept;
36+
// static std::optional<Decoder> Create(Filesystem_Stream::InputStream& is) noexcept;
37+
Decoder(Filesystem_Stream::InputStream& is) noexcept;
38+
inline operator bool() const noexcept { return decoder != nullptr; }
3739
private:
3840
explicit Decoder() noexcept = default;
3941
WebPAnimDecoder* decoder;

src/multiplayer/chat_overlay.cpp

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@ void ChatOverlay::Draw(Bitmap& dst) {
125125
text_height = 12;
126126
}
127127

128+
for (auto it = emojiCache.begin(); it != emojiCache.end(); ++it) {
129+
if (auto emoji = it->second.lock()) {
130+
emoji->in_viewport = false;
131+
}
132+
}
133+
128134
int i = 0;
129135
int half_screen = y_end / 2 / text_height;
130136

@@ -205,6 +211,7 @@ void ChatOverlay::Draw(Bitmap& dst) {
205211
bool emojis_only = false;
206212

207213
// begin override line height for components
214+
// if adding new components, remember to update LayoutViewport
208215

209216
// expand messages with only emojis
210217
if (std::any_of(line->cbegin(), line->cend(), [](const std::shared_ptr<ChatComponent>& comp) { return bool(comp->Downcast<ChatComponents::Screenshot>()); })) {
@@ -248,6 +255,7 @@ void ChatOverlay::Draw(Bitmap& dst) {
248255
offset += Text::Draw(*bitmap, offset, y + (baseline ? baseline - text_height : 0), font, str->color, str->string).x;
249256
}
250257
else if (auto emoji = span->Downcast<ChatComponents::Emoji>()) {
258+
emoji->in_viewport = true;
251259
Point dims = emoji->GetSize();
252260
if (emojis_only) dims.x = dims.y = line_height;
253261
int comp_y = y + (baseline ? baseline - text_height : 0);
@@ -301,6 +309,44 @@ void ChatOverlay::Draw(Bitmap& dst) {
301309
dirty = false;
302310
}
303311

312+
ViewportInfo ChatOverlay::LayoutViewport(int text_height, const Font& font) const {
313+
Rect screen_rect = DisplayUi->GetScreenSurfaceRect();
314+
int y_end = screen_rect.height;
315+
int half_screen = y_end / 2 / text_height;
316+
317+
bool unlocked_start = scroll < half_screen;
318+
bool unlocked_end = scroll > messages.size() - half_screen;
319+
int last_sticky = std::max(0, (int)messages.size() - half_screen * 2);
320+
int viewport = show_all
321+
? std::clamp(scroll - half_screen, 0, std::min(scroll + half_screen, last_sticky))
322+
: 0;
323+
324+
int extra = 0, lidx = viewport, scroll_extra = 0, last_sticky_extra = 0;
325+
for (auto message = messages.rbegin() + viewport; message != messages.rend(); ++message) {
326+
const auto& components = message->text;
327+
if (std::any_of(components.cbegin(), components.cend(), [](const std::shared_ptr<ChatComponent>& comp) { return bool(comp->Downcast<ChatComponents::Screenshot>()); })) {
328+
last_sticky_extra += extra = ChatScreenshot::sizer().y - text_height;
329+
} else if (std::all_of(components.cbegin(), components.cend(), [](const std::shared_ptr<ChatComponent>& comp) { return bool(comp->Downcast<ChatComponents::Emoji>()); })) {
330+
last_sticky_extra += extra = (OverlayUtils::LargeScreen() ? 56 : 18) - text_height;
331+
} else {
332+
auto dims = Text::GetSize(font, message->text_orig);
333+
int lines = ceilf(dims.width / (double)bitmap->width());
334+
last_sticky_extra += extra = std::max(0, lines - 1) * text_height;
335+
lidx += std::max(0, lines - 1);
336+
}
337+
if (lidx <= scroll) scroll_extra += extra;
338+
if (lidx >= last_sticky) break;
339+
lidx += 1;
340+
}
341+
342+
ViewportInfo out{};
343+
out.scroll_px = scroll * text_height + scroll_extra;
344+
out.last_sticky_px = last_sticky * text_height + last_sticky_extra;
345+
out.extra = last_sticky_extra;
346+
347+
return out;
348+
}
349+
304350
ChatOverlayMessage& ChatOverlay::AddMessage(
305351
std::string_view message, std::string_view sender, std::string_view sender_uuid, std::string_view system, std::string_view badge,
306352
bool account, bool global, int rank) {
@@ -416,8 +462,8 @@ void ChatOverlay::UpdateScene() {
416462
if (Input::IsRawKeyTriggered(Input::Keys::RETURN) && !input.empty()) {
417463
// TODO: map chat and party chat
418464
std::string encoded = Utils::EncodeUTF(input);
419-
ChatOverlay::AddMessage(encoded, "blah", "00000000000000000000", "", "", true, true, 0);
420-
// GMI().sessionConn.SendPacket(Messages::C2S::SessionGSay{ std::move(encoded) });
465+
// ChatOverlay::AddMessage(encoded, "blah", "00000000000000000000", "", "", true, true, 0);
466+
GMI().sessionConn.SendPacket(Messages::C2S::SessionSay{ std::move(encoded) });
421467
input.clear();
422468
dirty = true;
423469
}
@@ -654,7 +700,7 @@ void ChatEmoji::RequestBitmap(ChatOverlay* parent_) {
654700
}
655701

656702
void ChatEmoji::DecodeGif(const std::string& filePath) {
657-
constexpr int minimum_frame_delay = 64;
703+
constexpr int minimum_frame_delay = 20;
658704
frames.clear();
659705
frameDelays.clear();
660706
auto fs = FileFinder::OpenImage("../images/ynomoji", filePath);
@@ -694,24 +740,21 @@ void ChatEmoji::DecodeGif(const std::string& filePath) {
694740
}
695741

696742
void ChatEmoji::DecodeWebP(const std::string& filePath) {
697-
// Use a WebP decoding library to load the image
698-
// Example: libwebp or similar library
699-
// Pseudo-code:
700743
frames.clear();
701744
frameDelays.clear();
702745
auto fs = FileFinder::OpenImage("../images/ynomoji", filePath);
703746
if (!fs) return;
704747

705-
auto dec = ImageWebP::Decoder::Create(fs);
748+
ImageWebP::Decoder dec(fs);
706749
if (!dec) return;
707750

708751
ImageOut image{};
709752
TimingInfo timing{};
710753
int smear = 0;
711-
while (dec.value().ReadNext(image, timing)) {
712-
auto bitmap = Bitmap::Create(image.pixels, image.width, image.height, image.bpp, format_R8G8B8A8_a().format());
754+
while (dec.ReadNext(image, timing)) {
755+
auto bitmap = Bitmap::Create(image.pixels, image.width, image.height, 0, format_R8G8B8A8_a().format());
713756
if (!bitmap) {
714-
delete image.pixels;
757+
delete[] image.pixels;
715758
continue;
716759
}
717760
bitmap->SetBilinear();
@@ -730,7 +773,6 @@ void ChatEmoji::DecodeWebP(const std::string& filePath) {
730773
}
731774
loopLength += frameDelays.back();
732775
}
733-
// if (image.pixels) delete image.pixels;
734776

735777
currentFrame = 0;
736778
if (loopLength && frames.size() > 1) {
@@ -754,7 +796,7 @@ void ChatEmoji::UpdateAnimation() {
754796
currentFrame = (currentFrame + 1) % frames.size();
755797
lastFrameTime = now;
756798
bitmap = frames[currentFrame]; // Update the displayed frame
757-
if (parent) parent->MarkDirty(); // Mark the parent as dirty to trigger a redraw
799+
if (parent && in_viewport) parent->MarkDirty(); // Mark the parent as dirty to trigger a redraw
758800
}
759801
}
760802

src/multiplayer/chat_overlay.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ class ChatOverlayMessage {
100100

101101
inline void ChatOverlayMessage::SetOnInteract(std::function<void(ChatOverlayMessage&)> on_interact) { OnInteract = on_interact; }
102102

103+
struct ViewportInfo {
104+
int scroll_px, last_sticky_px, extra;
105+
};
106+
103107
class ChatOverlay : public Drawable {
104108
public:
105109
ChatOverlay();
@@ -121,6 +125,8 @@ class ChatOverlay : public Drawable {
121125
private:
122126
bool IsAnyMessageVisible() const;
123127

128+
ViewportInfo LayoutViewport(int text_height, const Font& font) const;
129+
124130
bool dirty = false;
125131
bool show_all = false;
126132
int ox = 0;
@@ -178,6 +184,7 @@ class ChatEmoji : public ChatComponent {
178184
BitmapRef bitmap = nullptr;
179185
ChatOverlay* parent = nullptr;
180186
FileRequestBinding request = nullptr;
187+
bool in_viewport = false;
181188

182189
ChatEmoji(ChatOverlay* parent, int width, int height, std::string emoji);
183190
void RequestBitmap(ChatOverlay* parent);

0 commit comments

Comments
 (0)