dynamic_thumbnails.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  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 "ui/dynamic_thumbnails.h"
  8. #include "data/data_changes.h"
  9. #include "data/data_document.h"
  10. #include "data/data_document_media.h"
  11. #include "data/data_file_origin.h"
  12. #include "data/data_peer.h"
  13. #include "data/data_photo.h"
  14. #include "data/data_photo_media.h"
  15. #include "data/data_session.h"
  16. #include "data/stickers/data_custom_emoji.h"
  17. #include "data/data_story.h"
  18. #include "main/main_session.h"
  19. #include "ui/empty_userpic.h"
  20. #include "ui/dynamic_image.h"
  21. #include "ui/painter.h"
  22. #include "ui/userpic_view.h"
  23. namespace Ui {
  24. namespace {
  25. class PeerUserpic final : public DynamicImage {
  26. public:
  27. PeerUserpic(not_null<PeerData*> peer, bool forceRound);
  28. std::shared_ptr<DynamicImage> clone() override;
  29. QImage image(int size) override;
  30. void subscribeToUpdates(Fn<void()> callback) override;
  31. private:
  32. struct Subscribed {
  33. explicit Subscribed(Fn<void()> callback)
  34. : callback(std::move(callback)) {
  35. }
  36. Ui::PeerUserpicView view;
  37. Fn<void()> callback;
  38. InMemoryKey key;
  39. int paletteVersion = 0;
  40. rpl::lifetime photoLifetime;
  41. rpl::lifetime downloadLifetime;
  42. };
  43. [[nodiscard]] bool waitingUserpicLoad() const;
  44. void processNewPhoto();
  45. const not_null<PeerData*> _peer;
  46. QImage _frame;
  47. std::unique_ptr<Subscribed> _subscribed;
  48. bool _forceRound = false;
  49. };
  50. class MediaThumbnail : public DynamicImage {
  51. public:
  52. explicit MediaThumbnail(Data::FileOrigin origin, bool forceRound);
  53. QImage image(int size) override;
  54. void subscribeToUpdates(Fn<void()> callback) override;
  55. protected:
  56. struct Thumb {
  57. Image *image = nullptr;
  58. bool blurred = false;
  59. };
  60. [[nodiscard]] Data::FileOrigin origin() const;
  61. [[nodiscard]] bool forceRound() const;
  62. [[nodiscard]] virtual Main::Session &session() = 0;
  63. [[nodiscard]] virtual Thumb loaded(Data::FileOrigin origin) = 0;
  64. virtual void clear() = 0;
  65. private:
  66. const Data::FileOrigin _origin;
  67. const bool _forceRound;
  68. QImage _full;
  69. rpl::lifetime _subscription;
  70. QImage _prepared;
  71. bool _blurred = false;
  72. };
  73. class PhotoThumbnail final : public MediaThumbnail {
  74. public:
  75. PhotoThumbnail(
  76. not_null<PhotoData*> photo,
  77. Data::FileOrigin origin,
  78. bool forceRound);
  79. std::shared_ptr<DynamicImage> clone() override;
  80. private:
  81. Main::Session &session() override;
  82. Thumb loaded(Data::FileOrigin origin) override;
  83. void clear() override;
  84. const not_null<PhotoData*> _photo;
  85. std::shared_ptr<Data::PhotoMedia> _media;
  86. };
  87. class VideoThumbnail final : public MediaThumbnail {
  88. public:
  89. VideoThumbnail(
  90. not_null<DocumentData*> video,
  91. Data::FileOrigin origin,
  92. bool forceRound);
  93. std::shared_ptr<DynamicImage> clone() override;
  94. private:
  95. Main::Session &session() override;
  96. Thumb loaded(Data::FileOrigin origin) override;
  97. void clear() override;
  98. const not_null<DocumentData*> _video;
  99. std::shared_ptr<Data::DocumentMedia> _media;
  100. };
  101. class EmptyThumbnail final : public DynamicImage {
  102. public:
  103. std::shared_ptr<DynamicImage> clone() override;
  104. QImage image(int size) override;
  105. void subscribeToUpdates(Fn<void()> callback) override;
  106. private:
  107. QImage _cached;
  108. };
  109. class SavedMessagesUserpic final : public DynamicImage {
  110. public:
  111. std::shared_ptr<DynamicImage> clone() override;
  112. QImage image(int size) override;
  113. void subscribeToUpdates(Fn<void()> callback) override;
  114. private:
  115. QImage _frame;
  116. int _paletteVersion = 0;
  117. };
  118. class RepliesUserpic final : public DynamicImage {
  119. public:
  120. std::shared_ptr<DynamicImage> clone() override;
  121. QImage image(int size) override;
  122. void subscribeToUpdates(Fn<void()> callback) override;
  123. private:
  124. QImage _frame;
  125. int _paletteVersion = 0;
  126. };
  127. class HiddenAuthorUserpic final : public DynamicImage {
  128. public:
  129. std::shared_ptr<DynamicImage> clone() override;
  130. QImage image(int size) override;
  131. void subscribeToUpdates(Fn<void()> callback) override;
  132. private:
  133. QImage _frame;
  134. int _paletteVersion = 0;
  135. };
  136. class IconThumbnail final : public DynamicImage {
  137. public:
  138. explicit IconThumbnail(const style::icon &icon);
  139. std::shared_ptr<DynamicImage> clone() override;
  140. QImage image(int size) override;
  141. void subscribeToUpdates(Fn<void()> callback) override;
  142. private:
  143. const style::icon &_icon;
  144. int _paletteVersion = 0;
  145. QImage _frame;
  146. };
  147. class EmojiThumbnail final : public DynamicImage {
  148. public:
  149. EmojiThumbnail(not_null<Data::Session*> owner, const QString &data);
  150. std::shared_ptr<DynamicImage> clone() override;
  151. QImage image(int size) override;
  152. void subscribeToUpdates(Fn<void()> callback) override;
  153. private:
  154. const not_null<Data::Session*> _owner;
  155. const QString _data;
  156. std::unique_ptr<Ui::Text::CustomEmoji> _emoji;
  157. QImage _frame;
  158. };
  159. PeerUserpic::PeerUserpic(not_null<PeerData*> peer, bool forceRound)
  160. : _peer(peer)
  161. , _forceRound(forceRound) {
  162. }
  163. std::shared_ptr<DynamicImage> PeerUserpic::clone() {
  164. return std::make_shared<PeerUserpic>(_peer, _forceRound);
  165. }
  166. QImage PeerUserpic::image(int size) {
  167. Expects(_subscribed != nullptr);
  168. const auto good = (_frame.width() == size * _frame.devicePixelRatio());
  169. const auto key = _peer->userpicUniqueKey(_subscribed->view);
  170. const auto paletteVersion = style::PaletteVersion();
  171. if (!good
  172. || (_subscribed->paletteVersion != paletteVersion
  173. && _peer->useEmptyUserpic(_subscribed->view))
  174. || (_subscribed->key != key && !waitingUserpicLoad())) {
  175. _subscribed->key = key;
  176. _subscribed->paletteVersion = paletteVersion;
  177. const auto ratio = style::DevicePixelRatio();
  178. if (!good) {
  179. _frame = QImage(
  180. QSize(size, size) * ratio,
  181. QImage::Format_ARGB32_Premultiplied);
  182. _frame.setDevicePixelRatio(ratio);
  183. }
  184. _frame.fill(Qt::transparent);
  185. auto p = Painter(&_frame);
  186. auto &view = _subscribed->view;
  187. if (!_forceRound) {
  188. _peer->paintUserpic(p, view, 0, 0, size);
  189. } else if (const auto cloud = _peer->userpicCloudImage(view)) {
  190. const auto full = size * style::DevicePixelRatio();
  191. Ui::ValidateUserpicCache(view, cloud, nullptr, full, false);
  192. p.drawImage(QRect(0, 0, size, size), view.cached);
  193. } else {
  194. const auto full = size * style::DevicePixelRatio();
  195. const auto r = full / 2.;
  196. const auto empty = PeerData::GenerateUserpicImage(
  197. _peer,
  198. view,
  199. full,
  200. r);
  201. p.drawImage(QRect(0, 0, size, size), empty);
  202. }
  203. }
  204. return _frame;
  205. }
  206. bool PeerUserpic::waitingUserpicLoad() const {
  207. return _peer->hasUserpic() && _peer->useEmptyUserpic(_subscribed->view);
  208. }
  209. void PeerUserpic::subscribeToUpdates(Fn<void()> callback) {
  210. if (!callback) {
  211. _subscribed = nullptr;
  212. return;
  213. }
  214. const auto old = std::exchange(
  215. _subscribed,
  216. std::make_unique<Subscribed>(std::move(callback)));
  217. _peer->session().changes().peerUpdates(
  218. _peer,
  219. Data::PeerUpdate::Flag::Photo
  220. ) | rpl::start_with_next([=] {
  221. _subscribed->callback();
  222. processNewPhoto();
  223. }, _subscribed->photoLifetime);
  224. processNewPhoto();
  225. }
  226. void PeerUserpic::processNewPhoto() {
  227. Expects(_subscribed != nullptr);
  228. if (!waitingUserpicLoad()) {
  229. _subscribed->downloadLifetime.destroy();
  230. return;
  231. }
  232. _peer->session().downloaderTaskFinished(
  233. ) | rpl::filter([=] {
  234. return !waitingUserpicLoad();
  235. }) | rpl::start_with_next([=] {
  236. _subscribed->callback();
  237. _subscribed->downloadLifetime.destroy();
  238. }, _subscribed->downloadLifetime);
  239. }
  240. MediaThumbnail::MediaThumbnail(Data::FileOrigin origin, bool forceRound)
  241. : _origin(origin)
  242. , _forceRound(forceRound) {
  243. }
  244. QImage MediaThumbnail::image(int size) {
  245. const auto ratio = style::DevicePixelRatio();
  246. if (_prepared.width() != size * ratio) {
  247. if (_full.isNull()) {
  248. _prepared = QImage(
  249. QSize(size, size) * ratio,
  250. QImage::Format_ARGB32_Premultiplied);
  251. _prepared.fill(Qt::black);
  252. } else {
  253. const auto width = _full.width();
  254. const auto skip = std::max((_full.height() - width) / 2, 0);
  255. _prepared = _full.copy(0, skip, width, width).scaled(
  256. QSize(size, size) * ratio,
  257. Qt::IgnoreAspectRatio,
  258. Qt::SmoothTransformation);
  259. }
  260. if (_forceRound) {
  261. _prepared = Images::Circle(std::move(_prepared));
  262. }
  263. _prepared.setDevicePixelRatio(ratio);
  264. }
  265. return _prepared;
  266. }
  267. void MediaThumbnail::subscribeToUpdates(Fn<void()> callback) {
  268. _subscription.destroy();
  269. if (!callback) {
  270. clear();
  271. return;
  272. } else if (!_full.isNull() && !_blurred) {
  273. return;
  274. }
  275. const auto thumbnail = loaded(_origin);
  276. if (const auto image = thumbnail.image) {
  277. _full = image->original();
  278. }
  279. _blurred = thumbnail.blurred;
  280. if (!_blurred) {
  281. _prepared = QImage();
  282. } else {
  283. _subscription = session().downloaderTaskFinished(
  284. ) | rpl::filter([=] {
  285. const auto thumbnail = loaded(_origin);
  286. if (!thumbnail.blurred) {
  287. _full = thumbnail.image->original();
  288. _prepared = QImage();
  289. _blurred = false;
  290. return true;
  291. }
  292. return false;
  293. }) | rpl::take(1) | rpl::start_with_next(callback);
  294. }
  295. }
  296. Data::FileOrigin MediaThumbnail::origin() const {
  297. return _origin;
  298. }
  299. bool MediaThumbnail::forceRound() const {
  300. return _forceRound;
  301. }
  302. PhotoThumbnail::PhotoThumbnail(
  303. not_null<PhotoData*> photo,
  304. Data::FileOrigin origin,
  305. bool forceRound)
  306. : MediaThumbnail(origin, forceRound)
  307. , _photo(photo) {
  308. }
  309. std::shared_ptr<DynamicImage> PhotoThumbnail::clone() {
  310. return std::make_shared<PhotoThumbnail>(_photo, origin(), forceRound());
  311. }
  312. Main::Session &PhotoThumbnail::session() {
  313. return _photo->session();
  314. }
  315. MediaThumbnail::Thumb PhotoThumbnail::loaded(Data::FileOrigin origin) {
  316. if (!_media) {
  317. _media = _photo->createMediaView();
  318. _media->wanted(Data::PhotoSize::Small, origin);
  319. }
  320. if (const auto small = _media->image(Data::PhotoSize::Small)) {
  321. return { .image = small };
  322. }
  323. return { .image = _media->thumbnailInline(), .blurred = true };
  324. }
  325. void PhotoThumbnail::clear() {
  326. _media = nullptr;
  327. }
  328. VideoThumbnail::VideoThumbnail(
  329. not_null<DocumentData*> video,
  330. Data::FileOrigin origin,
  331. bool forceRound)
  332. : MediaThumbnail(origin, forceRound)
  333. , _video(video) {
  334. }
  335. std::shared_ptr<DynamicImage> VideoThumbnail::clone() {
  336. return std::make_shared<VideoThumbnail>(_video, origin(), forceRound());
  337. }
  338. Main::Session &VideoThumbnail::session() {
  339. return _video->session();
  340. }
  341. MediaThumbnail::Thumb VideoThumbnail::loaded(Data::FileOrigin origin) {
  342. if (!_media) {
  343. _media = _video->createMediaView();
  344. _media->thumbnailWanted(origin);
  345. }
  346. if (const auto small = _media->thumbnail()) {
  347. return { .image = small };
  348. }
  349. return { .image = _media->thumbnailInline(), .blurred = true };
  350. }
  351. void VideoThumbnail::clear() {
  352. _media = nullptr;
  353. }
  354. std::shared_ptr<DynamicImage> EmptyThumbnail::clone() {
  355. return std::make_shared<EmptyThumbnail>();
  356. }
  357. QImage EmptyThumbnail::image(int size) {
  358. const auto ratio = style::DevicePixelRatio();
  359. if (_cached.width() != size * ratio) {
  360. _cached = QImage(
  361. QSize(size, size) * ratio,
  362. QImage::Format_ARGB32_Premultiplied);
  363. _cached.fill(Qt::black);
  364. _cached.setDevicePixelRatio(ratio);
  365. }
  366. return _cached;
  367. }
  368. void EmptyThumbnail::subscribeToUpdates(Fn<void()> callback) {
  369. }
  370. std::shared_ptr<DynamicImage> SavedMessagesUserpic::clone() {
  371. return std::make_shared<SavedMessagesUserpic>();
  372. }
  373. QImage SavedMessagesUserpic::image(int size) {
  374. const auto good = (_frame.width() == size * _frame.devicePixelRatio());
  375. const auto paletteVersion = style::PaletteVersion();
  376. if (!good || _paletteVersion != paletteVersion) {
  377. _paletteVersion = paletteVersion;
  378. const auto ratio = style::DevicePixelRatio();
  379. if (!good) {
  380. _frame = QImage(
  381. QSize(size, size) * ratio,
  382. QImage::Format_ARGB32_Premultiplied);
  383. _frame.setDevicePixelRatio(ratio);
  384. }
  385. _frame.fill(Qt::transparent);
  386. auto p = Painter(&_frame);
  387. Ui::EmptyUserpic::PaintSavedMessages(p, 0, 0, size, size);
  388. }
  389. return _frame;
  390. }
  391. void SavedMessagesUserpic::subscribeToUpdates(Fn<void()> callback) {
  392. if (!callback) {
  393. _frame = {};
  394. }
  395. }
  396. std::shared_ptr<DynamicImage> RepliesUserpic::clone() {
  397. return std::make_shared<RepliesUserpic>();
  398. }
  399. QImage RepliesUserpic::image(int size) {
  400. const auto good = (_frame.width() == size * _frame.devicePixelRatio());
  401. const auto paletteVersion = style::PaletteVersion();
  402. if (!good || _paletteVersion != paletteVersion) {
  403. _paletteVersion = paletteVersion;
  404. const auto ratio = style::DevicePixelRatio();
  405. if (!good) {
  406. _frame = QImage(
  407. QSize(size, size) * ratio,
  408. QImage::Format_ARGB32_Premultiplied);
  409. _frame.setDevicePixelRatio(ratio);
  410. }
  411. _frame.fill(Qt::transparent);
  412. auto p = Painter(&_frame);
  413. Ui::EmptyUserpic::PaintRepliesMessages(p, 0, 0, size, size);
  414. }
  415. return _frame;
  416. }
  417. void RepliesUserpic::subscribeToUpdates(Fn<void()> callback) {
  418. if (!callback) {
  419. _frame = {};
  420. }
  421. }
  422. std::shared_ptr<DynamicImage> HiddenAuthorUserpic::clone() {
  423. return std::make_shared<HiddenAuthorUserpic>();
  424. }
  425. QImage HiddenAuthorUserpic::image(int size) {
  426. const auto good = (_frame.width() == size * _frame.devicePixelRatio());
  427. const auto paletteVersion = style::PaletteVersion();
  428. if (!good || _paletteVersion != paletteVersion) {
  429. _paletteVersion = paletteVersion;
  430. const auto ratio = style::DevicePixelRatio();
  431. if (!good) {
  432. _frame = QImage(
  433. QSize(size, size) * ratio,
  434. QImage::Format_ARGB32_Premultiplied);
  435. _frame.setDevicePixelRatio(ratio);
  436. }
  437. _frame.fill(Qt::transparent);
  438. auto p = Painter(&_frame);
  439. Ui::EmptyUserpic::PaintHiddenAuthor(p, 0, 0, size, size);
  440. }
  441. return _frame;
  442. }
  443. void HiddenAuthorUserpic::subscribeToUpdates(Fn<void()> callback) {
  444. if (!callback) {
  445. _frame = {};
  446. }
  447. }
  448. IconThumbnail::IconThumbnail(const style::icon &icon) : _icon(icon) {
  449. }
  450. std::shared_ptr<DynamicImage> IconThumbnail::clone() {
  451. return std::make_shared<IconThumbnail>(_icon);
  452. }
  453. QImage IconThumbnail::image(int size) {
  454. const auto good = (_frame.width() == size * _frame.devicePixelRatio());
  455. const auto paletteVersion = style::PaletteVersion();
  456. if (!good || _paletteVersion != paletteVersion) {
  457. _paletteVersion = paletteVersion;
  458. const auto ratio = style::DevicePixelRatio();
  459. if (!good) {
  460. _frame = QImage(
  461. QSize(size, size) * ratio,
  462. QImage::Format_ARGB32_Premultiplied);
  463. _frame.setDevicePixelRatio(ratio);
  464. }
  465. _frame.fill(Qt::transparent);
  466. auto p = Painter(&_frame);
  467. _icon.paintInCenter(p, QRect(0, 0, size, size));
  468. }
  469. return _frame;
  470. }
  471. void IconThumbnail::subscribeToUpdates(Fn<void()> callback) {
  472. if (!callback) {
  473. _frame = {};
  474. }
  475. }
  476. EmojiThumbnail::EmojiThumbnail(
  477. not_null<Data::Session*> owner,
  478. const QString &data)
  479. : _owner(owner)
  480. , _data(data) {
  481. }
  482. void EmojiThumbnail::subscribeToUpdates(Fn<void()> callback) {
  483. if (!callback) {
  484. _emoji = nullptr;
  485. return;
  486. }
  487. _emoji = _owner->customEmojiManager().create(
  488. _data,
  489. std::move(callback),
  490. Data::CustomEmojiSizeTag::Large);
  491. }
  492. std::shared_ptr<DynamicImage> EmojiThumbnail::clone() {
  493. return std::make_shared<EmojiThumbnail>(_owner, _data);
  494. }
  495. QImage EmojiThumbnail::image(int size) {
  496. Expects(_emoji != nullptr);
  497. const auto ratio = style::DevicePixelRatio();
  498. const auto good = (_frame.width() == size * _frame.devicePixelRatio());
  499. if (!good) {
  500. _frame = QImage(
  501. QSize(size, size) * ratio,
  502. QImage::Format_ARGB32_Premultiplied);
  503. _frame.setDevicePixelRatio(ratio);
  504. }
  505. _frame.fill(Qt::transparent);
  506. auto p = Painter(&_frame);
  507. _emoji->paint(p, {
  508. .textColor = st::windowBoldFg->c,
  509. .now = crl::now(),
  510. .position = QPoint(0, 0),
  511. .paused = false,
  512. });
  513. p.end();
  514. return _frame;
  515. }
  516. } // namespace
  517. std::shared_ptr<DynamicImage> MakeUserpicThumbnail(
  518. not_null<PeerData*> peer,
  519. bool forceRound) {
  520. return std::make_shared<PeerUserpic>(peer, forceRound);
  521. }
  522. std::shared_ptr<DynamicImage> MakeSavedMessagesThumbnail() {
  523. return std::make_shared<SavedMessagesUserpic>();
  524. }
  525. std::shared_ptr<DynamicImage> MakeRepliesThumbnail() {
  526. return std::make_shared<RepliesUserpic>();
  527. }
  528. std::shared_ptr<DynamicImage> MakeHiddenAuthorThumbnail() {
  529. return std::make_shared<HiddenAuthorUserpic>();
  530. }
  531. std::shared_ptr<DynamicImage> MakeStoryThumbnail(
  532. not_null<Data::Story*> story) {
  533. using Result = std::shared_ptr<DynamicImage>;
  534. const auto id = story->fullId();
  535. return v::match(story->media().data, [](v::null_t) -> Result {
  536. return std::make_shared<EmptyThumbnail>();
  537. }, [&](not_null<PhotoData*> photo) -> Result {
  538. return std::make_shared<PhotoThumbnail>(photo, id, true);
  539. }, [&](not_null<DocumentData*> video) -> Result {
  540. return std::make_shared<VideoThumbnail>(video, id, true);
  541. });
  542. }
  543. std::shared_ptr<DynamicImage> MakeIconThumbnail(const style::icon &icon) {
  544. return std::make_shared<IconThumbnail>(icon);
  545. }
  546. std::shared_ptr<DynamicImage> MakeEmojiThumbnail(
  547. not_null<Data::Session*> owner,
  548. const QString &data) {
  549. return std::make_shared<EmojiThumbnail>(owner, data);
  550. }
  551. std::shared_ptr<DynamicImage> MakePhotoThumbnail(
  552. not_null<PhotoData*> photo,
  553. FullMsgId fullId) {
  554. return std::make_shared<PhotoThumbnail>(photo, fullId, false);
  555. }
  556. std::shared_ptr<DynamicImage> MakeDocumentThumbnail(
  557. not_null<DocumentData*> document,
  558. FullMsgId fullId) {
  559. return std::make_shared<VideoThumbnail>(document, fullId, false);
  560. }
  561. } // namespace Ui