Generate mini-previews for photos and files.

This commit is contained in:
John Preston 2021-10-01 18:58:41 +04:00
parent 8c21fad642
commit 792b9090a7
7 changed files with 226 additions and 14 deletions

View File

@ -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<const Image*> 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<const HistoryItem*> item,
const std::shared_ptr<PhotoMedia> &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<const HistoryItem*> item,
const std::shared_ptr<DocumentMedia> &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<const HistoryItem*> item,
const std::shared_ptr<DocumentMedia> &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<DocumentData*> 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<QImage>();
// #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<QImage>();
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),
};
}

View File

@ -296,6 +296,6 @@ dialogsScamFont: font(9px semibold);
dialogsScamSkip: 4px;
dialogsScamRadius: 2px;
dialogsMiniPreview: 32px;
dialogsMiniPreviewSkip: 4px;
dialogsMiniPreviewRight: 6px;
dialogsMiniPreview: 16px;
dialogsMiniPreviewSkip: 2px;
dialogsMiniPreviewRight: 3px;

View File

@ -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<LoadingContext>();
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();
}

View File

@ -7,10 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include <any>
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<QImage> _imagesCache;
mutable std::unique_ptr<LoadingContext> _loadingContext;
};

View File

@ -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();

View File

@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/value_ordering.h"
#include "data/data_media_types.h"
#include <any>
enum class UnreadMentionType;
struct HistoryMessageReplyMarkup;
class ReplyKeyboard;
@ -63,6 +65,7 @@ struct ToPreviewOptions {
struct ItemPreview {
QString text;
std::vector<QImage> images;
std::any loadingContext;
};
} // namespace HistoryView

View File

@ -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),
};
}