Chatterino / chatterino2

Chat client for https://twitch.tv
MIT License
2.04k stars 448 forks source link

fix: override broken base sizes & scales for some Twitch emotes #5279

Closed pajlada closed 6 months ago

pajlada commented 6 months ago

Some Twitch emotes have the "wrong" scale factor on the 3x image, and some Twitch emotes have the "wrong" base size. This became more apparent after #5192 This PR overrides the base size & scale factor of some emotes I've found to be problematic. To figure out which emotes were problematic, I applied this patch & loaded the emotes I thought were broken in all scales by opening emote menu or sending them in chat, then scrolling my zoom through all the various zoom levels (can be done smarter xd)

diff --git a/src/controllers/commands/builtin/chatterino/Debugging.cpp b/src/controllers/commands/builtin/chatterino/Debugging.cpp
index c72f0cde0..efc357b24 100644
--- a/src/controllers/commands/builtin/chatterino/Debugging.cpp
+++ b/src/controllers/commands/builtin/chatterino/Debugging.cpp
@@ -8,16 +8,60 @@
 #include "messages/MessageBuilder.hpp"
 #include "messages/MessageElement.hpp"
 #include "singletons/Theme.hpp"
+#include "util/Clipboard.hpp"
 #include "util/PostToThread.hpp"

 #include <QApplication>
+#include <qjsonarray.h>
+#include <qjsondocument.h>
 #include <QLoggingCategory>
 #include <QString>

+// twitch emotes only xd
+struct XDEmoteData {
+    QString emoteID;
+    QString emoteName;
+    QSize size1;
+    QSize size2;
+    QSize size3;
+};
+
+namespace {
+
+using namespace chatterino;
+
+std::unordered_map<QString, XDEmoteData> EMOTE_DATA;
+
+}  // namespace
+
 namespace chatterino::commands {

 using namespace literals;

+void saveEmoteData(ImagePtr emote)
+{
+    if (emote->twitchEmoteID.isEmpty())
+    {
+        return;
+    }
+
+    auto &emoteData = EMOTE_DATA[emote->twitchEmoteID];
+    emoteData.emoteID = emote->twitchEmoteID;
+    emoteData.emoteName = emote->twitchEmoteName;
+    if (emote->scale() == 1.0)
+    {
+        emoteData.size1 = {emote->unscaledWidth(), emote->unscaledHeight()};
+    }
+    else if (emote->scale() == 0.5)
+    {
+        emoteData.size2 = {emote->unscaledWidth(), emote->unscaledHeight()};
+    }
+    else if (emote->scale() == 0.25)
+    {
+        emoteData.size3 = {emote->unscaledWidth(), emote->unscaledHeight()};
+    }
+}
+
 QString setLoggingRules(const CommandContext &ctx)
 {
     if (ctx.words.size() < 2)
@@ -76,24 +120,31 @@ QString listEnvironmentVariables(const CommandContext &ctx)
         return "";
     }

-    auto env = Env::get();
-
-    QStringList debugMessages{
-        "recentMessagesApiUrl: " + env.recentMessagesApiUrl,
-        "linkResolverUrl: " + env.linkResolverUrl,
-        "twitchServerHost: " + env.twitchServerHost,
-        "twitchServerPort: " + QString::number(env.twitchServerPort),
-        "twitchServerSecure: " + QString::number(env.twitchServerSecure),
-    };
+    QJsonArray jsonEmotes;

-    for (QString &str : debugMessages)
+    for (const auto &[_, emoteData] : EMOTE_DATA)
     {
-        MessageBuilder builder;
-        builder.emplace<TimestampElement>(QTime::currentTime());
-        builder.emplace<TextElement>(str, MessageElementFlag::Text,
-                                     MessageColor::System);
-        channel->addMessage(builder.release());
+        qDebug() << "XXX: Emote data:" << emoteData.emoteName
+                 << emoteData.emoteID << emoteData.size1 << emoteData.size2
+                 << emoteData.size3;
+        QJsonObject obj;
+        obj["name"] = emoteData.emoteName;
+        obj["id"] = emoteData.emoteID;
+        obj["size1"] = QString("{%1, %2}")
+                           .arg(emoteData.size1.width())
+                           .arg(emoteData.size1.height());
+        obj["size2"] = QString("{%1, %2}")
+                           .arg(emoteData.size2.width())
+                           .arg(emoteData.size2.height());
+        obj["size3"] = QString("{%1, %2}")
+                           .arg(emoteData.size3.width())
+                           .arg(emoteData.size3.height());
+        jsonEmotes.append(obj);
     }
+
+    QJsonDocument xd(jsonEmotes);
+    crossPlatformCopy(xd.toJson(QJsonDocument::JsonFormat::Indented));
+
     return "";
 }

diff --git a/src/controllers/commands/builtin/chatterino/Debugging.hpp b/src/controllers/commands/builtin/chatterino/Debugging.hpp
index 8d1857370..36633ef0b 100644
--- a/src/controllers/commands/builtin/chatterino/Debugging.hpp
+++ b/src/controllers/commands/builtin/chatterino/Debugging.hpp
@@ -1,5 +1,6 @@
 #pragma once

+#include "messages/Emote.hpp"
 class QString;

 namespace chatterino {
@@ -10,6 +11,8 @@ struct CommandContext;

 namespace chatterino::commands {

+void saveEmoteData(ImagePtr emote);
+
 QString setLoggingRules(const CommandContext &ctx);

 QString toggleThemeReload(const CommandContext &ctx);
diff --git a/src/messages/Image.cpp b/src/messages/Image.cpp
index f39485d5f..1a15c48f1 100644
--- a/src/messages/Image.cpp
+++ b/src/messages/Image.cpp
@@ -5,6 +5,7 @@
 #include "common/network/NetworkRequest.hpp"
 #include "common/network/NetworkResult.hpp"
 #include "common/QLogging.hpp"
+#include "controllers/commands/builtin/chatterino/Debugging.hpp"
 #include "debug/AssertInGuiThread.hpp"
 #include "debug/Benchmark.hpp"
 #include "singletons/Emotes.hpp"
@@ -486,6 +487,18 @@ int Image::width() const
     return static_cast<int>(this->expectedSize_.width() * this->scale_);
 }

+int Image::unscaledWidth() const
+{
+    assertInGuiThread();
+
+    if (auto pixmap = this->frames_->first())
+    {
+        return static_cast<int>(pixmap->width());
+    }
+
+    return {};
+}
+
 int Image::height() const
 {
     assertInGuiThread();
@@ -499,6 +512,18 @@ int Image::height() const
     return static_cast<int>(this->expectedSize_.height() * this->scale_);
 }

+int Image::unscaledHeight() const
+{
+    assertInGuiThread();
+
+    if (auto pixmap = this->frames_->first())
+    {
+        return static_cast<int>(pixmap->height());
+    }
+
+    return {};
+}
+
 void Image::actuallyLoad()
 {
     auto weak = weakOf(this);
@@ -563,6 +588,7 @@ void Image::actuallyLoad()
                     {
                         shared->frames_ = std::make_unique<detail::Frames>(
                             std::forward<decltype(frames)>(frames));
+                        commands::saveEmoteData(shared);
                     }
                 }));
         })
diff --git a/src/messages/Image.hpp b/src/messages/Image.hpp
index 2eb0fcf04..8b8914809 100644
--- a/src/messages/Image.hpp
+++ b/src/messages/Image.hpp
@@ -87,11 +87,16 @@ public:
     bool isEmpty() const;
     int width() const;
     int height() const;
+    int unscaledWidth() const;
+    int unscaledHeight() const;
     bool animated() const;

     bool operator==(const Image &image) = delete;
     bool operator!=(const Image &image) = delete;

+    QString twitchEmoteID;
+    QString twitchEmoteName;
+
 private:
     Image();
     Image(const Url &url, qreal scale, QSize expectedSize);
diff --git a/src/providers/twitch/TwitchEmotes.cpp b/src/providers/twitch/TwitchEmotes.cpp
index 918d504a4..20ae6b102 100644
--- a/src/providers/twitch/TwitchEmotes.cpp
+++ b/src/providers/twitch/TwitchEmotes.cpp
@@ -449,6 +449,13 @@ EmotePtr TwitchEmotes::getOrCreateEmote(const EmoteId &id,
             },
             Tooltip{name.toHtmlEscaped() + "<br>Twitch Emote"},
         });
+
+        shared->images.getImage1()->twitchEmoteName = name;
+        shared->images.getImage2()->twitchEmoteName = name;
+        shared->images.getImage3()->twitchEmoteName = name;
+        shared->images.getImage1()->twitchEmoteID = id.string;
+        shared->images.getImage2()->twitchEmoteID = id.string;
+        shared->images.getImage3()->twitchEmoteID = id.string;
     }

     return shared;

Then I ran /debug-env to copy all emote data to clipboard, pasted it to emotes.json and ran this python script to generate to .cpp files I could paste into the 3x scale override & base size overrides

#!/usr/bin/env python3

import json

input_file = "emotes.json"

with open(input_file, "r") as fh:
    raw_emote_data = fh.read()

fixed_base_size = []
fixed_3_scales = []

emote_data = json.loads(raw_emote_data)
for raw_emote in emote_data:
    emote = {
        "id": raw_emote["id"],
        "name": raw_emote["name"],
        "size1": (
            int(raw_emote["size1"].replace("{", "").replace("}", "").split(",")[0]),
            int(raw_emote["size1"].replace("{", "").replace("}", "").split(",")[1]),
        ),
        "size2": (
            int(raw_emote["size2"].replace("{", "").replace("}", "").split(",")[0]),
            int(raw_emote["size2"].replace("{", "").replace("}", "").split(",")[1]),
        ),
        "size3": (
            int(raw_emote["size3"].replace("{", "").replace("}", "").split(",")[0]),
            int(raw_emote["size3"].replace("{", "").replace("}", "").split(",")[1]),
        ),
    }
    if emote["size1"][0] != 28 or emote["size1"][1] != 28:
        # Bad base size
        print(f"Emote {emote['name']} has a bad size {emote}")
        fixed_base_size.append(
            {"name": emote["name"], "id": emote["id"], "base_size": emote["size1"]}
        )

    scale2 = emote["size1"][0] / emote["size2"][0]
    scale3 = emote["size1"][0] / emote["size3"][0]
    if scale2 != 0.5:
        # Bad scale
        print(f"Emote {emote['name']} has a bad 2.0 scale {emote}")
    if scale3 != 0.25:
        # Bad scale
        print(f"Emote {emote['name']} has a bad 3.0 scale {emote}")
        fixed_3_scales.append(
            {"name": emote["name"], "id": emote["id"], "3.0_scale": scale3}
        )

with open("fixed-base-sizes.json", "w") as fh:
    fh.write(json.dumps(fixed_base_size))
with open("fixed-3-scales.json", "w") as fh:
    fh.write(json.dumps(fixed_3_scales))

with open("fixed-base-sizes.cpp", "w") as fh:
    for fix in fixed_base_size:
        # {"49106", {27, 28}},
        clean_name = fix["name"].replace("\\", "\\\\")
        fh.write(
            f"{{\"{fix['id']}\", {{{fix['base_size'][0]}, {fix['base_size'][1]}}}}}, /* {clean_name} */\n"
        )
with open("fixed-3-scales.cpp", "w") as fh:
    for fix in fixed_3_scales:
        # {"49106", 0.33333333},
        clean_name = fix["name"].replace("\\", "\\\\")
        fh.write(f"{{\"{fix['id']}\", {fix['3.0_scale']}}}, /* {clean_name} */\n")