diff --git a/Telegram/SourceFiles/chat_helpers/stickers.cpp b/Telegram/SourceFiles/chat_helpers/stickers.cpp index 5690e2e7a2..b74c5fe695 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers.cpp @@ -1115,13 +1115,13 @@ template <typename Method> auto LottieCachedFromContent( Method &&method, Storage::Cache::Key baseKey, - LottieSize sizeTag, + uint8 keyShift, not_null<Main::Session*> session, const QByteArray &content, QSize box) { const auto key = Storage::Cache::Key{ baseKey.high, - baseKey.low + int(sizeTag) + baseKey.low + keyShift }; const auto get = [=](FnMut<void(QByteArray &&cached)> handler) { session->data().cacheBigFile().get( @@ -1145,7 +1145,7 @@ template <typename Method> auto LottieFromDocument( Method &&method, not_null<DocumentData*> document, - LottieSize sizeTag, + uint8 keyShift, QSize box) { const auto data = document->data(); const auto filepath = document->filepath(); @@ -1159,7 +1159,7 @@ auto LottieFromDocument( return LottieCachedFromContent( std::forward<Method>(method), *baseKey, - sizeTag, + keyShift, &document->session(), Lottie::ReadContent(data, filepath), box); @@ -1175,11 +1175,32 @@ std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument( QSize box, Lottie::Quality quality, std::shared_ptr<Lottie::FrameRenderer> renderer) { + return LottiePlayerFromDocument( + document, + nullptr, + sizeTag, + box, + quality, + std::move(renderer)); +} + +std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument( + not_null<DocumentData*> document, + const Lottie::ColorReplacements *replacements, + LottieSize sizeTag, + QSize box, + Lottie::Quality quality, + std::shared_ptr<Lottie::FrameRenderer> renderer) { const auto method = [&](auto &&...args) { return std::make_unique<Lottie::SinglePlayer>( - std::forward<decltype(args)>(args)..., quality, std::move(renderer)); + std::forward<decltype(args)>(args)..., + quality, + replacements, + std::move(renderer)); }; - return LottieFromDocument(method, document, sizeTag, box); + const auto tag = replacements ? replacements->tag : uint8(0); + const auto keyShift = ((tag << 4) & 0xF0) | (uint8(sizeTag) & 0x0F); + return LottieFromDocument(method, document, uint8(keyShift), box); } not_null<Lottie::Animation*> LottieAnimationFromDocument( @@ -1190,7 +1211,7 @@ not_null<Lottie::Animation*> LottieAnimationFromDocument( const auto method = [&](auto &&...args) { return player->append(std::forward<decltype(args)>(args)...); }; - return LottieFromDocument(method, document, sizeTag, box); + return LottieFromDocument(method, document, uint8(sizeTag), box); } bool HasLottieThumbnail( @@ -1243,7 +1264,7 @@ std::unique_ptr<Lottie::SinglePlayer> LottieThumbnail( return LottieCachedFromContent( method, *baseKey, - sizeTag, + uint8(sizeTag), &sticker->session(), content, box); diff --git a/Telegram/SourceFiles/chat_helpers/stickers.h b/Telegram/SourceFiles/chat_helpers/stickers.h index ac850f53c5..3a4ac09250 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers.h +++ b/Telegram/SourceFiles/chat_helpers/stickers.h @@ -28,6 +28,7 @@ class MultiPlayer; class FrameRenderer; class Animation; enum class Quality : char; +struct ColorReplacements; } // namespace Lottie namespace Stickers { @@ -140,6 +141,13 @@ enum class LottieSize : uchar { QSize box, Lottie::Quality quality = Lottie::Quality(), std::shared_ptr<Lottie::FrameRenderer> renderer = nullptr); +[[nodiscard]] std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument( + not_null<DocumentData*> document, + const Lottie::ColorReplacements *replacements, + LottieSize sizeTag, + QSize box, + Lottie::Quality quality = Lottie::Quality(), + std::shared_ptr<Lottie::FrameRenderer> renderer = nullptr); [[nodiscard]] not_null<Lottie::Animation*> LottieAnimationFromDocument( not_null<Lottie::MultiPlayer*> player, not_null<DocumentData*> document, diff --git a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp index 96904ac1a7..69f917f43c 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/stickers_emoji_pack.h" #include "history/history_item.h" +#include "lottie/lottie_common.h" #include "ui/emoji_config.h" #include "ui/text/text_isolated_emoji.h" #include "ui/image/image_source.h" @@ -54,6 +55,64 @@ constexpr auto kClearSourceTimeout = 10 * crl::time(1000); ) * cIntRetinaFactor(); } +[[nodiscard]] const Lottie::ColorReplacements *ColorReplacements(int index) { + Expects(index >= 1 && index <= 5); + + static const auto color1 = Lottie::ColorReplacements{ + { + { 0xf77e41U, 0xca907aU }, + { 0xffb139U, 0xedc5a5U }, + { 0xffd140U, 0xf7e3c3U }, + { 0xffdf79U, 0xfbefd6U }, + }, + 1, + }; + static const auto color2 = Lottie::ColorReplacements{ + { + { 0xf77e41U, 0xaa7c60U }, + { 0xffb139U, 0xc8a987U }, + { 0xffd140U, 0xddc89fU }, + { 0xffdf79U, 0xe6d6b2U }, + }, + 2, + }; + static const auto color3 = Lottie::ColorReplacements{ + { + { 0xf77e41U, 0x8c6148U }, + { 0xffb139U, 0xad8562U }, + { 0xffd140U, 0xc49e76U }, + { 0xffdf79U, 0xd4b188U }, + }, + 3, + }; + static const auto color4 = Lottie::ColorReplacements{ + { + { 0xf77e41U, 0x6e3c2cU }, + { 0xffb139U, 0x925a34U }, + { 0xffd140U, 0xa16e46U }, + { 0xffdf79U, 0xac7a52U }, + }, + 4, + }; + static const auto color5 = Lottie::ColorReplacements{ + { + { 0xf77e41U, 0x291c12U }, + { 0xffb139U, 0x472a22U }, + { 0xffd140U, 0x573b30U }, + { 0xffdf79U, 0x68493cU }, + }, + 5, + }; + static const auto list = std::array{ + &color1, + &color2, + &color3, + &color4, + &color5, + }; + return list[index - 1]; +} + class ImageSource : public Images::Source { public: explicit ImageSource( @@ -388,14 +447,26 @@ void EmojiPack::remove(not_null<const HistoryItem*> item) { } } -DocumentData *EmojiPack::stickerForEmoji(const IsolatedEmoji &emoji) { +auto EmojiPack::stickerForEmoji(const IsolatedEmoji &emoji) -> Sticker { Expects(!emoji.empty()); if (emoji.items[1] != nullptr) { - return nullptr; + return Sticker(); } - const auto i = _map.find(emoji.items[0]); - return (i != end(_map)) ? i->second.get() : nullptr; + const auto first = emoji.items[0]; + const auto i = _map.find(first); + if (i != end(_map)) { + return { i->second.get(), nullptr }; + } + if (!first->colored()) { + return Sticker(); + } + const auto j = _map.find(first->original()); + if (j != end(_map)) { + const auto index = first->variantIndex(first); + return { j->second.get(), details::ColorReplacements(index) }; + } + return Sticker(); } std::shared_ptr<Image> EmojiPack::image(EmojiPtr emoji) { diff --git a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h index 68ed3c3e32..16d3e1476a 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h @@ -12,12 +12,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include <crl/crl_object_on_queue.h> +class HistoryItem; +class DocumentData; + namespace Main { class Session; } // namespace Main -class HistoryItem; -class DocumentData; +namespace Lottie { +struct ColorReplacements; +} // namespace Lottie namespace Ui { namespace Text { @@ -37,13 +41,25 @@ using IsolatedEmoji = Ui::Text::IsolatedEmoji; class EmojiPack final { public: + struct Sticker { + DocumentData *document = nullptr; + const Lottie::ColorReplacements *replacements = nullptr; + + [[nodiscard]] bool empty() const { + return (document == nullptr); + } + [[nodiscard]] explicit operator bool() const { + return !empty(); + } + }; + explicit EmojiPack(not_null<Main::Session*> session); ~EmojiPack(); bool add(not_null<HistoryItem*> item); void remove(not_null<const HistoryItem*> item); - [[nodiscard]] DocumentData *stickerForEmoji(const IsolatedEmoji &emoji); + [[nodiscard]] Sticker stickerForEmoji(const IsolatedEmoji &emoji); [[nodiscard]] std::shared_ptr<Image> image(EmojiPtr emoji); private: diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 5bc8eb13fa..fbcbd6476b 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -349,10 +349,13 @@ void Element::refreshMedia() { && session->settings().largeEmoji()) { const auto emoji = _data->isolatedEmoji(); const auto emojiStickers = &session->emojiStickersPack(); - if (const auto document = emojiStickers->stickerForEmoji(emoji)) { + if (const auto sticker = emojiStickers->stickerForEmoji(emoji)) { _media = std::make_unique<UnwrappedMedia>( this, - std::make_unique<Sticker>(this, document)); + std::make_unique<Sticker>( + this, + sticker.document, + sticker.replacements)); } else { _media = std::make_unique<UnwrappedMedia>( this, diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp index a5dd6db3d5..0e3cbe7682 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp @@ -37,9 +37,11 @@ double GetEmojiStickerZoom(not_null<Main::Session*> session) { Sticker::Sticker( not_null<Element*> parent, - not_null<DocumentData*> document) + not_null<DocumentData*> document, + const Lottie::ColorReplacements *replacements) : _parent(parent) -, _document(document) { +, _document(document) +, _replacements(replacements) { _document->loadThumbnail(parent->data()->fullId()); } @@ -82,7 +84,7 @@ void Sticker::draw(Painter &p, const QRect &r, bool selected) { if (_lottie && _lottie->ready()) { paintLottie(p, r, selected); - } else { + } else if (!_lottie || !_replacements) { paintPixmap(p, r, selected); } } @@ -185,8 +187,9 @@ void Sticker::refreshLink() { void Sticker::setupLottie() { _lottie = Stickers::LottiePlayerFromDocument( _document, + _replacements, Stickers::LottieSize::MessageHistory, - QSize(st::maxStickerSize, st::maxStickerSize) * cIntRetinaFactor(), + _size * cIntRetinaFactor(), Lottie::Quality::High); _parent->data()->history()->owner().registerHeavyViewPart(_parent); diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.h b/Telegram/SourceFiles/history/view/media/history_view_sticker.h index 9b454364a0..6ee7370e48 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.h +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.h @@ -16,6 +16,7 @@ struct FileOrigin; namespace Lottie { class SinglePlayer; +struct ColorReplacements; } // namespace Lottie namespace HistoryView { @@ -26,7 +27,8 @@ class Sticker final public: Sticker( not_null<Element*> parent, - not_null<DocumentData*> document); + not_null<DocumentData*> document, + const Lottie::ColorReplacements *replacements = nullptr); ~Sticker(); QSize size() override; @@ -57,6 +59,7 @@ private: const not_null<Element*> _parent; const not_null<DocumentData*> _document; + const Lottie::ColorReplacements *_replacements = nullptr; std::unique_ptr<Lottie::SinglePlayer> _lottie; ClickHandlerPtr _link; QSize _size; diff --git a/Telegram/SourceFiles/lottie/lottie_animation.cpp b/Telegram/SourceFiles/lottie/lottie_animation.cpp index b9ed559927..f681c9220d 100644 --- a/Telegram/SourceFiles/lottie/lottie_animation.cpp +++ b/Telegram/SourceFiles/lottie/lottie_animation.cpp @@ -81,11 +81,12 @@ details::InitData CheckSharedState(std::unique_ptr<SharedState> state) { details::InitData Init( const QByteArray &content, const FrameRequest &request, - Quality quality) { + Quality quality, + const ColorReplacements *replacements) { if (const auto error = ContentError(content)) { return *error; } - auto animation = details::CreateFromContent(content); + auto animation = details::CreateFromContent(content, replacements); return animation ? CheckSharedState(std::make_unique<SharedState>( std::move(animation), @@ -99,7 +100,8 @@ details::InitData Init( FnMut<void(QByteArray &&cached)> put, const QByteArray &cached, const FrameRequest &request, - Quality quality) { + Quality quality, + const ColorReplacements *replacements) { Expects(!request.empty()); if (const auto error = ContentError(content)) { @@ -108,10 +110,13 @@ details::InitData Init( auto cache = std::make_unique<Cache>(cached, request, std::move(put)); const auto prepare = !cache->framesCount() || (cache->framesReady() < cache->framesCount()); - auto animation = prepare ? details::CreateFromContent(content) : nullptr; + auto animation = prepare + ? details::CreateFromContent(content, replacements) + : nullptr; return (!prepare || animation) ? CheckSharedState(std::make_unique<SharedState>( content, + replacements, std::move(animation), std::move(cache), request, @@ -124,7 +129,8 @@ details::InitData Init( namespace details { std::unique_ptr<rlottie::Animation> CreateFromContent( - const QByteArray &content) { + const QByteArray &content, + const ColorReplacements *replacements) { const auto string = UnpackGzip(content); Assert(string.size() <= kMaxFileSize); @@ -132,7 +138,10 @@ std::unique_ptr<rlottie::Animation> CreateFromContent( string, std::string(), std::string(), - false); + false, + (replacements + ? replacements->replacements + : std::vector<std::pair<std::uint32_t, std::uint32_t>>())); if (!result) { LOG(("Lottie Error: Parse failed.")); } @@ -146,8 +155,8 @@ std::shared_ptr<FrameRenderer> MakeFrameRenderer() { } QImage ReadThumbnail(const QByteArray &content) { - return Init(content, FrameRequest(), Quality::High).match([]( - const std::unique_ptr<SharedState> &state) { + return Init(content, FrameRequest(), Quality::High, nullptr).match([]( + const std::unique_ptr<SharedState> &state) { return state->frameForPaint()->original; }, [](Error) { return QImage(); @@ -158,11 +167,13 @@ Animation::Animation( not_null<Player*> player, const QByteArray &content, const FrameRequest &request, - Quality quality) + Quality quality, + const ColorReplacements *replacements) : _player(player) { const auto weak = base::make_weak(this); crl::async([=] { - crl::on_main(weak, [=, data = Init(content, request, quality)]() mutable { + auto result = Init(content, request, quality, replacements); + crl::on_main(weak, [=, data = std::move(result)]() mutable { initDone(std::move(data)); }); }); @@ -174,12 +185,19 @@ Animation::Animation( FnMut<void(QByteArray &&cached)> put, // Unknown thread. const QByteArray &content, const FrameRequest &request, - Quality quality) + Quality quality, + const ColorReplacements *replacements) : _player(player) { const auto weak = base::make_weak(this); get([=, put = std::move(put)](QByteArray &&cached) mutable { crl::async([=, put = std::move(put)]() mutable { - auto result = Init(content, std::move(put), cached, request, quality); + auto result = Init( + content, + std::move(put), + cached, + request, + quality, + replacements); crl::on_main(weak, [=, data = std::move(result)]() mutable { initDone(std::move(data)); }); diff --git a/Telegram/SourceFiles/lottie/lottie_animation.h b/Telegram/SourceFiles/lottie/lottie_animation.h index 23922bd6c4..09d16f4eae 100644 --- a/Telegram/SourceFiles/lottie/lottie_animation.h +++ b/Telegram/SourceFiles/lottie/lottie_animation.h @@ -34,7 +34,8 @@ namespace details { using InitData = base::variant<std::unique_ptr<SharedState>, Error>; std::unique_ptr<rlottie::Animation> CreateFromContent( - const QByteArray &content); + const QByteArray &content, + const ColorReplacements *replacements); } // namespace details @@ -49,14 +50,16 @@ public: not_null<Player*> player, const QByteArray &content, const FrameRequest &request, - Quality quality); + Quality quality, + const ColorReplacements *replacements = nullptr); Animation( not_null<Player*> player, FnMut<void(FnMut<void(QByteArray &&cached)>)> get, // Main thread. FnMut<void(QByteArray &&cached)> put, // Unknown thread. const QByteArray &content, const FrameRequest &request, - Quality quality); + Quality quality, + const ColorReplacements *replacements = nullptr); [[nodiscard]] bool ready() const; [[nodiscard]] QImage frame() const; diff --git a/Telegram/SourceFiles/lottie/lottie_common.h b/Telegram/SourceFiles/lottie/lottie_common.h index 53e9c66b6f..560d2e7ddb 100644 --- a/Telegram/SourceFiles/lottie/lottie_common.h +++ b/Telegram/SourceFiles/lottie/lottie_common.h @@ -55,6 +55,11 @@ enum class Quality : char { High, }; +struct ColorReplacements { + std::vector<std::pair<std::uint32_t, std::uint32_t>> replacements; + uint8 tag = 0; +}; + QByteArray ReadContent(const QByteArray &data, const QString &filepath); } // namespace Lottie diff --git a/Telegram/SourceFiles/lottie/lottie_frame_renderer.cpp b/Telegram/SourceFiles/lottie/lottie_frame_renderer.cpp index f1aa33db38..caae93a9d5 100644 --- a/Telegram/SourceFiles/lottie/lottie_frame_renderer.cpp +++ b/Telegram/SourceFiles/lottie/lottie_frame_renderer.cpp @@ -255,6 +255,7 @@ SharedState::SharedState( SharedState::SharedState( const QByteArray &content, + const ColorReplacements *replacements, std::unique_ptr<rlottie::Animation> animation, std::unique_ptr<Cache> cache, const FrameRequest &request, @@ -263,7 +264,8 @@ SharedState::SharedState( , _quality(quality) , _cache(std::move(cache)) , _animation(std::move(animation)) -, _content(content) { +, _content(content) +, _replacements(replacements) { construct(request); } @@ -310,7 +312,7 @@ void SharedState::renderFrame( if (_cache && _cache->renderFrame(image, request, index)) { return; } else if (!_animation) { - _animation = details::CreateFromContent(_content); + _animation = details::CreateFromContent(_content, _replacements); } image.fill(Qt::transparent); diff --git a/Telegram/SourceFiles/lottie/lottie_frame_renderer.h b/Telegram/SourceFiles/lottie/lottie_frame_renderer.h index dcb4420c67..60d0336c1d 100644 --- a/Telegram/SourceFiles/lottie/lottie_frame_renderer.h +++ b/Telegram/SourceFiles/lottie/lottie_frame_renderer.h @@ -57,6 +57,7 @@ public: Quality quality); SharedState( const QByteArray &content, + const ColorReplacements *replacements, std::unique_ptr<rlottie::Animation> animation, std::unique_ptr<Cache> cache, const FrameRequest &request, @@ -129,6 +130,7 @@ private: std::unique_ptr<rlottie::Animation> _animation; const QByteArray _content; + const ColorReplacements *_replacements = nullptr; }; diff --git a/Telegram/SourceFiles/lottie/lottie_single_player.cpp b/Telegram/SourceFiles/lottie/lottie_single_player.cpp index c2fa8d9226..d88a2cb596 100644 --- a/Telegram/SourceFiles/lottie/lottie_single_player.cpp +++ b/Telegram/SourceFiles/lottie/lottie_single_player.cpp @@ -15,8 +15,9 @@ SinglePlayer::SinglePlayer( const QByteArray &content, const FrameRequest &request, Quality quality, + const ColorReplacements *replacements, std::shared_ptr<FrameRenderer> renderer) -: _animation(this, content, request, quality) +: _animation(this, content, request, quality, replacements) , _timer([=] { checkNextFrameRender(); }) , _renderer(renderer ? renderer : FrameRenderer::Instance()) { } @@ -27,8 +28,16 @@ SinglePlayer::SinglePlayer( const QByteArray &content, const FrameRequest &request, Quality quality, + const ColorReplacements *replacements, std::shared_ptr<FrameRenderer> renderer) -: _animation(this, std::move(get), std::move(put), content, request, quality) +: _animation( + this, + std::move(get), + std::move(put), + content, + request, + quality, + replacements) , _timer([=] { checkNextFrameRender(); }) , _renderer(renderer ? renderer : FrameRenderer::Instance()) { } diff --git a/Telegram/SourceFiles/lottie/lottie_single_player.h b/Telegram/SourceFiles/lottie/lottie_single_player.h index 6a8c313bfe..772e7743ac 100644 --- a/Telegram/SourceFiles/lottie/lottie_single_player.h +++ b/Telegram/SourceFiles/lottie/lottie_single_player.h @@ -32,6 +32,7 @@ public: const QByteArray &content, const FrameRequest &request, Quality quality = Quality::Default, + const ColorReplacements *replacements = nullptr, std::shared_ptr<FrameRenderer> renderer = nullptr); SinglePlayer( FnMut<void(FnMut<void(QByteArray &&cached)>)> get, // Main thread. @@ -39,6 +40,7 @@ public: const QByteArray &content, const FrameRequest &request, Quality quality = Quality::Default, + const ColorReplacements *replacements = nullptr, std::shared_ptr<FrameRenderer> renderer = nullptr); ~SinglePlayer(); diff --git a/Telegram/ThirdParty/rlottie b/Telegram/ThirdParty/rlottie index bd9ee5239c..d08a03b650 160000 --- a/Telegram/ThirdParty/rlottie +++ b/Telegram/ThirdParty/rlottie @@ -1 +1 @@ -Subproject commit bd9ee5239c8caca95fee017a46c8e9198d7cdb21 +Subproject commit d08a03b6508b390af20491f2dbeee3453594afc8