| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684 |
- /*
- This file is part of Telegram Desktop,
- the official desktop application for the Telegram messaging service.
- For license and copyright information please follow this link:
- https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
- */
- #include "ui/dynamic_thumbnails.h"
- #include "data/data_changes.h"
- #include "data/data_document.h"
- #include "data/data_document_media.h"
- #include "data/data_file_origin.h"
- #include "data/data_peer.h"
- #include "data/data_photo.h"
- #include "data/data_photo_media.h"
- #include "data/data_session.h"
- #include "data/stickers/data_custom_emoji.h"
- #include "data/data_story.h"
- #include "main/main_session.h"
- #include "ui/empty_userpic.h"
- #include "ui/dynamic_image.h"
- #include "ui/painter.h"
- #include "ui/userpic_view.h"
- namespace Ui {
- namespace {
- class PeerUserpic final : public DynamicImage {
- public:
- PeerUserpic(not_null<PeerData*> peer, bool forceRound);
- std::shared_ptr<DynamicImage> clone() override;
- QImage image(int size) override;
- void subscribeToUpdates(Fn<void()> callback) override;
- private:
- struct Subscribed {
- explicit Subscribed(Fn<void()> callback)
- : callback(std::move(callback)) {
- }
- Ui::PeerUserpicView view;
- Fn<void()> callback;
- InMemoryKey key;
- int paletteVersion = 0;
- rpl::lifetime photoLifetime;
- rpl::lifetime downloadLifetime;
- };
- [[nodiscard]] bool waitingUserpicLoad() const;
- void processNewPhoto();
- const not_null<PeerData*> _peer;
- QImage _frame;
- std::unique_ptr<Subscribed> _subscribed;
- bool _forceRound = false;
- };
- class MediaThumbnail : public DynamicImage {
- public:
- explicit MediaThumbnail(Data::FileOrigin origin, bool forceRound);
- QImage image(int size) override;
- void subscribeToUpdates(Fn<void()> callback) override;
- protected:
- struct Thumb {
- Image *image = nullptr;
- bool blurred = false;
- };
- [[nodiscard]] Data::FileOrigin origin() const;
- [[nodiscard]] bool forceRound() const;
- [[nodiscard]] virtual Main::Session &session() = 0;
- [[nodiscard]] virtual Thumb loaded(Data::FileOrigin origin) = 0;
- virtual void clear() = 0;
- private:
- const Data::FileOrigin _origin;
- const bool _forceRound;
- QImage _full;
- rpl::lifetime _subscription;
- QImage _prepared;
- bool _blurred = false;
- };
- class PhotoThumbnail final : public MediaThumbnail {
- public:
- PhotoThumbnail(
- not_null<PhotoData*> photo,
- Data::FileOrigin origin,
- bool forceRound);
- std::shared_ptr<DynamicImage> clone() override;
- private:
- Main::Session &session() override;
- Thumb loaded(Data::FileOrigin origin) override;
- void clear() override;
- const not_null<PhotoData*> _photo;
- std::shared_ptr<Data::PhotoMedia> _media;
- };
- class VideoThumbnail final : public MediaThumbnail {
- public:
- VideoThumbnail(
- not_null<DocumentData*> video,
- Data::FileOrigin origin,
- bool forceRound);
- std::shared_ptr<DynamicImage> clone() override;
- private:
- Main::Session &session() override;
- Thumb loaded(Data::FileOrigin origin) override;
- void clear() override;
- const not_null<DocumentData*> _video;
- std::shared_ptr<Data::DocumentMedia> _media;
- };
- class EmptyThumbnail final : public DynamicImage {
- public:
- std::shared_ptr<DynamicImage> clone() override;
- QImage image(int size) override;
- void subscribeToUpdates(Fn<void()> callback) override;
- private:
- QImage _cached;
- };
- class SavedMessagesUserpic final : public DynamicImage {
- public:
- std::shared_ptr<DynamicImage> clone() override;
- QImage image(int size) override;
- void subscribeToUpdates(Fn<void()> callback) override;
- private:
- QImage _frame;
- int _paletteVersion = 0;
- };
- class RepliesUserpic final : public DynamicImage {
- public:
- std::shared_ptr<DynamicImage> clone() override;
- QImage image(int size) override;
- void subscribeToUpdates(Fn<void()> callback) override;
- private:
- QImage _frame;
- int _paletteVersion = 0;
- };
- class HiddenAuthorUserpic final : public DynamicImage {
- public:
- std::shared_ptr<DynamicImage> clone() override;
- QImage image(int size) override;
- void subscribeToUpdates(Fn<void()> callback) override;
- private:
- QImage _frame;
- int _paletteVersion = 0;
- };
- class IconThumbnail final : public DynamicImage {
- public:
- explicit IconThumbnail(const style::icon &icon);
- std::shared_ptr<DynamicImage> clone() override;
- QImage image(int size) override;
- void subscribeToUpdates(Fn<void()> callback) override;
- private:
- const style::icon &_icon;
- int _paletteVersion = 0;
- QImage _frame;
- };
- class EmojiThumbnail final : public DynamicImage {
- public:
- EmojiThumbnail(not_null<Data::Session*> owner, const QString &data);
- std::shared_ptr<DynamicImage> clone() override;
- QImage image(int size) override;
- void subscribeToUpdates(Fn<void()> callback) override;
- private:
- const not_null<Data::Session*> _owner;
- const QString _data;
- std::unique_ptr<Ui::Text::CustomEmoji> _emoji;
- QImage _frame;
- };
- PeerUserpic::PeerUserpic(not_null<PeerData*> peer, bool forceRound)
- : _peer(peer)
- , _forceRound(forceRound) {
- }
- std::shared_ptr<DynamicImage> PeerUserpic::clone() {
- return std::make_shared<PeerUserpic>(_peer, _forceRound);
- }
- QImage PeerUserpic::image(int size) {
- Expects(_subscribed != nullptr);
- const auto good = (_frame.width() == size * _frame.devicePixelRatio());
- const auto key = _peer->userpicUniqueKey(_subscribed->view);
- const auto paletteVersion = style::PaletteVersion();
- if (!good
- || (_subscribed->paletteVersion != paletteVersion
- && _peer->useEmptyUserpic(_subscribed->view))
- || (_subscribed->key != key && !waitingUserpicLoad())) {
- _subscribed->key = key;
- _subscribed->paletteVersion = paletteVersion;
- const auto ratio = style::DevicePixelRatio();
- if (!good) {
- _frame = QImage(
- QSize(size, size) * ratio,
- QImage::Format_ARGB32_Premultiplied);
- _frame.setDevicePixelRatio(ratio);
- }
- _frame.fill(Qt::transparent);
- auto p = Painter(&_frame);
- auto &view = _subscribed->view;
- if (!_forceRound) {
- _peer->paintUserpic(p, view, 0, 0, size);
- } else if (const auto cloud = _peer->userpicCloudImage(view)) {
- const auto full = size * style::DevicePixelRatio();
- Ui::ValidateUserpicCache(view, cloud, nullptr, full, false);
- p.drawImage(QRect(0, 0, size, size), view.cached);
- } else {
- const auto full = size * style::DevicePixelRatio();
- const auto r = full / 2.;
- const auto empty = PeerData::GenerateUserpicImage(
- _peer,
- view,
- full,
- r);
- p.drawImage(QRect(0, 0, size, size), empty);
- }
- }
- return _frame;
- }
- bool PeerUserpic::waitingUserpicLoad() const {
- return _peer->hasUserpic() && _peer->useEmptyUserpic(_subscribed->view);
- }
- void PeerUserpic::subscribeToUpdates(Fn<void()> callback) {
- if (!callback) {
- _subscribed = nullptr;
- return;
- }
- const auto old = std::exchange(
- _subscribed,
- std::make_unique<Subscribed>(std::move(callback)));
- _peer->session().changes().peerUpdates(
- _peer,
- Data::PeerUpdate::Flag::Photo
- ) | rpl::start_with_next([=] {
- _subscribed->callback();
- processNewPhoto();
- }, _subscribed->photoLifetime);
- processNewPhoto();
- }
- void PeerUserpic::processNewPhoto() {
- Expects(_subscribed != nullptr);
- if (!waitingUserpicLoad()) {
- _subscribed->downloadLifetime.destroy();
- return;
- }
- _peer->session().downloaderTaskFinished(
- ) | rpl::filter([=] {
- return !waitingUserpicLoad();
- }) | rpl::start_with_next([=] {
- _subscribed->callback();
- _subscribed->downloadLifetime.destroy();
- }, _subscribed->downloadLifetime);
- }
- MediaThumbnail::MediaThumbnail(Data::FileOrigin origin, bool forceRound)
- : _origin(origin)
- , _forceRound(forceRound) {
- }
- QImage MediaThumbnail::image(int size) {
- const auto ratio = style::DevicePixelRatio();
- if (_prepared.width() != size * ratio) {
- if (_full.isNull()) {
- _prepared = QImage(
- QSize(size, size) * ratio,
- QImage::Format_ARGB32_Premultiplied);
- _prepared.fill(Qt::black);
- } else {
- const auto width = _full.width();
- const auto skip = std::max((_full.height() - width) / 2, 0);
- _prepared = _full.copy(0, skip, width, width).scaled(
- QSize(size, size) * ratio,
- Qt::IgnoreAspectRatio,
- Qt::SmoothTransformation);
- }
- if (_forceRound) {
- _prepared = Images::Circle(std::move(_prepared));
- }
- _prepared.setDevicePixelRatio(ratio);
- }
- return _prepared;
- }
- void MediaThumbnail::subscribeToUpdates(Fn<void()> callback) {
- _subscription.destroy();
- if (!callback) {
- clear();
- return;
- } else if (!_full.isNull() && !_blurred) {
- return;
- }
- const auto thumbnail = loaded(_origin);
- if (const auto image = thumbnail.image) {
- _full = image->original();
- }
- _blurred = thumbnail.blurred;
- if (!_blurred) {
- _prepared = QImage();
- } else {
- _subscription = session().downloaderTaskFinished(
- ) | rpl::filter([=] {
- const auto thumbnail = loaded(_origin);
- if (!thumbnail.blurred) {
- _full = thumbnail.image->original();
- _prepared = QImage();
- _blurred = false;
- return true;
- }
- return false;
- }) | rpl::take(1) | rpl::start_with_next(callback);
- }
- }
- Data::FileOrigin MediaThumbnail::origin() const {
- return _origin;
- }
- bool MediaThumbnail::forceRound() const {
- return _forceRound;
- }
- PhotoThumbnail::PhotoThumbnail(
- not_null<PhotoData*> photo,
- Data::FileOrigin origin,
- bool forceRound)
- : MediaThumbnail(origin, forceRound)
- , _photo(photo) {
- }
- std::shared_ptr<DynamicImage> PhotoThumbnail::clone() {
- return std::make_shared<PhotoThumbnail>(_photo, origin(), forceRound());
- }
- Main::Session &PhotoThumbnail::session() {
- return _photo->session();
- }
- MediaThumbnail::Thumb PhotoThumbnail::loaded(Data::FileOrigin origin) {
- if (!_media) {
- _media = _photo->createMediaView();
- _media->wanted(Data::PhotoSize::Small, origin);
- }
- if (const auto small = _media->image(Data::PhotoSize::Small)) {
- return { .image = small };
- }
- return { .image = _media->thumbnailInline(), .blurred = true };
- }
- void PhotoThumbnail::clear() {
- _media = nullptr;
- }
- VideoThumbnail::VideoThumbnail(
- not_null<DocumentData*> video,
- Data::FileOrigin origin,
- bool forceRound)
- : MediaThumbnail(origin, forceRound)
- , _video(video) {
- }
- std::shared_ptr<DynamicImage> VideoThumbnail::clone() {
- return std::make_shared<VideoThumbnail>(_video, origin(), forceRound());
- }
- Main::Session &VideoThumbnail::session() {
- return _video->session();
- }
- MediaThumbnail::Thumb VideoThumbnail::loaded(Data::FileOrigin origin) {
- if (!_media) {
- _media = _video->createMediaView();
- _media->thumbnailWanted(origin);
- }
- if (const auto small = _media->thumbnail()) {
- return { .image = small };
- }
- return { .image = _media->thumbnailInline(), .blurred = true };
- }
- void VideoThumbnail::clear() {
- _media = nullptr;
- }
- std::shared_ptr<DynamicImage> EmptyThumbnail::clone() {
- return std::make_shared<EmptyThumbnail>();
- }
- QImage EmptyThumbnail::image(int size) {
- const auto ratio = style::DevicePixelRatio();
- if (_cached.width() != size * ratio) {
- _cached = QImage(
- QSize(size, size) * ratio,
- QImage::Format_ARGB32_Premultiplied);
- _cached.fill(Qt::black);
- _cached.setDevicePixelRatio(ratio);
- }
- return _cached;
- }
- void EmptyThumbnail::subscribeToUpdates(Fn<void()> callback) {
- }
- std::shared_ptr<DynamicImage> SavedMessagesUserpic::clone() {
- return std::make_shared<SavedMessagesUserpic>();
- }
- QImage SavedMessagesUserpic::image(int size) {
- const auto good = (_frame.width() == size * _frame.devicePixelRatio());
- const auto paletteVersion = style::PaletteVersion();
- if (!good || _paletteVersion != paletteVersion) {
- _paletteVersion = paletteVersion;
- const auto ratio = style::DevicePixelRatio();
- if (!good) {
- _frame = QImage(
- QSize(size, size) * ratio,
- QImage::Format_ARGB32_Premultiplied);
- _frame.setDevicePixelRatio(ratio);
- }
- _frame.fill(Qt::transparent);
- auto p = Painter(&_frame);
- Ui::EmptyUserpic::PaintSavedMessages(p, 0, 0, size, size);
- }
- return _frame;
- }
- void SavedMessagesUserpic::subscribeToUpdates(Fn<void()> callback) {
- if (!callback) {
- _frame = {};
- }
- }
- std::shared_ptr<DynamicImage> RepliesUserpic::clone() {
- return std::make_shared<RepliesUserpic>();
- }
- QImage RepliesUserpic::image(int size) {
- const auto good = (_frame.width() == size * _frame.devicePixelRatio());
- const auto paletteVersion = style::PaletteVersion();
- if (!good || _paletteVersion != paletteVersion) {
- _paletteVersion = paletteVersion;
- const auto ratio = style::DevicePixelRatio();
- if (!good) {
- _frame = QImage(
- QSize(size, size) * ratio,
- QImage::Format_ARGB32_Premultiplied);
- _frame.setDevicePixelRatio(ratio);
- }
- _frame.fill(Qt::transparent);
- auto p = Painter(&_frame);
- Ui::EmptyUserpic::PaintRepliesMessages(p, 0, 0, size, size);
- }
- return _frame;
- }
- void RepliesUserpic::subscribeToUpdates(Fn<void()> callback) {
- if (!callback) {
- _frame = {};
- }
- }
- std::shared_ptr<DynamicImage> HiddenAuthorUserpic::clone() {
- return std::make_shared<HiddenAuthorUserpic>();
- }
- QImage HiddenAuthorUserpic::image(int size) {
- const auto good = (_frame.width() == size * _frame.devicePixelRatio());
- const auto paletteVersion = style::PaletteVersion();
- if (!good || _paletteVersion != paletteVersion) {
- _paletteVersion = paletteVersion;
- const auto ratio = style::DevicePixelRatio();
- if (!good) {
- _frame = QImage(
- QSize(size, size) * ratio,
- QImage::Format_ARGB32_Premultiplied);
- _frame.setDevicePixelRatio(ratio);
- }
- _frame.fill(Qt::transparent);
- auto p = Painter(&_frame);
- Ui::EmptyUserpic::PaintHiddenAuthor(p, 0, 0, size, size);
- }
- return _frame;
- }
- void HiddenAuthorUserpic::subscribeToUpdates(Fn<void()> callback) {
- if (!callback) {
- _frame = {};
- }
- }
- IconThumbnail::IconThumbnail(const style::icon &icon) : _icon(icon) {
- }
- std::shared_ptr<DynamicImage> IconThumbnail::clone() {
- return std::make_shared<IconThumbnail>(_icon);
- }
- QImage IconThumbnail::image(int size) {
- const auto good = (_frame.width() == size * _frame.devicePixelRatio());
- const auto paletteVersion = style::PaletteVersion();
- if (!good || _paletteVersion != paletteVersion) {
- _paletteVersion = paletteVersion;
- const auto ratio = style::DevicePixelRatio();
- if (!good) {
- _frame = QImage(
- QSize(size, size) * ratio,
- QImage::Format_ARGB32_Premultiplied);
- _frame.setDevicePixelRatio(ratio);
- }
- _frame.fill(Qt::transparent);
- auto p = Painter(&_frame);
- _icon.paintInCenter(p, QRect(0, 0, size, size));
- }
- return _frame;
- }
- void IconThumbnail::subscribeToUpdates(Fn<void()> callback) {
- if (!callback) {
- _frame = {};
- }
- }
- EmojiThumbnail::EmojiThumbnail(
- not_null<Data::Session*> owner,
- const QString &data)
- : _owner(owner)
- , _data(data) {
- }
- void EmojiThumbnail::subscribeToUpdates(Fn<void()> callback) {
- if (!callback) {
- _emoji = nullptr;
- return;
- }
- _emoji = _owner->customEmojiManager().create(
- _data,
- std::move(callback),
- Data::CustomEmojiSizeTag::Large);
- }
- std::shared_ptr<DynamicImage> EmojiThumbnail::clone() {
- return std::make_shared<EmojiThumbnail>(_owner, _data);
- }
- QImage EmojiThumbnail::image(int size) {
- Expects(_emoji != nullptr);
- const auto ratio = style::DevicePixelRatio();
- const auto good = (_frame.width() == size * _frame.devicePixelRatio());
- if (!good) {
- _frame = QImage(
- QSize(size, size) * ratio,
- QImage::Format_ARGB32_Premultiplied);
- _frame.setDevicePixelRatio(ratio);
- }
- _frame.fill(Qt::transparent);
- auto p = Painter(&_frame);
- _emoji->paint(p, {
- .textColor = st::windowBoldFg->c,
- .now = crl::now(),
- .position = QPoint(0, 0),
- .paused = false,
- });
- p.end();
- return _frame;
- }
- } // namespace
- std::shared_ptr<DynamicImage> MakeUserpicThumbnail(
- not_null<PeerData*> peer,
- bool forceRound) {
- return std::make_shared<PeerUserpic>(peer, forceRound);
- }
- std::shared_ptr<DynamicImage> MakeSavedMessagesThumbnail() {
- return std::make_shared<SavedMessagesUserpic>();
- }
- std::shared_ptr<DynamicImage> MakeRepliesThumbnail() {
- return std::make_shared<RepliesUserpic>();
- }
- std::shared_ptr<DynamicImage> MakeHiddenAuthorThumbnail() {
- return std::make_shared<HiddenAuthorUserpic>();
- }
- std::shared_ptr<DynamicImage> MakeStoryThumbnail(
- not_null<Data::Story*> story) {
- using Result = std::shared_ptr<DynamicImage>;
- const auto id = story->fullId();
- return v::match(story->media().data, [](v::null_t) -> Result {
- return std::make_shared<EmptyThumbnail>();
- }, [&](not_null<PhotoData*> photo) -> Result {
- return std::make_shared<PhotoThumbnail>(photo, id, true);
- }, [&](not_null<DocumentData*> video) -> Result {
- return std::make_shared<VideoThumbnail>(video, id, true);
- });
- }
- std::shared_ptr<DynamicImage> MakeIconThumbnail(const style::icon &icon) {
- return std::make_shared<IconThumbnail>(icon);
- }
- std::shared_ptr<DynamicImage> MakeEmojiThumbnail(
- not_null<Data::Session*> owner,
- const QString &data) {
- return std::make_shared<EmojiThumbnail>(owner, data);
- }
- std::shared_ptr<DynamicImage> MakePhotoThumbnail(
- not_null<PhotoData*> photo,
- FullMsgId fullId) {
- return std::make_shared<PhotoThumbnail>(photo, fullId, false);
- }
- std::shared_ptr<DynamicImage> MakeDocumentThumbnail(
- not_null<DocumentData*> document,
- FullMsgId fullId) {
- return std::make_shared<VideoThumbnail>(document, fullId, false);
- }
- } // namespace Ui
|