window_media_preview.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  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 "window/window_media_preview.h"
  8. #include "chat_helpers/stickers_lottie.h"
  9. #include "chat_helpers/stickers_emoji_pack.h"
  10. #include "data/data_photo.h"
  11. #include "data/data_photo_media.h"
  12. #include "data/data_document.h"
  13. #include "data/data_document_media.h"
  14. #include "data/data_session.h"
  15. #include "data/stickers/data_stickers.h"
  16. #include "history/view/media/history_view_sticker.h"
  17. #include "ui/image/image.h"
  18. #include "ui/emoji_config.h"
  19. #include "ui/ui_utility.h"
  20. #include "lottie/lottie_single_player.h"
  21. #include "main/main_session.h"
  22. #include "window/window_session_controller.h"
  23. #include "styles/style_layers.h"
  24. #include "styles/style_chat_helpers.h"
  25. #include "styles/style_chat.h"
  26. namespace Window {
  27. namespace {
  28. constexpr auto kStickerPreviewEmojiLimit = 10;
  29. constexpr auto kPremiumShift = 21. / 240;
  30. constexpr auto kPremiumMultiplier = (1 + 0.245 * 2);
  31. constexpr auto kPremiumDownscale = 1.25;
  32. } // namespace
  33. MediaPreviewWidget::MediaPreviewWidget(
  34. QWidget *parent,
  35. not_null<Window::SessionController*> controller)
  36. : RpWidget(parent)
  37. , _controller(controller)
  38. , _emojiSize(Ui::Emoji::GetSizeLarge() / style::DevicePixelRatio()) {
  39. setAttribute(Qt::WA_TransparentForMouseEvents);
  40. _controller->session().downloaderTaskFinished(
  41. ) | rpl::start_with_next([=] {
  42. update();
  43. }, lifetime());
  44. style::PaletteChanged(
  45. ) | rpl::start_with_next([=] {
  46. if (_document && _document->emojiUsesTextColor()) {
  47. _cache = QPixmap();
  48. }
  49. }, lifetime());
  50. }
  51. QRect MediaPreviewWidget::updateArea() const {
  52. const auto size = currentDimensions();
  53. const auto position = QPoint(
  54. (width() - size.width()) / 2,
  55. (height() - size.height()) / 2);
  56. const auto premium = _document && _document->isPremiumSticker();
  57. const auto adjusted = position
  58. - (premium
  59. ? QPoint(size.width() - (size.width() / 2), size.height() / 2)
  60. : QPoint());
  61. return QRect(adjusted, size * (premium ? 2 : 1));
  62. }
  63. void MediaPreviewWidget::paintEvent(QPaintEvent *e) {
  64. auto p = QPainter(this);
  65. const auto r = e->rect();
  66. const auto factor = style::DevicePixelRatio();
  67. const auto dimensions = currentDimensions();
  68. const auto frame = (_lottie && _lottie->ready())
  69. ? _lottie->frameInfo({
  70. .box = dimensions * factor,
  71. .colored = ((_document && _document->emojiUsesTextColor())
  72. ? st::windowFg->c
  73. : QColor(0, 0, 0, 0)),
  74. })
  75. : Lottie::Animation::FrameInfo();
  76. const auto effect = (_effect && _effect->ready())
  77. ? _effect->frameInfo({ dimensions * kPremiumMultiplier * factor })
  78. : Lottie::Animation::FrameInfo();
  79. const auto image = frame.image;
  80. const auto effectImage = effect.image;
  81. //const auto framesCount = !image.isNull() ? _lottie->framesCount() : 1;
  82. //const auto effectsCount = !effectImage.isNull()
  83. // ? _effect->framesCount()
  84. // : 1;
  85. const auto pixmap = image.isNull() ? currentImage() : QPixmap();
  86. const auto size = image.isNull() ? pixmap.size() : image.size();
  87. int w = size.width() / factor, h = size.height() / factor;
  88. auto shown = _a_shown.value(_hiding ? 0. : 1.);
  89. if (!_a_shown.animating()) {
  90. if (_hiding) {
  91. hide();
  92. _controller->disableGifPauseReason(Window::GifPauseReason::MediaPreview);
  93. return;
  94. }
  95. } else {
  96. p.setOpacity(shown);
  97. // w = qMax(qRound(w * (st::stickerPreviewMin + ((1. - st::stickerPreviewMin) * shown)) / 2.) * 2 + int(w % 2), 1);
  98. // h = qMax(qRound(h * (st::stickerPreviewMin + ((1. - st::stickerPreviewMin) * shown)) / 2.) * 2 + int(h % 2), 1);
  99. }
  100. p.fillRect(r, st::stickerPreviewBg);
  101. const auto position = innerPosition({ w, h });
  102. if (image.isNull()) {
  103. p.drawPixmap(position, pixmap);
  104. } else {
  105. p.drawImage(QRect(position, QSize(w, h)), image);
  106. }
  107. if (!effectImage.isNull()) {
  108. p.drawImage(
  109. QRect(outerPosition({ w, h }), effectImage.size() / factor),
  110. effectImage);
  111. }
  112. if (!_emojiList.empty()) {
  113. const auto emojiCount = _emojiList.size();
  114. const auto emojiWidth = (emojiCount * _emojiSize) + (emojiCount - 1) * st::stickerEmojiSkip;
  115. auto emojiLeft = (width() - emojiWidth) / 2;
  116. const auto esize = Ui::Emoji::GetSizeLarge();
  117. for (const auto emoji : _emojiList) {
  118. Ui::Emoji::Draw(
  119. p,
  120. emoji,
  121. esize,
  122. emojiLeft,
  123. (height() - h) / 2 - (_emojiSize * 2));
  124. emojiLeft += _emojiSize + st::stickerEmojiSkip;
  125. }
  126. }
  127. if (!frame.image.isNull()/*
  128. && (!_effect || ((frame.index % effectsCount) <= effect.index))*/) {
  129. _lottie->markFrameShown();
  130. }
  131. if (!effect.image.isNull()/*
  132. && ((effect.index % framesCount) <= frame.index)*/) {
  133. _effect->markFrameShown();
  134. }
  135. }
  136. void MediaPreviewWidget::resizeEvent(QResizeEvent *e) {
  137. update();
  138. }
  139. QPoint MediaPreviewWidget::innerPosition(QSize size) const {
  140. if (!_document || !_document->isPremiumSticker()) {
  141. return QPoint(
  142. (width() - size.width()) / 2,
  143. (height() - size.height()) / 2);
  144. }
  145. const auto outer = size * kPremiumMultiplier;
  146. const auto shift = size.width() * kPremiumShift;
  147. return outerPosition(size)
  148. + QPoint(
  149. outer.width() - size.width() - shift,
  150. (outer.height() - size.height()) / 2);
  151. }
  152. QPoint MediaPreviewWidget::outerPosition(QSize size) const {
  153. const auto outer = size * kPremiumMultiplier;
  154. return QPoint(
  155. (width() - outer.width()) / 2,
  156. (height() - outer.height()) / 2);
  157. }
  158. void MediaPreviewWidget::showPreview(
  159. Data::FileOrigin origin,
  160. not_null<DocumentData*> document) {
  161. if (!document
  162. || (!document->isAnimation() && !document->sticker())
  163. || document->isVideoMessage()) {
  164. hidePreview();
  165. return;
  166. }
  167. startShow();
  168. _origin = origin;
  169. _photo = nullptr;
  170. _photoMedia = nullptr;
  171. _document = document;
  172. _documentMedia = _document->createMediaView();
  173. _documentMedia->thumbnailWanted(_origin);
  174. _documentMedia->videoThumbnailWanted(_origin);
  175. _documentMedia->automaticLoad(_origin, nullptr);
  176. fillEmojiString();
  177. resetGifAndCache();
  178. }
  179. void MediaPreviewWidget::showPreview(
  180. Data::FileOrigin origin,
  181. not_null<PhotoData*> photo) {
  182. startShow();
  183. _origin = origin;
  184. _document = nullptr;
  185. _documentMedia = nullptr;
  186. _photo = photo;
  187. _photoMedia = _photo->createMediaView();
  188. fillEmojiString();
  189. resetGifAndCache();
  190. }
  191. void MediaPreviewWidget::startShow() {
  192. _cache = QPixmap();
  193. if (isHidden() || _a_shown.animating()) {
  194. if (isHidden()) {
  195. show();
  196. _controller->enableGifPauseReason(Window::GifPauseReason::MediaPreview);
  197. }
  198. _hiding = false;
  199. _a_shown.start([=] { update(); }, 0., 1., st::stickerPreviewDuration);
  200. } else {
  201. update();
  202. }
  203. }
  204. void MediaPreviewWidget::hidePreview() {
  205. if (isHidden()) {
  206. return;
  207. }
  208. if (_gif || _gifThumbnail) {
  209. _cache = currentImage();
  210. }
  211. _hiding = true;
  212. _a_shown.start([=] { update(); }, 1., 0., st::stickerPreviewDuration);
  213. _photo = nullptr;
  214. _photoMedia = nullptr;
  215. _document = nullptr;
  216. _documentMedia = nullptr;
  217. resetGifAndCache();
  218. }
  219. void MediaPreviewWidget::fillEmojiString() {
  220. _emojiList.clear();
  221. if (_photo) {
  222. return;
  223. }
  224. if (auto sticker = _document->sticker()) {
  225. if (auto list = _document->owner().stickers().getEmojiListFromSet(_document)) {
  226. _emojiList = std::move(*list);
  227. while (_emojiList.size() > kStickerPreviewEmojiLimit) {
  228. _emojiList.pop_back();
  229. }
  230. } else if (const auto emoji = Ui::Emoji::Find(sticker->alt)) {
  231. _emojiList.emplace_back(emoji);
  232. }
  233. }
  234. }
  235. void MediaPreviewWidget::resetGifAndCache() {
  236. _lottie = nullptr;
  237. _effect = nullptr;
  238. _gif.reset();
  239. _gifThumbnail.reset();
  240. _gifLastPosition = 0;
  241. _cacheStatus = CacheNotLoaded;
  242. _cachedSize = QSize();
  243. }
  244. QSize MediaPreviewWidget::currentDimensions() const {
  245. if (!_cachedSize.isEmpty()) {
  246. return _cachedSize;
  247. }
  248. if (!_document && !_photo) {
  249. _cachedSize = _cache.size() * style::DevicePixelRatio();
  250. return _cachedSize;
  251. }
  252. QSize result, box;
  253. if (_photo) {
  254. result = QSize(_photo->width(), _photo->height());
  255. const auto skip = st::defaultBox.margin.top();
  256. box = QSize(width() - 2 * skip, height() - 2 * skip);
  257. } else {
  258. result = _document->dimensions;
  259. if (result.isEmpty()) {
  260. const auto &gif = (_gif && _gif->ready()) ? _gif : _gifThumbnail;
  261. if (gif && gif->ready()) {
  262. result = QSize(gif->width(), gif->height());
  263. }
  264. }
  265. if (_document->sticker()) {
  266. box = QSize(st::maxStickerSize, st::maxStickerSize);
  267. if (_document->isPremiumSticker()) {
  268. result = (box /= kPremiumDownscale);
  269. }
  270. } else {
  271. box = QSize(2 * st::maxStickerSize, 2 * st::maxStickerSize);
  272. }
  273. }
  274. result = QSize(qMax(style::ConvertScale(result.width()), 1), qMax(style::ConvertScale(result.height()), 1));
  275. if (result.width() > box.width()) {
  276. result.setHeight(qMax((box.width() * result.height()) / result.width(), 1));
  277. result.setWidth(box.width());
  278. }
  279. if (result.height() > box.height()) {
  280. result.setWidth(qMax((box.height() * result.width()) / result.height(), 1));
  281. result.setHeight(box.height());
  282. }
  283. if (_photo) {
  284. _cachedSize = result;
  285. }
  286. return result;
  287. }
  288. void MediaPreviewWidget::createLottieIfReady(
  289. not_null<DocumentData*> document) {
  290. const auto sticker = document->sticker();
  291. if (!sticker
  292. || !sticker->isLottie()
  293. || _lottie
  294. || !_documentMedia->loaded()) {
  295. return;
  296. } else if (document->isPremiumSticker()
  297. && _documentMedia->videoThumbnailContent().isEmpty()) {
  298. return;
  299. }
  300. const_cast<MediaPreviewWidget*>(this)->setupLottie();
  301. }
  302. void MediaPreviewWidget::setupLottie() {
  303. Expects(_document != nullptr);
  304. const auto factor = style::DevicePixelRatio();
  305. if (_document->isPremiumSticker()) {
  306. const auto size = HistoryView::Sticker::Size(_document);
  307. _cachedSize = size;
  308. _lottie = ChatHelpers::LottiePlayerFromDocument(
  309. _documentMedia.get(),
  310. nullptr,
  311. ChatHelpers::StickerLottieSize::MessageHistory,
  312. size * factor,
  313. Lottie::Quality::High);
  314. _effect = _document->session().emojiStickersPack().effectPlayer(
  315. _document,
  316. _documentMedia->videoThumbnailContent(),
  317. QString(),
  318. Stickers::EffectType::PremiumSticker);
  319. } else {
  320. const auto size = currentDimensions();
  321. _lottie = std::make_unique<Lottie::SinglePlayer>(
  322. Lottie::ReadContent(_documentMedia->bytes(), _document->filepath()),
  323. Lottie::FrameRequest{ size * factor },
  324. Lottie::Quality::High);
  325. }
  326. const auto handler = [=](Lottie::Update update) {
  327. v::match(update.data, [&](const Lottie::Information &) {
  328. this->update();
  329. }, [&](const Lottie::DisplayFrameRequest &) {
  330. this->update(updateArea());
  331. });
  332. };
  333. _lottie->updates() | rpl::start_with_next(handler, lifetime());
  334. if (_effect) {
  335. _effect->updates() | rpl::start_with_next(handler, lifetime());
  336. }
  337. }
  338. QPixmap MediaPreviewWidget::currentImage() const {
  339. const auto blur = Images::PrepareArgs{ .options = Images::Option::Blur };
  340. if (_document) {
  341. const auto sticker = _document->sticker();
  342. const auto webm = sticker && sticker->isWebm();
  343. if (sticker && !webm) {
  344. if (_cacheStatus != CacheLoaded) {
  345. const_cast<MediaPreviewWidget*>(this)->createLottieIfReady(
  346. _document);
  347. if (_lottie && _lottie->ready()) {
  348. return QPixmap();
  349. } else if (const auto image = _documentMedia->getStickerLarge()) {
  350. QSize s = currentDimensions();
  351. _cache = image->pix(s);
  352. _cacheStatus = CacheLoaded;
  353. } else if (_cacheStatus != CacheThumbLoaded
  354. && _document->hasThumbnail()
  355. && _documentMedia->thumbnail()) {
  356. QSize s = currentDimensions();
  357. _cache = _documentMedia->thumbnail()->pix(s, blur);
  358. if (_document && _document->emojiUsesTextColor()) {
  359. _cache = Ui::PixmapFromImage(
  360. Images::Colored(
  361. _cache.toImage(),
  362. st::windowFg->c));
  363. }
  364. _cacheStatus = CacheThumbLoaded;
  365. }
  366. }
  367. } else {
  368. const_cast<MediaPreviewWidget*>(this)->validateGifAnimation();
  369. const auto &gif = (_gif && _gif->started())
  370. ? _gif
  371. : _gifThumbnail;
  372. if (gif && gif->started()) {
  373. const auto paused = _controller->isGifPausedAtLeastFor(
  374. Window::GifPauseReason::MediaPreview);
  375. return QPixmap::fromImage(gif->current(
  376. { .frame = currentDimensions(), .keepAlpha = webm },
  377. paused ? 0 : crl::now()), Qt::ColorOnly);
  378. }
  379. if (_cacheStatus != CacheThumbLoaded
  380. && _document->hasThumbnail()) {
  381. QSize s = currentDimensions();
  382. const auto thumbnail = _documentMedia->thumbnail();
  383. if (thumbnail) {
  384. _cache = thumbnail->pix(s, blur);
  385. _cacheStatus = CacheThumbLoaded;
  386. } else if (const auto blurred = _documentMedia->thumbnailInline()) {
  387. _cache = blurred->pix(s, blur);
  388. _cacheStatus = CacheThumbLoaded;
  389. }
  390. }
  391. }
  392. } else if (_photo) {
  393. if (_cacheStatus != CacheLoaded) {
  394. if (_photoMedia->loaded()) {
  395. QSize s = currentDimensions();
  396. _cache = _photoMedia->image(Data::PhotoSize::Large)->pix(s);
  397. _cacheStatus = CacheLoaded;
  398. } else {
  399. _photo->load(_origin);
  400. if (_cacheStatus != CacheThumbLoaded) {
  401. QSize s = currentDimensions();
  402. if (const auto thumbnail = _photoMedia->image(
  403. Data::PhotoSize::Thumbnail)) {
  404. _cache = thumbnail->pix(s, blur);
  405. _cacheStatus = CacheThumbLoaded;
  406. } else if (const auto small = _photoMedia->image(
  407. Data::PhotoSize::Small)) {
  408. _cache = small->pix(s, blur);
  409. _cacheStatus = CacheThumbLoaded;
  410. } else if (const auto blurred = _photoMedia->thumbnailInline()) {
  411. _cache = blurred->pix(s, blur);
  412. _cacheStatus = CacheThumbLoaded;
  413. } else {
  414. _photoMedia->wanted(Data::PhotoSize::Small, _origin);
  415. }
  416. }
  417. }
  418. }
  419. }
  420. return _cache;
  421. }
  422. void MediaPreviewWidget::startGifAnimation(
  423. const Media::Clip::ReaderPointer &gif) {
  424. gif->start({ .frame = currentDimensions(), .keepAlpha = _gifWithAlpha });
  425. }
  426. void MediaPreviewWidget::validateGifAnimation() {
  427. Expects(_documentMedia != nullptr);
  428. if (_gifThumbnail && _gifThumbnail->started()) {
  429. const auto position = _gifThumbnail->getPositionMs();
  430. if (_gif
  431. && _gif->ready()
  432. && !_gif->started()
  433. && (_gifLastPosition > position)) {
  434. startGifAnimation(_gif);
  435. _gifThumbnail.reset();
  436. _gifLastPosition = 0;
  437. return;
  438. } else {
  439. _gifLastPosition = position;
  440. }
  441. } else if (_gif || _gif.isBad()) {
  442. return;
  443. }
  444. const auto contentLoaded = _documentMedia->loaded();
  445. const auto thumbContent = _documentMedia->videoThumbnailContent();
  446. const auto thumbLoaded = !thumbContent.isEmpty();
  447. if (!contentLoaded
  448. && (_gifThumbnail || _gifThumbnail.isBad() | !thumbLoaded)) {
  449. return;
  450. }
  451. const auto callback = [=](Media::Clip::Notification notification) {
  452. clipCallback(notification);
  453. };
  454. _gifWithAlpha = (_documentMedia->owner()->sticker() != nullptr);
  455. if (contentLoaded) {
  456. _gif = Media::Clip::MakeReader(
  457. _documentMedia->owner()->location(),
  458. _documentMedia->bytes(),
  459. std::move(callback));
  460. } else {
  461. _gifThumbnail = Media::Clip::MakeReader(
  462. thumbContent,
  463. std::move(callback));
  464. }
  465. }
  466. void MediaPreviewWidget::clipCallback(
  467. Media::Clip::Notification notification) {
  468. using namespace Media::Clip;
  469. switch (notification) {
  470. case Notification::Reinit: {
  471. if (_gifThumbnail && _gifThumbnail->state() == State::Error) {
  472. _gifThumbnail.setBad();
  473. }
  474. if (_gif && _gif->state() == State::Error) {
  475. _gif.setBad();
  476. }
  477. if (_gif
  478. && _gif->ready()
  479. && !_gif->started()
  480. && (!_gifThumbnail || !_gifThumbnail->started())) {
  481. startGifAnimation(_gif);
  482. } else if (!_gif
  483. && _gifThumbnail
  484. && _gifThumbnail->ready()
  485. && !_gifThumbnail->started()) {
  486. startGifAnimation(_gifThumbnail);
  487. }
  488. update();
  489. } break;
  490. case Notification::Repaint: {
  491. if ((_gif && _gif->started() && !_gif->currentDisplayed())
  492. || (_gifThumbnail
  493. && _gifThumbnail->started()
  494. && !_gifThumbnail->currentDisplayed())) {
  495. update(updateArea());
  496. }
  497. } break;
  498. }
  499. }
  500. MediaPreviewWidget::~MediaPreviewWidget() {
  501. }
  502. } // namespace Window