info_userpic_emoji_builder_preview.cpp 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  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 "info/userpic/info_userpic_emoji_builder_preview.h"
  8. #include "chat_helpers/stickers_lottie.h"
  9. #include "data/data_document.h"
  10. #include "data/data_document_media.h"
  11. #include "data/data_session.h"
  12. #include "history/view/media/history_view_sticker_player.h"
  13. #include "info/userpic/info_userpic_emoji_builder_common.h"
  14. #include "main/main_app_config.h"
  15. #include "main/main_session.h"
  16. #include "ui/painter.h"
  17. #include "ui/rect.h"
  18. namespace UserpicBuilder {
  19. PreviewPainter::PreviewPainter(int size)
  20. : _size(size)
  21. , _emojiSize(base::SafeRound(_size / M_SQRT2))
  22. , _frameGeometry(Rect(Size(_size)) - Margins((_size - _emojiSize) / 2))
  23. , _frameRect(Rect(_frameGeometry.size()))
  24. , _mask(
  25. _frameRect.size() * style::DevicePixelRatio(),
  26. QImage::Format_ARGB32_Premultiplied)
  27. , _frame(_mask.size(), QImage::Format_ARGB32_Premultiplied) {
  28. _frame.setDevicePixelRatio(style::DevicePixelRatio());
  29. _mask.setDevicePixelRatio(style::DevicePixelRatio());
  30. _mask.fill(Qt::transparent);
  31. {
  32. auto p = QPainter(&_mask);
  33. auto hq = PainterHighQualityEnabler(p);
  34. p.setPen(Qt::NoPen);
  35. p.setBrush(st::windowBg);
  36. constexpr auto kFrameRadiusPercent = 25;
  37. p.drawRoundedRect(
  38. _frameRect,
  39. kFrameRadiusPercent,
  40. kFrameRadiusPercent,
  41. Qt::RelativeSize);
  42. }
  43. }
  44. DocumentData *PreviewPainter::document() const {
  45. return _media ? _media->owner().get() : nullptr;
  46. }
  47. void PreviewPainter::setPlayOnce(bool value) {
  48. _playOnce = value;
  49. }
  50. void PreviewPainter::setDocument(
  51. not_null<DocumentData*> document,
  52. Fn<void()> updateCallback) {
  53. if (_media && (document == _media->owner())) {
  54. return;
  55. }
  56. _lifetime.destroy();
  57. const auto sticker = document->sticker();
  58. Assert(sticker != nullptr);
  59. _media = document->createMediaView();
  60. _media->checkStickerLarge();
  61. _media->goodThumbnailWanted();
  62. if (_playOnce) {
  63. _firstFrameShown = false;
  64. _paused = false;
  65. } else {
  66. _paused = true;
  67. }
  68. rpl::single() | rpl::then(
  69. document->owner().session().downloaderTaskFinished()
  70. ) | rpl::start_with_next([=] {
  71. if (!_media->loaded()) {
  72. return;
  73. }
  74. _lifetime.destroy();
  75. const auto emojiSize = Size(_size * style::DevicePixelRatio());
  76. if (sticker->isLottie()) {
  77. _player = std::make_unique<HistoryView::LottiePlayer>(
  78. ChatHelpers::LottiePlayerFromDocument(
  79. _media.get(),
  80. //
  81. ChatHelpers::StickerLottieSize::EmojiInteractionReserved7,
  82. emojiSize,
  83. Lottie::Quality::High));
  84. } else if (sticker->isWebm()) {
  85. _player = std::make_unique<HistoryView::WebmPlayer>(
  86. _media->owner()->location(),
  87. _media->bytes(),
  88. emojiSize);
  89. } else if (sticker) {
  90. _player = std::make_unique<HistoryView::StaticStickerPlayer>(
  91. _media->owner()->location(),
  92. _media->bytes(),
  93. emojiSize);
  94. }
  95. if (_player) {
  96. _player->setRepaintCallback(updateCallback);
  97. } else if (updateCallback) {
  98. updateCallback();
  99. }
  100. }, _lifetime);
  101. }
  102. void PreviewPainter::paintBackground(QPainter &p, const QImage &image) {
  103. p.drawImage(0, 0, image);
  104. }
  105. bool PreviewPainter::paintForeground(QPainter &p) {
  106. if (_player && _player->ready()) {
  107. const auto c = _media->owner()->emojiUsesTextColor() ? 255 : 0;
  108. auto frame = _player->frame(
  109. Size(_emojiSize),
  110. QColor(c, c, c, c),
  111. false,
  112. crl::now(),
  113. _paused);
  114. if (_playOnce) {
  115. if (!_firstFrameShown && (frame.index == 1)) {
  116. _firstFrameShown = true;
  117. } else if (_firstFrameShown && !frame.index) {
  118. _paused = true;
  119. }
  120. }
  121. _frame.fill(Qt::transparent);
  122. {
  123. QPainter q(&_frame);
  124. if (frame.image.width() == frame.image.height()) {
  125. q.drawImage(_frameRect, frame.image);
  126. } else {
  127. auto frameRect = Rect(frame.image.size().scaled(
  128. _frameRect.size(),
  129. Qt::KeepAspectRatio));
  130. frameRect.moveCenter(_frameRect.center());
  131. q.drawImage(frameRect, frame.image);
  132. }
  133. q.setCompositionMode(QPainter::CompositionMode_DestinationIn);
  134. q.drawImage(0, 0, _mask);
  135. }
  136. p.drawImage(_frameGeometry.topLeft(), _frame);
  137. if (!_paused) {
  138. _player->markFrameShown();
  139. }
  140. return true;
  141. }
  142. return false;
  143. }
  144. EmojiUserpic::EmojiUserpic(
  145. not_null<Ui::RpWidget*> parent,
  146. const QSize &size,
  147. bool isForum)
  148. : Ui::RpWidget(parent)
  149. , _forum(isForum)
  150. , _painter(size.width())
  151. , _duration(st::slideWrapDuration) {
  152. resize(size);
  153. }
  154. void EmojiUserpic::setDocument(not_null<DocumentData*> document) {
  155. if (!_playOnce.has_value()) {
  156. const auto &c = document->owner().session().appConfig();
  157. _playOnce = !c.get<bool>(u"upload_markup_video"_q, false);
  158. }
  159. _painter.setDocument(document, [=] { update(); });
  160. _painter.setPlayOnce(*_playOnce);
  161. }
  162. void EmojiUserpic::result(int size, Fn<void(UserpicBuilder::Result)> done) {
  163. const auto painter = lifetime().make_state<PreviewPainter>(size);
  164. // Reset to the first frame.
  165. const auto document = _painter.document();
  166. const auto callback = [=] {
  167. auto background = GenerateGradient(Size(size), _colors, false);
  168. {
  169. constexpr auto kAttemptsToDrawFirstFrame = 3000;
  170. auto attempts = 0;
  171. auto p = QPainter(&background);
  172. while (attempts < kAttemptsToDrawFirstFrame) {
  173. if (painter->paintForeground(p)) {
  174. break;
  175. }
  176. attempts++;
  177. }
  178. }
  179. if (*_playOnce && document) {
  180. done({ std::move(background), document->id, _colors });
  181. } else {
  182. done({ std::move(background) });
  183. }
  184. };
  185. if (document) {
  186. painter->setDocument(document, callback);
  187. } else {
  188. callback();
  189. }
  190. }
  191. void EmojiUserpic::setGradientColors(std::vector<QColor> colors) {
  192. if (_colors == colors) {
  193. return;
  194. }
  195. if (const auto colors = base::take(_colors); !colors.empty()) {
  196. _previousImage = GenerateGradient(size(), colors, !_forum, _forum);
  197. }
  198. _colors = std::move(colors);
  199. {
  200. _image = GenerateGradient(size(), _colors, !_forum, _forum);
  201. }
  202. if (_duration) {
  203. _animation.stop();
  204. _animation.start([=] { update(); }, 0., 1., _duration);
  205. } else {
  206. update();
  207. }
  208. }
  209. void EmojiUserpic::paintEvent(QPaintEvent *event) {
  210. auto p = QPainter(this);
  211. if (_animation.animating() && !_previousImage.isNull()) {
  212. _painter.paintBackground(p, _previousImage);
  213. p.setOpacity(_animation.value(1.));
  214. }
  215. _painter.paintBackground(p, _image);
  216. p.setOpacity(1.);
  217. _painter.paintForeground(p);
  218. }
  219. void EmojiUserpic::setDuration(crl::time duration) {
  220. _duration = duration;
  221. }
  222. } // namespace UserpicBuilder