stickers_lottie.cpp 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  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_lottie.h"
  8. #include "lottie/lottie_single_player.h"
  9. #include "lottie/lottie_multi_player.h"
  10. #include "data/stickers/data_stickers_set.h"
  11. #include "data/data_document.h"
  12. #include "data/data_document_media.h"
  13. #include "data/data_session.h"
  14. #include "data/data_file_origin.h"
  15. #include "storage/cache/storage_cache_database.h"
  16. #include "storage/localimageloader.h"
  17. #include "history/view/media/history_view_media_common.h"
  18. #include "media/clip/media_clip_reader.h"
  19. #include "ui/chat/attach/attach_prepare.h"
  20. #include "ui/effects/path_shift_gradient.h"
  21. #include "ui/image/image_location_factory.h"
  22. #include "ui/painter.h"
  23. #include "main/main_session.h"
  24. #include <xxhash.h>
  25. namespace ChatHelpers {
  26. namespace {
  27. constexpr auto kDontCacheLottieAfterArea = 512 * 512;
  28. [[nodiscard]] uint64 LocalStickerId(QStringView name) {
  29. auto full = u"local_sticker:"_q;
  30. full.append(name);
  31. return XXH64(full.data(), full.size() * sizeof(QChar), 0);
  32. }
  33. } // namespace
  34. uint8 LottieCacheKeyShift(uint8 replacementsTag, StickerLottieSize sizeTag) {
  35. return ((replacementsTag << 4) & 0xF0) | (uint8(sizeTag) & 0x0F);
  36. }
  37. template <typename Method>
  38. auto LottieCachedFromContent(
  39. Method &&method,
  40. Storage::Cache::Key baseKey,
  41. uint8 keyShift,
  42. not_null<Main::Session*> session,
  43. const QByteArray &content,
  44. QSize box) {
  45. const auto key = Storage::Cache::Key{
  46. baseKey.high,
  47. baseKey.low + keyShift
  48. };
  49. const auto get = [=](FnMut<void(QByteArray &&cached)> handler) {
  50. session->data().cacheBigFile().get(
  51. key,
  52. std::move(handler));
  53. };
  54. const auto weak = base::make_weak(session);
  55. const auto put = [=](QByteArray &&cached) {
  56. crl::on_main(weak, [=, data = std::move(cached)]() mutable {
  57. weak->data().cacheBigFile().put(key, std::move(data));
  58. });
  59. };
  60. return method(
  61. get,
  62. put,
  63. content,
  64. Lottie::FrameRequest{ box });
  65. }
  66. template <typename Method>
  67. auto LottieFromDocument(
  68. Method &&method,
  69. not_null<Data::DocumentMedia*> media,
  70. uint8 keyShift,
  71. QSize box) {
  72. const auto document = media->owner();
  73. const auto data = media->bytes();
  74. const auto filepath = document->filepath();
  75. if (box.width() * box.height() > kDontCacheLottieAfterArea) {
  76. // Don't use frame caching for large stickers.
  77. return method(
  78. Lottie::ReadContent(data, filepath),
  79. Lottie::FrameRequest{ box });
  80. }
  81. if (const auto baseKey = document->bigFileBaseCacheKey()) {
  82. return LottieCachedFromContent(
  83. std::forward<Method>(method),
  84. baseKey,
  85. keyShift,
  86. &document->session(),
  87. Lottie::ReadContent(data, filepath),
  88. box);
  89. }
  90. return method(
  91. Lottie::ReadContent(data, filepath),
  92. Lottie::FrameRequest{ box });
  93. }
  94. std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
  95. not_null<Data::DocumentMedia*> media,
  96. StickerLottieSize sizeTag,
  97. QSize box,
  98. Lottie::Quality quality,
  99. std::shared_ptr<Lottie::FrameRenderer> renderer) {
  100. return LottiePlayerFromDocument(
  101. media,
  102. nullptr,
  103. sizeTag,
  104. box,
  105. quality,
  106. std::move(renderer));
  107. }
  108. std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
  109. not_null<Data::DocumentMedia*> media,
  110. const Lottie::ColorReplacements *replacements,
  111. StickerLottieSize sizeTag,
  112. QSize box,
  113. Lottie::Quality quality,
  114. std::shared_ptr<Lottie::FrameRenderer> renderer) {
  115. const auto method = [&](auto &&...args) {
  116. return std::make_unique<Lottie::SinglePlayer>(
  117. std::forward<decltype(args)>(args)...,
  118. quality,
  119. replacements,
  120. std::move(renderer));
  121. };
  122. const auto keyShift = LottieCacheKeyShift(
  123. replacements ? replacements->tag : uint8(0),
  124. sizeTag);
  125. return LottieFromDocument(method, media, uint8(keyShift), box);
  126. }
  127. not_null<Lottie::Animation*> LottieAnimationFromDocument(
  128. not_null<Lottie::MultiPlayer*> player,
  129. not_null<Data::DocumentMedia*> media,
  130. StickerLottieSize sizeTag,
  131. QSize box) {
  132. const auto method = [&](auto &&...args) {
  133. return player->append(std::forward<decltype(args)>(args)...);
  134. };
  135. return LottieFromDocument(method, media, uint8(sizeTag), box);
  136. }
  137. bool HasLottieThumbnail(
  138. StickerType thumbType,
  139. Data::StickersSetThumbnailView *thumb,
  140. Data::DocumentMedia *media) {
  141. if (thumb) {
  142. return (thumbType == StickerType::Tgs)
  143. && !thumb->content().isEmpty();
  144. } else if (!media) {
  145. return false;
  146. }
  147. const auto document = media->owner();
  148. if (const auto info = document->sticker()) {
  149. if (!info->isLottie()) {
  150. return false;
  151. }
  152. media->automaticLoad(document->stickerSetOrigin(), nullptr);
  153. if (!media->loaded()) {
  154. return false;
  155. }
  156. return document->bigFileBaseCacheKey().valid();
  157. }
  158. return false;
  159. }
  160. std::unique_ptr<Lottie::SinglePlayer> LottieThumbnail(
  161. Data::StickersSetThumbnailView *thumb,
  162. Data::DocumentMedia *media,
  163. StickerLottieSize sizeTag,
  164. QSize box,
  165. std::shared_ptr<Lottie::FrameRenderer> renderer) {
  166. const auto baseKey = thumb
  167. ? thumb->owner()->thumbnailBigFileBaseCacheKey()
  168. : media
  169. ? media->owner()->bigFileBaseCacheKey()
  170. : Storage::Cache::Key();
  171. if (!baseKey) {
  172. return nullptr;
  173. }
  174. const auto content = thumb
  175. ? thumb->content()
  176. : Lottie::ReadContent(media->bytes(), media->owner()->filepath());
  177. if (content.isEmpty()) {
  178. return nullptr;
  179. }
  180. const auto method = [](auto &&...args) {
  181. return std::make_unique<Lottie::SinglePlayer>(
  182. std::forward<decltype(args)>(args)...);
  183. };
  184. const auto session = thumb
  185. ? &thumb->owner()->session()
  186. : &media->owner()->session();
  187. return LottieCachedFromContent(
  188. method,
  189. baseKey,
  190. uint8(sizeTag),
  191. session,
  192. content,
  193. box);
  194. }
  195. bool HasWebmThumbnail(
  196. StickerType thumbType,
  197. Data::StickersSetThumbnailView *thumb,
  198. Data::DocumentMedia *media) {
  199. if (thumb) {
  200. return (thumbType == StickerType::Webm)
  201. && !thumb->content().isEmpty();
  202. } else if (!media) {
  203. return false;
  204. }
  205. const auto document = media->owner();
  206. if (const auto info = document->sticker()) {
  207. if (!info->isWebm()) {
  208. return false;
  209. }
  210. media->automaticLoad(document->stickerSetOrigin(), nullptr);
  211. if (!media->loaded()) {
  212. return false;
  213. }
  214. return document->bigFileBaseCacheKey().valid();
  215. }
  216. return false;
  217. }
  218. Media::Clip::ReaderPointer WebmThumbnail(
  219. Data::StickersSetThumbnailView *thumb,
  220. Data::DocumentMedia *media,
  221. Fn<void(Media::Clip::Notification)> callback) {
  222. return thumb
  223. ? ::Media::Clip::MakeReader(
  224. thumb->content(),
  225. std::move(callback))
  226. : ::Media::Clip::MakeReader(
  227. media->owner()->location(),
  228. media->bytes(),
  229. std::move(callback));
  230. }
  231. bool PaintStickerThumbnailPath(
  232. QPainter &p,
  233. not_null<Data::DocumentMedia*> media,
  234. QRect target,
  235. QLinearGradient *gradient,
  236. bool mirrorHorizontal) {
  237. const auto &path = media->thumbnailPath();
  238. const auto dimensions = media->owner()->dimensions;
  239. if (path.isEmpty() || dimensions.isEmpty() || target.isEmpty()) {
  240. return false;
  241. }
  242. p.save();
  243. auto hq = PainterHighQualityEnabler(p);
  244. p.setPen(Qt::NoPen);
  245. p.translate(target.topLeft());
  246. if (gradient) {
  247. const auto scale = dimensions.width() / float64(target.width());
  248. const auto shift = p.worldTransform().dx();
  249. gradient->setStart((gradient->start().x() - shift) * scale, 0);
  250. gradient->setFinalStop(
  251. (gradient->finalStop().x() - shift) * scale,
  252. 0);
  253. p.setBrush(*gradient);
  254. }
  255. if (mirrorHorizontal) {
  256. const auto c = QPointF(target.width() / 2., target.height() / 2.);
  257. p.translate(c);
  258. p.scale(-1., 1.);
  259. p.translate(-c);
  260. }
  261. p.scale(
  262. target.width() / float64(dimensions.width()),
  263. target.height() / float64(dimensions.height()));
  264. p.drawPath(path);
  265. p.restore();
  266. return true;
  267. }
  268. bool PaintStickerThumbnailPath(
  269. QPainter &p,
  270. not_null<Data::DocumentMedia*> media,
  271. QRect target,
  272. not_null<Ui::PathShiftGradient*> gradient,
  273. bool mirrorHorizontal) {
  274. return gradient->paint([&](const Ui::PathShiftGradient::Background &bg) {
  275. if (const auto color = std::get_if<style::color>(&bg)) {
  276. p.setBrush(*color);
  277. return PaintStickerThumbnailPath(
  278. p,
  279. media,
  280. target,
  281. nullptr,
  282. mirrorHorizontal);
  283. }
  284. const auto gradient = v::get<QLinearGradient*>(bg);
  285. return PaintStickerThumbnailPath(
  286. p,
  287. media,
  288. target,
  289. gradient,
  290. mirrorHorizontal);
  291. });
  292. }
  293. QSize ComputeStickerSize(not_null<DocumentData*> document, QSize box) {
  294. const auto sticker = document->sticker();
  295. const auto dimensions = document->dimensions;
  296. if (!sticker || !sticker->isLottie() || dimensions.isEmpty()) {
  297. return HistoryView::DownscaledSize(dimensions, box);
  298. }
  299. const auto ratio = style::DevicePixelRatio();
  300. const auto request = Lottie::FrameRequest{ box * ratio };
  301. return HistoryView::NonEmptySize(request.size(dimensions, 8) / ratio);
  302. }
  303. not_null<DocumentData*> GenerateLocalSticker(
  304. not_null<Main::Session*> session,
  305. const QString &path) {
  306. auto task = FileLoadTask(
  307. session,
  308. path,
  309. QByteArray(),
  310. nullptr,
  311. nullptr,
  312. SendMediaType::File,
  313. FileLoadTo(0, {}, {}, 0),
  314. {},
  315. false,
  316. nullptr,
  317. LocalStickerId(path));
  318. task.process({ .generateGoodThumbnail = false });
  319. const auto result = task.peekResult();
  320. Assert(result != nullptr);
  321. const auto document = session->data().processDocument(
  322. result->document,
  323. Images::FromImageInMemory(
  324. result->thumb,
  325. "WEBP",
  326. result->thumbbytes));
  327. document->setLocation(Core::FileLocation(path));
  328. Ensures(document->sticker());
  329. return document;
  330. }
  331. not_null<DocumentData*> GenerateLocalTgsSticker(
  332. not_null<Main::Session*> session,
  333. const QString &name) {
  334. const auto result = GenerateLocalSticker(
  335. session,
  336. u":/animations/"_q + name + u".tgs"_q);
  337. Ensures(result->sticker()->isLottie());
  338. return result;
  339. }
  340. } // namespace ChatHelpers