From 792b9090a752538e840301203c12687afe7f68ab Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 1 Oct 2021 18:58:41 +0400 Subject: [PATCH] Generate mini-previews for photos and files. --- .../SourceFiles/data/data_media_types.cpp | 168 +++++++++++++++++- Telegram/SourceFiles/dialogs/dialogs.style | 6 +- .../dialogs/ui/dialogs_message_view.cpp | 47 ++++- .../dialogs/ui/dialogs_message_view.h | 8 +- Telegram/SourceFiles/history/history_item.cpp | 2 +- Telegram/SourceFiles/history/history_item.h | 3 + .../SourceFiles/history/history_service.cpp | 6 +- 7 files changed, 226 insertions(+), 14 deletions(-) diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 942dc6b26..919a540d7 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_theme_document.h" #include "history/view/media/history_view_slot_machine.h" #include "history/view/media/history_view_dice.h" +#include "dialogs/ui/dialogs_message_view.h" #include "ui/image/image.h" #include "ui/text/format_song_document_name.h" #include "ui/text/format_values.h" @@ -37,18 +38,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/localstorage.h" #include "chat_helpers/stickers_dice_pack.h" // Stickers::DicePacks::IsSlot. #include "data/data_session.h" +#include "data/data_auto_download.h" #include "data/data_photo.h" +#include "data/data_photo_media.h" #include "data/data_document.h" +#include "data/data_document_media.h" #include "data/data_game.h" #include "data/data_web_page.h" #include "data/data_poll.h" #include "data/data_channel.h" #include "data/data_file_origin.h" #include "main/main_session.h" +#include "main/main_session_settings.h" #include "lang/lang_keys.h" #include "storage/file_upload.h" #include "app.h" #include "styles/style_chat.h" +#include "styles/style_dialogs.h" namespace Data { namespace { @@ -134,6 +140,130 @@ using ItemPreview = HistoryView::ItemPreview; caption); } +[[nodiscard]] QImage PreparePreviewImage( + not_null image, + ImageRoundRadius radius = ImageRoundRadius::Small) { + const auto original = image->original(); + if (original.width() * 10 < original.height() + || original.height() * 10 < original.width()) { + return QImage(); + } + const auto factor = style::DevicePixelRatio(); + const auto size = st::dialogsMiniPreview * factor; + const auto scaled = original.scaled( + QSize(size, size), + Qt::KeepAspectRatioByExpanding, + Qt::SmoothTransformation); + auto square = scaled.copy( + (scaled.width() - size) / 2, + (scaled.height() - size) / 2, + size, + size + ).convertToFormat(QImage::Format_ARGB32_Premultiplied); + Images::prepareRound(square, radius); + square.setDevicePixelRatio(factor); + return square; +} + +struct PreparedPreview { + QImage preview; + bool loading = false; + + explicit operator bool() const { + return !preview.isNull() || loading; + } +}; + +[[nodiscard]] PreparedPreview PreparePhotoPreview( + not_null item, + const std::shared_ptr &media, + ImageRoundRadius radius) { + if (const auto small = media->image(PhotoSize::Small)) { + return { PreparePreviewImage(small, radius) }; + } else if (const auto thumbnail = media->image(PhotoSize::Thumbnail)) { + return { PreparePreviewImage(thumbnail, radius) }; + } else if (const auto large = media->image(PhotoSize::Large)) { + return { PreparePreviewImage(large, radius) }; + } + const auto allowedToDownload = [&] { + const auto photo = media->owner(); + if (media->loaded() || photo->cancelled()) { + return false; + } + return photo->hasExact(PhotoSize::Small) + || photo->hasExact(PhotoSize::Thumbnail) + || AutoDownload::Should( + photo->session().settings().autoDownload(), + item->history()->peer, + photo); + }(); + if (allowedToDownload) { + media->owner()->load(PhotoSize::Small, item->fullId()); + } + if (const auto blurred = media->thumbnailInline()) { + return { PreparePreviewImage(blurred, radius), allowedToDownload }; + } + return { QImage(), allowedToDownload }; +} + +[[nodiscard]] PreparedPreview PrepareFilePreviewImage( + not_null item, + const std::shared_ptr &media, + ImageRoundRadius radius) { + Expects(media->owner()->hasThumbnail()); + + if (const auto thumbnail = media->thumbnail()) { + return { PreparePreviewImage(thumbnail, radius) }; + } + media->owner()->loadThumbnail(item->fullId()); + if (const auto blurred = media->thumbnailInline()) { + return { PreparePreviewImage(blurred, radius), true }; + } + return { QImage(), true }; +} + +[[nodiscard]] QImage PutPlayIcon(QImage preview) { + Expects(!preview.isNull()); + + { + QPainter p(&preview); + //st::overviewVideoPlay.paintInCenter( + // p, + // QRect(QPoint(), preview.size() / preview.devicePixelRatio())); + } + return preview; +} + +[[nodiscard]] PreparedPreview PrepareFilePreview( + not_null item, + const std::shared_ptr &media, + ImageRoundRadius radius) { + if (auto result = PrepareFilePreviewImage(item, media, radius)) { + const auto document = media->owner(); + if (!result.preview.isNull() + && (document->isVideoFile() || document->isVideoMessage())) { + result.preview = PutPlayIcon(std::move(result.preview)); + } + return result; + } + Expects(media->owner()->hasThumbnail()); + + if (const auto thumbnail = media->thumbnail()) { + return { PreparePreviewImage(thumbnail, radius) }; + } + media->owner()->loadThumbnail(item->fullId()); + if (const auto blurred = media->thumbnailInline()) { + return { PreparePreviewImage(blurred, radius), true }; + } + return { QImage(), true }; +} + +[[nodiscard]] bool TryFilePreview(not_null document) { + return document->hasThumbnail() + && !document->sticker() + && !document->isAudioFile(); +} + } // namespace TextForMimeData WithCaptionClipboardText( @@ -348,11 +478,25 @@ ItemPreview MediaPhoto::toPreview(ToPreviewOptions options) const { const auto caption = options.hideCaption ? QString() : parent()->originalText().text; - // #TODO minis generate images and support albums + auto images = std::vector(); + // #TODO minis support albums + const auto media = _photo->createMediaView(); + const auto radius = _chat + ? ImageRoundRadius::Ellipse + : ImageRoundRadius::Small; + auto context = std::any(); + if (auto prepared = PreparePhotoPreview(parent(), media, radius)) { + images.push_back(std::move(prepared.preview)); + if (prepared.loading) { + context = media; + } + } return { .text = WithCaptionDialogsText( tr::lng_in_dlg_photo(tr::now), - caption) + caption), + .images = std::move(images), + .loadingContext = std::move(context), }; } @@ -544,9 +688,27 @@ ItemPreview MediaFile::toPreview(ToPreviewOptions options) const { const auto caption = options.hideCaption ? QString() : parent()->originalText().text; - // #TODO minis generate images and support albums + // #TODO minis support albums + auto images = std::vector(); + const auto media = TryFilePreview(_document) + ? _document->createMediaView() + : nullptr; + const auto radius = _document->isVideoMessage() + ? ImageRoundRadius::Ellipse + : ImageRoundRadius::Small; + auto context = std::any(); + if (media) { + if (auto prepared = PrepareFilePreview(parent(), media, radius)) { + images.push_back(std::move(prepared.preview)); + if (prepared.loading) { + context = media; + } + } + } return { .text = WithCaptionDialogsText(type, caption), + .images = std::move(images), + .loadingContext = std::move(context), }; } diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 1312cb3ca..00148aac4 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -296,6 +296,6 @@ dialogsScamFont: font(9px semibold); dialogsScamSkip: 4px; dialogsScamRadius: 2px; -dialogsMiniPreview: 32px; -dialogsMiniPreviewSkip: 4px; -dialogsMiniPreviewRight: 6px; +dialogsMiniPreview: 16px; +dialogsMiniPreviewSkip: 2px; +dialogsMiniPreviewRight: 3px; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp index 176f83d87..0b088188a 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp @@ -7,8 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "dialogs/ui/dialogs_message_view.h" +#include "history/history.h" #include "history/history_item.h" +#include "main/main_session.h" #include "ui/text/text_options.h" +#include "ui/image/image.h" #include "styles/style_dialogs.h" namespace Dialogs::Ui { @@ -16,6 +19,11 @@ namespace { } // namespace +struct MessageView::LoadingContext { + std::any context; + rpl::lifetime lifetime; +}; + MessageView::MessageView() : _textCache(st::dialogsTextWidthMin) { } @@ -43,12 +51,41 @@ void MessageView::paint( return; } if (_textCachedFor != item.get()) { - const auto preview = item->toPreview(options); + auto preview = item->toPreview(options); _textCache.setText( st::dialogsTextStyle, preview.text, DialogTextOptions()); _textCachedFor = item; + _imagesCache = std::move(preview.images); + if (preview.loadingContext.has_value()) { + if (!_loadingContext) { + _loadingContext = std::make_unique(); + item->history()->session().downloaderTaskFinished( + ) | rpl::start_with_next([=] { + _textCachedFor = nullptr; + }, _loadingContext->lifetime); + } + _loadingContext->context = std::move(preview.loadingContext); + } else { + _loadingContext = nullptr; + } + } + auto rect = geometry; + for (const auto &image : _imagesCache) { + if (rect.width() < st::dialogsMiniPreview) { + break; + } + p.drawImage(rect.topLeft(), image); + rect.setLeft(rect.x() + + st::dialogsMiniPreview + + st::dialogsMiniPreviewSkip); + } + if (!_imagesCache.empty()) { + rect.setLeft(rect.x() + st::dialogsMiniPreviewRight); + } + if (rect.isEmpty()) { + return; } p.setTextPalette(active ? st::dialogsTextPaletteActive @@ -59,10 +96,10 @@ void MessageView::paint( p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg)); _textCache.drawElided( p, - geometry.left(), - geometry.top(), - geometry.width(), - geometry.height() / st::dialogsTextFont->height); + rect.left(), + rect.top(), + rect.width(), + rect.height() / st::dialogsTextFont->height); p.restoreTextPalette(); } diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h index a365cea1b..cdf12965e 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h @@ -7,10 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include + +class Image; class HistoryItem; +enum class ImageRoundRadius; namespace Ui { - } // namespace Ui namespace HistoryView { @@ -42,9 +45,12 @@ public: ToPreviewOptions options) const; private: + struct LoadingContext; + mutable const HistoryItem *_textCachedFor = nullptr; mutable Ui::Text::String _textCache; mutable std::vector _imagesCache; + mutable std::unique_ptr _loadingContext; }; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index f3a6a3be0..e4f373d11 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -926,7 +926,7 @@ bool HistoryItem::isEmpty() const { QString HistoryItem::notificationText() const { const auto result = [&] { - if (_media) { + if (_media && !serviceMsg()) { return _media->notificationText(); } else if (!emptyText()) { return _text.toString(); diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 2568c1ad3..4e7ed3d08 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/value_ordering.h" #include "data/data_media_types.h" +#include + enum class UnreadMentionType; struct HistoryMessageReplyMarkup; class ReplyKeyboard; @@ -63,6 +65,7 @@ struct ToPreviewOptions { struct ItemPreview { QString text; std::vector images; + std::any loadingContext; }; } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index 96e10eca7..109c871c2 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -926,9 +926,13 @@ bool HistoryService::needCheck() const { } ItemPreview HistoryService::toPreview(ToPreviewOptions options) const { - // #TODO minis generate images + // Don't show for service messages (chat photo changed). + // Because larger version is shown exactly to the left of the preview. + //auto media = _media ? _media->toPreview(options) : ItemPreview(); return { .text = textcmdLink(1, TextUtilities::Clean(notificationText())), + //.images = std::move(media.images), + //.loadingContext = std::move(media.loadingContext), }; }