stickers_emoji_pack.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "chat_helpers/stickers_emoji_pack.h"
  8. #include "chat_helpers/stickers_emoji_image_loader.h"
  9. #include "history/view/history_view_element.h"
  10. #include "history/history_item.h"
  11. #include "history/history.h"
  12. #include "lottie/lottie_common.h"
  13. #include "ui/emoji_config.h"
  14. #include "ui/text/text_isolated_emoji.h"
  15. #include "ui/image/image.h"
  16. #include "ui/rect.h"
  17. #include "main/main_session.h"
  18. #include "data/data_file_origin.h"
  19. #include "data/data_session.h"
  20. #include "data/data_document.h"
  21. #include "data/stickers/data_custom_emoji.h"
  22. #include "core/core_settings.h"
  23. #include "core/application.h"
  24. #include "base/call_delayed.h"
  25. #include "chat_helpers/stickers_lottie.h"
  26. #include "history/view/media/history_view_sticker.h"
  27. #include "lottie/lottie_single_player.h"
  28. #include "apiwrap.h"
  29. #include "styles/style_chat.h"
  30. #include <QtCore/QBuffer>
  31. namespace Stickers {
  32. namespace {
  33. constexpr auto kRefreshTimeout = 7200 * crl::time(1000);
  34. constexpr auto kEmojiCachesCount = 4;
  35. constexpr auto kPremiumCachesCount = 8;
  36. [[nodiscard]] std::optional<int> IndexFromEmoticon(const QString &emoticon) {
  37. if (emoticon.size() < 2) {
  38. return std::nullopt;
  39. }
  40. const auto first = emoticon[0].unicode();
  41. return (first >= '1' && first <= '9')
  42. ? std::make_optional(first - '1')
  43. : (first == 55357 && emoticon[1].unicode() == 56607)
  44. ? std::make_optional(9)
  45. : std::nullopt;
  46. }
  47. [[nodiscard]] QSize SingleSize() {
  48. const auto single = st::largeEmojiSize;
  49. const auto outline = st::largeEmojiOutline;
  50. return Size(2 * outline + single) * style::DevicePixelRatio();
  51. }
  52. [[nodiscard]] const Lottie::ColorReplacements *ColorReplacements(int index) {
  53. Expects(index >= 1 && index <= 5);
  54. static const auto color1 = Lottie::ColorReplacements{
  55. .modifier = Lottie::SkinModifier::Color1,
  56. .tag = 1,
  57. };
  58. static const auto color2 = Lottie::ColorReplacements{
  59. .modifier = Lottie::SkinModifier::Color2,
  60. .tag = 2,
  61. };
  62. static const auto color3 = Lottie::ColorReplacements{
  63. .modifier = Lottie::SkinModifier::Color3,
  64. .tag = 3,
  65. };
  66. static const auto color4 = Lottie::ColorReplacements{
  67. .modifier = Lottie::SkinModifier::Color4,
  68. .tag = 4,
  69. };
  70. static const auto color5 = Lottie::ColorReplacements{
  71. .modifier = Lottie::SkinModifier::Color5,
  72. .tag = 5,
  73. };
  74. static const auto list = std::array{
  75. &color1,
  76. &color2,
  77. &color3,
  78. &color4,
  79. &color5,
  80. };
  81. return list[index - 1];
  82. }
  83. } // namespace
  84. QSize LargeEmojiImage::Size() {
  85. return SingleSize();
  86. }
  87. EmojiPack::EmojiPack(not_null<Main::Session*> session)
  88. : _session(session) {
  89. refresh();
  90. session->data().viewRemoved(
  91. ) | rpl::filter([](not_null<const ViewElement*> view) {
  92. return view->isIsolatedEmoji() || view->isOnlyCustomEmoji();
  93. }) | rpl::start_with_next([=](not_null<const ViewElement*> item) {
  94. remove(item);
  95. }, _lifetime);
  96. Core::App().settings().largeEmojiChanges(
  97. ) | rpl::start_with_next([=](bool large) {
  98. refreshAll();
  99. }, _lifetime);
  100. Ui::Emoji::Updated(
  101. ) | rpl::start_with_next([=] {
  102. _images.clear();
  103. refreshAll();
  104. }, _lifetime);
  105. }
  106. EmojiPack::~EmojiPack() = default;
  107. bool EmojiPack::add(not_null<ViewElement*> view) {
  108. if (const auto custom = view->onlyCustomEmoji()) {
  109. _onlyCustomItems.emplace(view);
  110. return true;
  111. } else if (const auto emoji = view->isolatedEmoji()) {
  112. _items[emoji].emplace(view);
  113. return true;
  114. }
  115. return false;
  116. }
  117. void EmojiPack::remove(not_null<const ViewElement*> view) {
  118. Expects(view->isIsolatedEmoji() || view->isOnlyCustomEmoji());
  119. if (view->isOnlyCustomEmoji()) {
  120. _onlyCustomItems.remove(view);
  121. } else if (const auto emoji = view->isolatedEmoji()) {
  122. const auto i = _items.find(emoji);
  123. Assert(i != end(_items));
  124. const auto j = i->second.find(view);
  125. Assert(j != end(i->second));
  126. i->second.erase(j);
  127. if (i->second.empty()) {
  128. _items.erase(i);
  129. }
  130. }
  131. }
  132. auto EmojiPack::stickerForEmoji(EmojiPtr emoji) -> Sticker {
  133. Expects(emoji != nullptr);
  134. const auto i = _map.find(emoji);
  135. if (i != end(_map)) {
  136. return { i->second.get(), nullptr };
  137. }
  138. if (!emoji->colored()) {
  139. return {};
  140. }
  141. const auto j = _map.find(emoji->original());
  142. if (j != end(_map)) {
  143. const auto index = emoji->variantIndex(emoji);
  144. return { j->second.get(), ColorReplacements(index) };
  145. }
  146. return {};
  147. }
  148. auto EmojiPack::stickerForEmoji(const IsolatedEmoji &emoji) -> Sticker {
  149. Expects(!emoji.empty());
  150. if (!v::is_null(emoji.items[1])) {
  151. return {};
  152. } else if (const auto regular = std::get_if<EmojiPtr>(&emoji.items[0])) {
  153. return stickerForEmoji(*regular);
  154. }
  155. return {};
  156. }
  157. std::shared_ptr<LargeEmojiImage> EmojiPack::image(EmojiPtr emoji) {
  158. const auto i = _images.emplace(
  159. emoji,
  160. std::weak_ptr<LargeEmojiImage>()).first;
  161. if (const auto result = i->second.lock()) {
  162. return result;
  163. }
  164. auto result = std::make_shared<LargeEmojiImage>();
  165. const auto raw = result.get();
  166. const auto weak = base::make_weak(_session);
  167. raw->load = [=] {
  168. Core::App().emojiImageLoader().with([=](
  169. const EmojiImageLoader &loader) {
  170. crl::on_main(weak, [
  171. =,
  172. image = loader.prepare(emoji)
  173. ]() mutable {
  174. const auto i = _images.find(emoji);
  175. if (i != end(_images)) {
  176. if (const auto strong = i->second.lock()) {
  177. if (!strong->image) {
  178. strong->load = nullptr;
  179. strong->image.emplace(std::move(image));
  180. _session->notifyDownloaderTaskFinished();
  181. }
  182. }
  183. }
  184. });
  185. });
  186. raw->load = nullptr;
  187. };
  188. i->second = result;
  189. return result;
  190. }
  191. EmojiPtr EmojiPack::chooseInteractionEmoji(
  192. not_null<HistoryItem*> item) const {
  193. return chooseInteractionEmoji(item->originalText().text);
  194. }
  195. EmojiPtr EmojiPack::chooseInteractionEmoji(
  196. const QString &emoticon) const {
  197. const auto emoji = Ui::Emoji::Find(emoticon);
  198. if (!emoji) {
  199. return nullptr;
  200. }
  201. if (!animationsForEmoji(emoji).empty()) {
  202. return emoji;
  203. }
  204. if (const auto original = emoji->original(); original != emoji) {
  205. if (!animationsForEmoji(original).empty()) {
  206. return original;
  207. }
  208. }
  209. static const auto kHearts = {
  210. QString::fromUtf8("\xf0\x9f\x92\x9b"),
  211. QString::fromUtf8("\xf0\x9f\x92\x99"),
  212. QString::fromUtf8("\xf0\x9f\x92\x9a"),
  213. QString::fromUtf8("\xf0\x9f\x92\x9c"),
  214. QString::fromUtf8("\xf0\x9f\xa7\xa1"),
  215. QString::fromUtf8("\xf0\x9f\x96\xa4"),
  216. QString::fromUtf8("\xf0\x9f\xa4\x8e"),
  217. QString::fromUtf8("\xf0\x9f\xa4\x8d"),
  218. };
  219. return ranges::contains(kHearts, emoji->id())
  220. ? Ui::Emoji::Find(QString::fromUtf8("\xe2\x9d\xa4"))
  221. : emoji;
  222. }
  223. auto EmojiPack::animationsForEmoji(EmojiPtr emoji) const
  224. -> const base::flat_map<int, not_null<DocumentData*>> & {
  225. static const auto empty = base::flat_map<int, not_null<DocumentData*>>();
  226. if (!emoji) {
  227. return empty;
  228. }
  229. const auto i = _animations.find(emoji);
  230. return (i != end(_animations)) ? i->second : empty;
  231. }
  232. bool EmojiPack::hasAnimationsFor(not_null<HistoryItem*> item) const {
  233. return !animationsForEmoji(chooseInteractionEmoji(item)).empty();
  234. }
  235. bool EmojiPack::hasAnimationsFor(const QString &emoticon) const {
  236. return !animationsForEmoji(chooseInteractionEmoji(emoticon)).empty();
  237. }
  238. std::unique_ptr<Lottie::SinglePlayer> EmojiPack::effectPlayer(
  239. not_null<DocumentData*> document,
  240. QByteArray data,
  241. QString filepath,
  242. EffectType type) {
  243. // Shortened copy from stickers_lottie module.
  244. const auto baseKey = document->bigFileBaseCacheKey();
  245. const auto tag = uint8(type);
  246. const auto keyShift = ((tag << 4) & 0xF0)
  247. | (uint8(ChatHelpers::StickerLottieSize::EmojiInteraction) & 0x0F);
  248. const auto key = Storage::Cache::Key{
  249. baseKey.high,
  250. baseKey.low + keyShift
  251. };
  252. const auto get = [=](int i, FnMut<void(QByteArray &&cached)> handler) {
  253. document->owner().cacheBigFile().get(
  254. { key.high, key.low + i },
  255. std::move(handler));
  256. };
  257. const auto weak = base::make_weak(&document->session());
  258. const auto put = [=](int i, QByteArray &&cached) {
  259. crl::on_main(weak, [=, data = std::move(cached)]() mutable {
  260. weak->data().cacheBigFile().put(
  261. { key.high, key.low + i },
  262. std::move(data));
  263. });
  264. };
  265. const auto size = (type == EffectType::PremiumSticker)
  266. ? HistoryView::Sticker::PremiumEffectSize(document)
  267. : (type == EffectType::EmojiInteraction)
  268. ? HistoryView::Sticker::EmojiEffectSize()
  269. : HistoryView::Sticker::MessageEffectSize();
  270. const auto request = Lottie::FrameRequest{
  271. size * style::DevicePixelRatio(),
  272. };
  273. auto &weakProvider = _sharedProviders[{ document, type }];
  274. auto shared = [&] {
  275. if (const auto result = weakProvider.lock()) {
  276. return result;
  277. }
  278. const auto count = (type == EffectType::PremiumSticker)
  279. ? kPremiumCachesCount
  280. : kEmojiCachesCount;
  281. const auto result = Lottie::SinglePlayer::SharedProvider(
  282. count,
  283. get,
  284. put,
  285. Lottie::ReadContent(data, filepath),
  286. request,
  287. Lottie::Quality::High);
  288. weakProvider = result;
  289. return result;
  290. }();
  291. return std::make_unique<Lottie::SinglePlayer>(std::move(shared), request);
  292. }
  293. void EmojiPack::refresh() {
  294. if (_requestId) {
  295. return;
  296. }
  297. _requestId = _session->api().request(MTPmessages_GetStickerSet(
  298. MTP_inputStickerSetAnimatedEmoji(),
  299. MTP_int(0) // hash
  300. )).done([=](const MTPmessages_StickerSet &result) {
  301. _requestId = 0;
  302. refreshAnimations();
  303. result.match([&](const MTPDmessages_stickerSet &data) {
  304. applySet(data);
  305. }, [](const MTPDmessages_stickerSetNotModified &) {
  306. LOG(("API Error: Unexpected messages.stickerSetNotModified."));
  307. });
  308. }).fail([=](const MTP::Error &error) {
  309. _requestId = 0;
  310. refreshDelayed();
  311. }).send();
  312. }
  313. void EmojiPack::refreshAnimations() {
  314. if (_animationsRequestId) {
  315. return;
  316. }
  317. _animationsRequestId = _session->api().request(MTPmessages_GetStickerSet(
  318. MTP_inputStickerSetAnimatedEmojiAnimations(),
  319. MTP_int(0) // hash
  320. )).done([=](const MTPmessages_StickerSet &result) {
  321. _animationsRequestId = 0;
  322. refreshDelayed();
  323. result.match([&](const MTPDmessages_stickerSet &data) {
  324. applyAnimationsSet(data);
  325. }, [](const MTPDmessages_stickerSetNotModified &) {
  326. LOG(("API Error: Unexpected messages.stickerSetNotModified."));
  327. });
  328. }).fail([=] {
  329. _animationsRequestId = 0;
  330. refreshDelayed();
  331. }).send();
  332. }
  333. void EmojiPack::applySet(const MTPDmessages_stickerSet &data) {
  334. const auto stickers = collectStickers(data.vdocuments().v);
  335. auto was = base::take(_map);
  336. for (const auto &pack : data.vpacks().v) {
  337. pack.match([&](const MTPDstickerPack &data) {
  338. applyPack(data, stickers);
  339. });
  340. }
  341. for (const auto &[emoji, document] : _map) {
  342. const auto i = was.find(emoji);
  343. if (i == end(was)) {
  344. refreshItems(emoji);
  345. } else {
  346. if (i->second != document) {
  347. refreshItems(i->first);
  348. }
  349. was.erase(i);
  350. }
  351. }
  352. for (const auto &[emoji, document] : was) {
  353. refreshItems(emoji);
  354. }
  355. _refreshed.fire({});
  356. }
  357. void EmojiPack::applyAnimationsSet(const MTPDmessages_stickerSet &data) {
  358. const auto stickers = collectStickers(data.vdocuments().v);
  359. const auto &packs = data.vpacks().v;
  360. const auto indices = collectAnimationsIndices(packs);
  361. _animations.clear();
  362. for (const auto &pack : packs) {
  363. pack.match([&](const MTPDstickerPack &data) {
  364. const auto emoticon = qs(data.vemoticon());
  365. if (IndexFromEmoticon(emoticon).has_value()) {
  366. return;
  367. }
  368. const auto emoji = Ui::Emoji::Find(emoticon);
  369. if (!emoji) {
  370. return;
  371. }
  372. for (const auto &id : data.vdocuments().v) {
  373. const auto i = indices.find(id.v);
  374. if (i == end(indices)) {
  375. continue;
  376. }
  377. const auto j = stickers.find(id.v);
  378. if (j == end(stickers)) {
  379. continue;
  380. }
  381. for (const auto index : i->second) {
  382. _animations[emoji].emplace(index, j->second);
  383. }
  384. }
  385. });
  386. }
  387. ++_animationsVersion;
  388. }
  389. auto EmojiPack::collectAnimationsIndices(
  390. const QVector<MTPStickerPack> &packs
  391. ) const -> base::flat_map<uint64, base::flat_set<int>> {
  392. auto result = base::flat_map<uint64, base::flat_set<int>>();
  393. for (const auto &pack : packs) {
  394. pack.match([&](const MTPDstickerPack &data) {
  395. if (const auto index = IndexFromEmoticon(qs(data.vemoticon()))) {
  396. for (const auto &id : data.vdocuments().v) {
  397. result[id.v].emplace(*index);
  398. }
  399. }
  400. });
  401. }
  402. return result;
  403. }
  404. void EmojiPack::refreshAll() {
  405. auto items = base::flat_set<not_null<HistoryItem*>>();
  406. auto count = 0;
  407. for (const auto &[emoji, list] : _items) {
  408. // refreshItems(list); // This call changes _items!
  409. count += int(list.size());
  410. }
  411. items.reserve(count);
  412. for (const auto &[emoji, list] : _items) {
  413. // refreshItems(list); // This call changes _items!
  414. for (const auto &view : list) {
  415. items.emplace(view->data());
  416. }
  417. }
  418. refreshItems(items);
  419. refreshItems(_onlyCustomItems);
  420. }
  421. void EmojiPack::refreshItems(EmojiPtr emoji) {
  422. const auto i = _items.find(IsolatedEmoji{ { emoji } });
  423. if (!emoji->colored()) {
  424. if (const auto count = emoji->variantsCount()) {
  425. for (auto i = 0; i != count; ++i) {
  426. refreshItems(emoji->variant(i + 1));
  427. }
  428. }
  429. }
  430. if (i == end(_items)) {
  431. return;
  432. }
  433. refreshItems(i->second);
  434. }
  435. void EmojiPack::refreshItems(
  436. const base::flat_set<not_null<ViewElement*>> &list) {
  437. auto items = base::flat_set<not_null<HistoryItem*>>();
  438. items.reserve(list.size());
  439. for (const auto &view : list) {
  440. items.emplace(view->data());
  441. }
  442. refreshItems(items);
  443. }
  444. void EmojiPack::refreshItems(
  445. const base::flat_set<not_null<HistoryItem*>> &items) {
  446. for (const auto &item : items) {
  447. _session->data().requestItemViewRefresh(item);
  448. }
  449. }
  450. void EmojiPack::applyPack(
  451. const MTPDstickerPack &data,
  452. const base::flat_map<uint64, not_null<DocumentData*>> &map) {
  453. const auto emoji = [&] {
  454. return Ui::Emoji::Find(qs(data.vemoticon()));
  455. }();
  456. const auto document = [&]() -> DocumentData * {
  457. for (const auto &id : data.vdocuments().v) {
  458. const auto i = map.find(id.v);
  459. if (i != end(map)) {
  460. return i->second.get();
  461. }
  462. }
  463. return nullptr;
  464. }();
  465. if (emoji && document) {
  466. _map.emplace_or_assign(emoji, document);
  467. }
  468. }
  469. base::flat_map<uint64, not_null<DocumentData*>> EmojiPack::collectStickers(
  470. const QVector<MTPDocument> &list) const {
  471. auto result = base::flat_map<uint64, not_null<DocumentData*>>();
  472. for (const auto &sticker : list) {
  473. const auto document = _session->data().processDocument(
  474. sticker);
  475. if (document->sticker()) {
  476. result.emplace(document->id, document);
  477. }
  478. }
  479. return result;
  480. }
  481. void EmojiPack::refreshDelayed() {
  482. base::call_delayed(kRefreshTimeout, _session, [=] {
  483. refresh();
  484. });
  485. }
  486. } // namespace Stickers