Use Data::DocumentMedia to store good thumbnails.

This commit is contained in:
John Preston 2020-03-27 15:40:50 +04:00
parent 61647275e8
commit 7db53599e8
29 changed files with 545 additions and 168 deletions

View File

@ -360,6 +360,8 @@ PRIVATE
data/data_document.h
data/data_document_good_thumbnail.cpp
data/data_document_good_thumbnail.h
data/data_document_media.cpp
data/data_document_media.h
data/data_drafts.cpp
data/data_drafts.h
data/data_folder.cpp

View File

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_streaming.h"
#include "data/data_document_good_thumbnail.h"
#include "data/data_document_media.h"
#include "lang/lang_keys.h"
#include "inline_bots/inline_bot_layout_item.h"
#include "main/main_session.h"
@ -569,7 +570,6 @@ void DocumentData::setattributes(
_additional = nullptr;
}
}
validateGoodThumbnail();
if (isAudioFile() || isAnimation() || isVoiceMessage()) {
setMaybeSupportsStreaming(true);
}
@ -612,7 +612,6 @@ bool DocumentData::checkWallPaperProperties() {
return false; // #TODO themes support svg patterns
}
type = WallPaperDocument;
validateGoodThumbnail();
return true;
}
@ -661,48 +660,65 @@ Storage::Cache::Key DocumentData::goodThumbnailCacheKey() const {
return Data::DocumentThumbCacheKey(_dc, id);
}
Image *DocumentData::goodThumbnail() const {
return _goodThumbnail.get();
bool DocumentData::goodThumbnailChecked() const {
return (_goodThumbnailState & GoodThumbnailFlag::Mask)
== GoodThumbnailFlag::Checked;
}
void DocumentData::validateGoodThumbnail() {
if (!isVideoFile()
&& !isAnimation()
&& !isWallPaper()
&& !isTheme()
&& (!sticker() || !sticker()->animated)) {
_goodThumbnail = nullptr;
} else if (!_goodThumbnail && hasRemoteLocation()) {
_goodThumbnail = std::make_unique<Image>(
std::make_unique<Data::GoodThumbSource>(this));
}
bool DocumentData::goodThumbnailGenerating() const {
return (_goodThumbnailState & GoodThumbnailFlag::Mask)
== GoodThumbnailFlag::Generating;
}
void DocumentData::refreshGoodThumbnail() {
if (_goodThumbnail && hasRemoteLocation()) {
replaceGoodThumbnail(std::make_unique<Data::GoodThumbSource>(this));
}
bool DocumentData::goodThumbnailNoData() const {
return (_goodThumbnailState & GoodThumbnailFlag::Mask)
== GoodThumbnailFlag::NoData;
}
void DocumentData::replaceGoodThumbnail(
std::unique_ptr<Images::Source> &&source) {
_goodThumbnail->replaceSource(std::move(source));
void DocumentData::setGoodThumbnailGenerating() {
_goodThumbnailState = (_goodThumbnailState & ~GoodThumbnailFlag::Mask)
| GoodThumbnailFlag::Generating;
}
void DocumentData::setGoodThumbnailOnUpload(
QImage &&image,
QByteArray &&bytes) {
Expects(uploadingData != nullptr);
void DocumentData::setGoodThumbnailDataReady() {
_goodThumbnailState = GoodThumbnailFlag::DataReady
| (goodThumbnailNoData()
? GoodThumbnailFlag(0)
: (_goodThumbnailState & GoodThumbnailFlag::Mask));
}
if (image.isNull()) {
void DocumentData::setGoodThumbnailChecked(bool hasData) {
if (!hasData && (_goodThumbnailState & GoodThumbnailFlag::DataReady)) {
_goodThumbnailState &= ~GoodThumbnailFlag::DataReady;
_goodThumbnailState &= ~GoodThumbnailFlag::Mask;
Data::DocumentMedia::CheckGoodThumbnail(this);
return;
}
_goodThumbnail = std::make_unique<Image>(
std::make_unique<Images::LocalFileSource>(
QString(),
std::move(bytes),
sticker() ? "WEBP" : "JPG",
std::move(image)));
_goodThumbnailState = (_goodThumbnailState & ~GoodThumbnailFlag::Mask)
| (hasData
? GoodThumbnailFlag::Checked
: GoodThumbnailFlag::NoData);
}
std::shared_ptr<Data::DocumentMedia> DocumentData::createMediaView() {
if (auto result = activeMediaView()) {
return result;
}
auto result = std::make_shared<Data::DocumentMedia>(this);
_media = result;
return result;
}
std::shared_ptr<Data::DocumentMedia> DocumentData::activeMediaView() {
return _media.lock();
}
void DocumentData::setGoodThumbnailPhoto(not_null<PhotoData*> photo) {
_goodThumbnailPhoto = photo;
}
PhotoData *DocumentData::goodThumbnailPhoto() const {
return _goodThumbnailPhoto;
}
auto DocumentData::bigFileBaseCacheKey() const
@ -817,7 +833,8 @@ bool DocumentData::loaded(FilePathResolve resolve) const {
ActiveCache().increment(ComputeUsage(that->sticker()));
}
that->refreshGoodThumbnail();
that->setGoodThumbnailDataReady();
Data::DocumentMedia::CheckGoodThumbnail(that);
destroyLoader();
if (!that->_data.isEmpty() || that->getStickerLarge()) {
@ -1606,7 +1623,6 @@ void DocumentData::setRemoteLocation(
}
}
}
validateGoodThumbnail();
}
void DocumentData::setContentUrl(const QString &url) {

View File

@ -32,6 +32,7 @@ class Loader;
namespace Data {
class Session;
class DocumentMedia;
} // namespace Data
namespace Main {
@ -180,11 +181,18 @@ public:
ImagePtr thumbnailInline,
ImagePtr thumbnail);
[[nodiscard]] Image *goodThumbnail() const;
[[nodiscard]] Storage::Cache::Key goodThumbnailCacheKey() const;
void setGoodThumbnailOnUpload(QImage &&image, QByteArray &&bytes);
void refreshGoodThumbnail();
void replaceGoodThumbnail(std::unique_ptr<Images::Source> &&source);
[[nodiscard]] bool goodThumbnailChecked() const;
[[nodiscard]] bool goodThumbnailGenerating() const;
[[nodiscard]] bool goodThumbnailNoData() const;
void setGoodThumbnailGenerating();
void setGoodThumbnailDataReady();
void setGoodThumbnailChecked(bool hasData);
[[nodiscard]] std::shared_ptr<Data::DocumentMedia> createMediaView();
[[nodiscard]] std::shared_ptr<Data::DocumentMedia> activeMediaView();
void setGoodThumbnailPhoto(not_null<PhotoData*> photo);
[[nodiscard]] PhotoData *goodThumbnailPhoto() const;
[[nodiscard]] auto bigFileBaseCacheKey() const
-> std::optional<Storage::Cache::Key>;
@ -258,6 +266,17 @@ private:
using Flags = base::flags<Flag>;
friend constexpr bool is_flag_type(Flag) { return true; };
enum class GoodThumbnailFlag : uchar {
Checked = 0x01,
Generating = 0x02,
NoData = 0x03,
Mask = 0x03,
DataReady = 0x04,
};
using GoodThumbnailState = base::flags<GoodThumbnailFlag>;
friend constexpr bool is_flag_type(GoodThumbnailFlag) { return true; };
static constexpr Flags kStreamingSupportedMask = Flags()
| Flag::StreamingMaybeYes
| Flag::StreamingMaybeNo;
@ -272,9 +291,8 @@ private:
friend class Serialize::Document;
LocationType locationType() const;
[[nodiscard]] LocationType locationType() const;
void validateLottieSticker();
void validateGoodThumbnail();
void setMaybeSupportsStreaming(bool supports);
void setLoadedInMediaCacheLocation();
@ -294,8 +312,9 @@ private:
ImagePtr _thumbnailInline;
ImagePtr _thumbnail;
std::unique_ptr<Image> _goodThumbnail;
Data::ReplyPreview _replyPreview;
std::weak_ptr<Data::DocumentMedia> _media;
PhotoData *_goodThumbnailPhoto = nullptr;
not_null<Data::Session*> _owner;
@ -304,6 +323,7 @@ private:
std::unique_ptr<DocumentAdditionalData> _additional;
int32 _duration = -1;
mutable Flags _flags = kStreamingSupportedUnknown;
GoodThumbnailState _goodThumbnailState = GoodThumbnailState();
mutable std::unique_ptr<FileLoader> _loader;
};

View File

@ -0,0 +1,202 @@
/*
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 "data/data_document_media.h"
#include "data/data_document.h"
#include "data/data_document_good_thumbnail.h"
#include "data/data_session.h"
#include "data/data_cloud_themes.h"
#include "media/clip/media_clip_reader.h"
#include "main/main_session.h"
#include "lottie/lottie_animation.h"
#include "window/themes/window_theme_preview.h"
#include "ui/image/image.h"
#include "app.h"
#include <QtCore/QBuffer>
#include <QtGui/QImageReader>
namespace Data {
namespace {
constexpr auto kReadAreaLimit = 12'032 * 9'024;
constexpr auto kWallPaperThumbnailLimit = 960;
constexpr auto kMaxVideoFrameArea = 7'680 * 4'320;
constexpr auto kGoodThumbQuality = 87;
enum class FileType {
Video,
AnimatedSticker,
WallPaper,
Theme,
};
[[nodiscard]] bool MayHaveGoodThumbnail(not_null<DocumentData*> owner) {
return owner->isVideoFile()
|| owner->isAnimation()
|| owner->isWallPaper()
|| owner->isTheme()
|| (owner->sticker() && owner->sticker()->animated);
}
[[nodiscard]] QImage PrepareGoodThumbnail(
const QString &path,
QByteArray data,
FileType type) {
if (type == FileType::Video) {
return ::Media::Clip::PrepareForSending(path, data).thumbnail;
} else if (type == FileType::AnimatedSticker) {
return Lottie::ReadThumbnail(Lottie::ReadContent(data, path));
} else if (type == FileType::Theme) {
return Window::Theme::GeneratePreview(data, path);
}
auto buffer = QBuffer(&data);
auto file = QFile(path);
auto device = data.isEmpty() ? static_cast<QIODevice*>(&file) : &buffer;
auto reader = QImageReader(device);
const auto size = reader.size();
if (!reader.canRead()
|| (size.width() * size.height() > kReadAreaLimit)) {
return QImage();
}
auto result = reader.read();
if (!result.width() || !result.height()) {
return QImage();
}
return (result.width() > kWallPaperThumbnailLimit
|| result.height() > kWallPaperThumbnailLimit)
? result.scaled(
kWallPaperThumbnailLimit,
kWallPaperThumbnailLimit,
Qt::KeepAspectRatio,
Qt::SmoothTransformation)
: result;
}
} // namespace
DocumentMedia::DocumentMedia(not_null<DocumentData*> owner)
: _owner(owner) {
}
DocumentMedia::~DocumentMedia() = default;
void DocumentMedia::goodThumbnailWanted() {
_flags |= Flag::GoodThumbnailWanted;
}
Image *DocumentMedia::goodThumbnail() const {
Expects((_flags & Flag::GoodThumbnailWanted) != 0);
if (!_goodThumbnail) {
ReadOrGenerateThumbnail(_owner);
}
return _goodThumbnail.get();
}
void DocumentMedia::setGoodThumbnail(QImage thumbnail) {
if (!(_flags & Flag::GoodThumbnailWanted)) {
return;
}
_goodThumbnail = std::make_unique<Image>(
std::make_unique<Images::ImageSource>(std::move(thumbnail), "PNG"));
_owner->session().downloaderTaskFinished().notify();
}
void DocumentMedia::GenerateGoodThumbnail(not_null<DocumentData*> document) {
const auto data = document->data();
const auto type = document->isWallPaper()
? FileType::WallPaper
: document->isTheme()
? FileType::Theme
: document->sticker()
? FileType::AnimatedSticker
: FileType::Video;
auto location = document->location().isEmpty()
? nullptr
: std::make_unique<FileLocation>(document->location());
if (data.isEmpty() && !location) {
document->setGoodThumbnailChecked(false);
return;
}
const auto guard = base::make_weak(&document->owner().session());
crl::async([=, location = std::move(location)] {
const auto filepath = (location && location->accessEnable())
? location->name()
: QString();
auto result = PrepareGoodThumbnail(filepath, data, type);
auto bytes = QByteArray();
if (!result.isNull()) {
auto buffer = QBuffer(&bytes);
const auto format = (type == FileType::AnimatedSticker)
? "WEBP"
: (type == FileType::WallPaper && result.hasAlphaChannel())
? "PNG"
: "JPG";
result.save(&buffer, format, kGoodThumbQuality);
}
if (!filepath.isEmpty()) {
location->accessDisable();
}
const auto cache = bytes.isEmpty() ? QByteArray("(failed)") : bytes;
crl::on_main(guard, [=] {
document->setGoodThumbnailChecked(true);
if (const auto active = document->activeMediaView()) {
active->setGoodThumbnail(result);
}
document->owner().cache().put(
document->goodThumbnailCacheKey(),
Storage::Cache::Database::TaggedValue{
base::duplicate(cache),
kImageCacheTag });
});
});
}
void DocumentMedia::CheckGoodThumbnail(not_null<DocumentData*> document) {
if (!document->goodThumbnailChecked()) {
ReadOrGenerateThumbnail(document);
}
}
void DocumentMedia::ReadOrGenerateThumbnail(
not_null<DocumentData*> document) {
if (document->goodThumbnailGenerating()
|| document->goodThumbnailNoData()
|| !MayHaveGoodThumbnail(document)) {
return;
}
document->setGoodThumbnailGenerating();
const auto guard = base::make_weak(&document->session());
const auto active = document->activeMediaView();
const auto got = [=](QByteArray value) {
if (value.isEmpty()) {
crl::on_main(guard, [=] {
GenerateGoodThumbnail(document);
});
} else if (active) {
crl::async([=] {
const auto image = App::readImage(value, nullptr, false);
crl::on_main(guard, [=] {
document->setGoodThumbnailChecked(true);
if (const auto active = document->activeMediaView()) {
active->setGoodThumbnail(image);
}
});
});
} else {
crl::on_main(guard, [=] {
document->setGoodThumbnailChecked(true);
});
}
};
document->owner().cache().get(document->goodThumbnailCacheKey(), got);
}
} // namespace Data

View File

@ -0,0 +1,43 @@
/*
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
*/
#pragma once
#include "base/flags.h"
namespace Data {
class DocumentMedia final {
public:
explicit DocumentMedia(not_null<DocumentData*> owner);
~DocumentMedia();
void goodThumbnailWanted();
[[nodiscard]] Image *goodThumbnail() const;
void setGoodThumbnail(QImage thumbnail);
// For DocumentData.
void validateGoodThumbnail();
static void CheckGoodThumbnail(not_null<DocumentData*> document);
private:
enum class Flag : uchar {
GoodThumbnailWanted = 0x01,
};
inline constexpr bool is_flag_type(Flag) { return true; };
using Flags = base::flags<Flag>;
static void ReadOrGenerateThumbnail(not_null<DocumentData*> document);
static void GenerateGoodThumbnail(not_null<DocumentData*> document);
const not_null<DocumentData*> _owner;
std::unique_ptr<Image> _goodThumbnail;
Flags _flags;
};
} // namespace Data

View File

@ -750,23 +750,6 @@ bool MediaFile::updateSentMedia(const MTPMessageMedia &media) {
return false;
}
parent()->history()->owner().documentConvert(_document, *content);
if (const auto good = _document->goodThumbnail()) {
auto bytes = good->bytesForCache();
if (const auto length = bytes.size()) {
if (length > Storage::kMaxFileInMemory) {
LOG(("App Error: Bad thumbnail data for saving to cache."));
} else {
parent()->history()->owner().cache().putIfEmpty(
_document->goodThumbnailCacheKey(),
Storage::Cache::Database::TaggedValue(
std::move(bytes),
Data::kImageCacheTag));
_document->refreshGoodThumbnail();
}
}
}
return true;
}

View File

@ -2421,6 +2421,7 @@ void Session::documentConvert(
}();
const auto oldKey = original->mediaKey();
const auto oldCacheKey = original->cacheKey();
const auto oldGoodKey = original->goodThumbnailCacheKey();
const auto idChanged = (original->id != id);
const auto sentSticker = idChanged && (original->sticker() != nullptr);
if (idChanged) {
@ -2444,6 +2445,7 @@ void Session::documentConvert(
documentApplyFields(original, data);
if (idChanged) {
cache().moveIfEmpty(oldCacheKey, original->cacheKey());
cache().moveIfEmpty(oldGoodKey, original->goodThumbnailCacheKey());
if (savedGifs().indexOf(original) >= 0) {
Local::writeSavedGifs();
}
@ -3180,10 +3182,10 @@ void Session::unregisterPlayingVideoFile(not_null<ViewElement*> view) {
if (i != _playingVideoFiles.end()) {
if (!--i->second) {
_playingVideoFiles.erase(i);
unregisterHeavyViewPart(view);
view->checkHeavyPart();
}
} else {
unregisterHeavyViewPart(view);
view->checkHeavyPart();
}
}
@ -3205,7 +3207,7 @@ void Session::checkPlayingVideoFiles() {
continue;
}
}
unregisterHeavyViewPart(view);
view->checkHeavyPart();
}
}

View File

@ -237,15 +237,7 @@ bool WebPageData::applyChanges(
}
void WebPageData::replaceDocumentGoodThumbnail() {
if (!document || !photo || !document->goodThumbnail()) {
return;
if (document && photo) {
document->setGoodThumbnailPhoto(photo);
}
const auto &location = photo->large()->location();
if (location.valid()) {
document->replaceGoodThumbnail(
std::make_unique<Images::StorageSource>(
location,
photo->large()->bytesSize()));
}
}

View File

@ -66,7 +66,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
constexpr auto kScrollDateHideTimeout = 1000;
constexpr auto kUnloadHeavyPartsPages = 1;
constexpr auto kUnloadHeavyPartsPages = 2;
// Helper binary search for an item in a list that is not completely
// above the given top of the visible area or below the given bottom of the visible area

View File

@ -606,6 +606,12 @@ auto Element::verticalRepaintRange() const -> VerticalRepaintRange {
};
}
void Element::checkHeavyPart() {
if (_media) {
_media->checkHeavyPart();
}
}
void Element::unloadHeavyPart() {
if (_media) {
_media->unloadHeavyPart();

View File

@ -261,7 +261,8 @@ public:
};
[[nodiscard]] virtual VerticalRepaintRange verticalRepaintRange() const;
virtual void unloadHeavyPart();
void checkHeavyPart();
void unloadHeavyPart();
// Legacy blocks structure.
HistoryBlock *block();

View File

@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_streaming.h"
#include "data/data_document.h"
#include "data/data_file_origin.h"
#include "data/data_document_media.h"
#include "app.h"
#include "styles/style_history.h"
@ -92,6 +93,8 @@ Gif::~Gif() {
_data->owner().streaming().keepAlive(_data);
setStreamed(nullptr);
}
_dataMedia = nullptr;
checkHeavyPart();
}
QSize Gif::sizeForAspectRatio() const {
@ -404,7 +407,8 @@ void Gif::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms
}
}
} else {
const auto good = _data->goodThumbnail();
ensureDataMediaCreated();
const auto good = _dataMedia->goodThumbnail();
if (good && good->loaded()) {
p.drawPixmap(rthumb.topLeft(), good->pixSingle({}, _thumbw, _thumbh, usew, painth, roundRadius, roundCorners));
} else {
@ -1071,6 +1075,15 @@ TextState Gif::getStateGrouped(
: _savel);
}
void Gif::ensureDataMediaCreated() const {
if (_dataMedia) {
return;
}
_dataMedia = _data->createMediaView();
_dataMedia->goodThumbnailWanted();
history()->owner().registerHeavyViewPart(_parent);
}
bool Gif::uploading() const {
return _data->uploading();
}
@ -1116,7 +1129,10 @@ void Gif::validateGroupedCache(
not_null<uint64*> cacheKey,
not_null<QPixmap*> cache) const {
using Option = Images::Option;
const auto good = _data->goodThumbnail();
ensureDataMediaCreated();
const auto good = _dataMedia->goodThumbnail();
const auto useGood = (good && good->loaded());
const auto thumb = _data->thumbnail();
const auto useThumb = (thumb && thumb->loaded());
@ -1245,6 +1261,17 @@ void Gif::parentTextUpdated() {
}
}
void Gif::checkHeavyPart() {
if (!_dataMedia && !_streamed) {
history()->owner().unregisterHeavyViewPart(_parent);
}
}
void Gif::unloadHeavyPart() {
stopAnimation();
_dataMedia = nullptr;
}
void Gif::refreshParentId(not_null<HistoryItem*> realParent) {
File::refreshParentId(realParent);
if (_parent->media() == this) {

View File

@ -15,6 +15,10 @@ struct HistoryMessageReply;
struct HistoryMessageForwarded;
class Painter;
namespace Data {
class DocumentMedia;
} // namespace Data
namespace Media {
namespace View {
class PlaybackProgress;
@ -100,9 +104,8 @@ public:
void parentTextUpdated() override;
void unloadHeavyPart() override {
stopAnimation();
}
void checkHeavyPart() override;
void unloadHeavyPart() override;
void refreshParentId(not_null<HistoryItem*> realParent) override;
@ -113,6 +116,7 @@ private:
bool dataFinished() const override;
bool dataLoaded() const override;
void ensureDataMediaCreated() const;
void refreshCaption();
[[nodiscard]] bool autoplayEnabled() const;
@ -161,11 +165,12 @@ private:
StateRequest request,
QPoint position) const;
not_null<DocumentData*> _data;
const not_null<DocumentData*> _data;
int _thumbw = 1;
int _thumbh = 1;
Ui::Text::String _caption;
std::unique_ptr<Streamed> _streamed;
mutable std::shared_ptr<Data::DocumentMedia> _dataMedia;
QString _downloadSize;

View File

@ -253,6 +253,8 @@ public:
crl::time ms) const {
}
virtual void checkHeavyPart() {
}
virtual void unloadHeavyPart() {
}

View File

@ -422,6 +422,12 @@ int GroupedMedia::checkAnimationCount() {
return result;
}
void GroupedMedia::checkHeavyPart() {
for (auto &part : _parts) {
part.content->checkHeavyPart();
}
}
void GroupedMedia::unloadHeavyPart() {
for (auto &part : _parts) {
part.content->unloadHeavyPart();

View File

@ -88,6 +88,7 @@ public:
void stopAnimation() override;
int checkAnimationCount() override;
void checkHeavyPart() override;
void unloadHeavyPart() override;
void parentTextUpdated() override;

View File

@ -34,6 +34,8 @@ public:
}
virtual void clearStickerLoopPlayed() {
}
virtual void checkHeavyPart() {
}
virtual void unloadHeavyPart() {
}
virtual void refreshLink() {
@ -82,6 +84,9 @@ public:
_content->clearStickerLoopPlayed();
}
void checkHeavyPart() override {
_content->checkHeavyPart();
}
void unloadHeavyPart() override {
_content->unloadHeavyPart();
}

View File

@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h" // isGifPausedAtLeastFor.
#include "data/data_session.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_file_origin.h"
#include "lottie/lottie_single_player.h"
#include "styles/style_history.h"
@ -55,12 +56,12 @@ namespace {
Sticker::Sticker(
not_null<Element*> parent,
not_null<DocumentData*> document,
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements)
: _parent(parent)
, _document(document)
, _data(data)
, _replacements(replacements) {
_document->loadThumbnail(parent->data()->fullId());
_data->loadThumbnail(parent->data()->fullId());
}
Sticker::~Sticker() {
@ -72,9 +73,9 @@ bool Sticker::isEmojiSticker() const {
}
void Sticker::initSize() {
_size = _document->dimensions;
_size = _data->dimensions;
if (isEmojiSticker() || _diceIndex >= 0) {
_size = GetAnimatedEmojiSize(&_document->session(), _size);
_size = GetAnimatedEmojiSize(&_data->session(), _size);
[[maybe_unused]] bool result = readyToDrawLottie();
} else {
_size = DownscaledSize(
@ -92,13 +93,13 @@ bool Sticker::readyToDrawLottie() {
if (!_lastDiceFrame.isNull()) {
return true;
}
const auto sticker = _document->sticker();
const auto sticker = _data->sticker();
if (!sticker) {
return false;
}
_document->checkStickerLarge();
const auto loaded = _document->loaded();
_data->checkStickerLarge();
const auto loaded = _data->loaded();
if (sticker->animated && !_lottie && loaded) {
setupLottie();
}
@ -123,8 +124,8 @@ QSize Sticker::GetAnimatedEmojiSize(
void Sticker::draw(Painter &p, const QRect &r, bool selected) {
if (readyToDrawLottie()) {
paintLottie(p, r, selected);
} else if (_document->sticker()
&& (!_document->sticker()->animated || !_replacements)) {
} else if (_data->sticker()
&& (!_data->sticker()->animated || !_replacements)) {
paintPixmap(p, r, selected);
}
}
@ -166,7 +167,7 @@ void Sticker::paintLottie(Painter &p, const QRect &r, bool selected) {
: (_diceIndex == 0)
? false
: (isEmojiSticker()
|| !_document->session().settings().loopAnimatedStickers());
|| !_data->session().settings().loopAnimatedStickers());
const auto count = _lottie->information().framesCount;
_atTheEnd = (frame.index + 1 == count);
_nextLastDiceFrame = !paused
@ -197,22 +198,24 @@ void Sticker::paintPixmap(Painter &p, const QRect &r, bool selected) {
}
QPixmap Sticker::paintedPixmap(bool selected) const {
ensureDataMediaCreated();
const auto o = _parent->data()->fullId();
const auto w = _size.width();
const auto h = _size.height();
const auto &c = st::msgStickerOverlay;
const auto good = _document->goodThumbnail();
const auto good = _dataMedia->goodThumbnail();
if (good && !good->loaded()) {
good->load({});
}
if (const auto image = _document->getStickerLarge()) {
if (const auto image = _data->getStickerLarge()) {
return selected
? image->pixColored(o, c, w, h)
: image->pix(o, w, h);
//
// Inline thumbnails can't have alpha channel.
//
//} else if (const auto blurred = _document->thumbnailInline()) {
//} else if (const auto blurred = _data->thumbnailInline()) {
// return selected
// ? blurred->pixBlurredColored(o, c, w, h)
// : blurred->pixBlurred(o, w, h);
@ -220,7 +223,7 @@ QPixmap Sticker::paintedPixmap(bool selected) const {
return selected
? good->pixColored(o, c, w, h)
: good->pix(o, w, h);
} else if (const auto thumbnail = _document->thumbnail()) {
} else if (const auto thumbnail = _data->thumbnail()) {
return selected
? thumbnail->pixBlurredColored(o, c, w, h)
: thumbnail->pixBlurred(o, w, h);
@ -232,7 +235,7 @@ void Sticker::refreshLink() {
if (_link) {
return;
}
const auto sticker = _document->sticker();
const auto sticker = _data->sticker();
if (isEmojiSticker()) {
const auto weak = base::make_weak(this);
_link = std::make_shared<LambdaClickHandler>([weak] {
@ -245,12 +248,20 @@ void Sticker::refreshLink() {
that->_parent);
});
} else if (sticker && sticker->set.type() != mtpc_inputStickerSetEmpty) {
_link = std::make_shared<LambdaClickHandler>([document = _document] {
_link = std::make_shared<LambdaClickHandler>([document = _data] {
StickerSetBox::Show(App::wnd()->sessionController(), document);
});
}
}
void Sticker::ensureDataMediaCreated() const {
if (_dataMedia) {
return;
}
_dataMedia = _data->createMediaView();
_dataMedia->goodThumbnailWanted();
}
void Sticker::setDiceIndex(const QString &emoji, int index) {
_diceEmoji = emoji;
_diceIndex = index;
@ -258,7 +269,7 @@ void Sticker::setDiceIndex(const QString &emoji, int index) {
void Sticker::setupLottie() {
_lottie = Stickers::LottiePlayerFromDocument(
_document,
_data,
_replacements,
Stickers::LottieSize::MessageHistory,
_size * cIntRetinaFactor(),

View File

@ -16,6 +16,7 @@ class Session;
namespace Data {
struct FileOrigin;
class DocumentMedia;
} // namespace Data
namespace Lottie {
@ -31,7 +32,7 @@ class Sticker final
public:
Sticker(
not_null<Element*> parent,
not_null<DocumentData*> document,
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements = nullptr);
~Sticker();
@ -43,7 +44,7 @@ public:
}
DocumentData *document() override {
return _document;
return _data;
}
void clearStickerLoopPlayed() override {
_lottieOncePlayed = false;
@ -71,13 +72,16 @@ private:
void paintPixmap(Painter &p, const QRect &r, bool selected);
[[nodiscard]] QPixmap paintedPixmap(bool selected) const;
void ensureDataMediaCreated() const;
void setupLottie();
void unloadLottie();
const not_null<Element*> _parent;
const not_null<DocumentData*> _document;
const not_null<DocumentData*> _data;
const Lottie::ColorReplacements *_replacements = nullptr;
std::unique_ptr<Lottie::SinglePlayer> _lottie;
mutable std::shared_ptr<Data::DocumentMedia> _dataMedia;
ClickHandlerPtr _link;
QSize _size;
QImage _lastDiceFrame;

View File

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_element.h"
#include "history/view/history_view_cursor_state.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_file_origin.h"
#include "base/qthelp_url.h"
#include "window/themes/window_theme.h"
@ -181,8 +182,11 @@ void ThemeDocument::validateThumbnail() const {
if (_thumbnailGood > 0) {
return;
}
const auto good = _data->goodThumbnail();
if (good) {
if (!_dataMedia) {
_dataMedia = _data->createMediaView();
_dataMedia->goodThumbnailWanted();
}
if (const auto good = _dataMedia->goodThumbnail()) {
if (good->loaded()) {
prepareThumbnailFrom(good, 1);
return;

View File

@ -9,9 +9,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_file.h"
namespace Data {
class DocumentMedia;
} // namespace Data
namespace HistoryView {
class ThemeDocument : public File {
class ThemeDocument final : public File {
public:
ThemeDocument(
not_null<Element*> parent,
@ -54,11 +58,12 @@ private:
void validateThumbnail() const;
void prepareThumbnailFrom(not_null<Image*> image, int good) const;
not_null<DocumentData*> _data;
const not_null<DocumentData*> _data;
int _pixw = 1;
int _pixh = 1;
mutable QPixmap _thumbnail;
mutable int _thumbnailGood = -1; // -1 inline, 0 thumbnail, 1 good
mutable std::shared_ptr<Data::DocumentMedia> _dataMedia;
// For wallpaper documents.
QColor _background;

View File

@ -10,7 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/streaming/media_streaming_instance.h"
#include "data/data_session.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_file_origin.h"
#include "main/main_session.h"
#include "storage/file_download.h" // Storage::kMaxFileInMemory.
#include "styles/style_widgets.h"
@ -192,48 +194,53 @@ void Document::waitingChange(bool waiting) {
}
void Document::validateGoodThumbnail() {
const auto good = _document->goodThumbnail();
if (_info.video.cover.isNull()
|| (good && good->loaded())
|| _document->uploading()) {
if (_info.video.cover.isNull() || _document->goodThumbnailChecked()) {
return;
}
auto image = [&] {
auto result = _info.video.cover;
if (_info.video.rotation != 0) {
auto transform = QTransform();
transform.rotate(_info.video.rotation);
result = result.transformed(transform);
const auto document = _document;
const auto information = _info.video;
const auto key = document->goodThumbnailCacheKey();
const auto guard = base::make_weak(&document->session());
document->owner().cache().get(key, [=](QByteArray value) {
if (!value.isEmpty()) {
return;
}
if (result.size() != _info.video.size) {
result = result.scaled(
_info.video.size,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
const auto image = [&] {
auto result = information.cover;
if (information.rotation != 0) {
auto transform = QTransform();
transform.rotate(information.rotation);
result = result.transformed(transform);
}
if (result.size() != information.size) {
result = result.scaled(
information.size,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
}
return result;
}();
auto bytes = QByteArray();
{
auto buffer = QBuffer(&bytes);
image.save(&buffer, "JPG", kGoodThumbnailQuality);
}
return result;
}();
auto bytes = QByteArray();
{
auto buffer = QBuffer(&bytes);
image.save(&buffer, "JPG", kGoodThumbnailQuality);
}
const auto length = bytes.size();
if (!length || length > Storage::kMaxFileInMemory) {
LOG(("App Error: Bad thumbnail data for saving to cache."));
} else if (_document->uploading()) {
_document->setGoodThumbnailOnUpload(
std::move(image),
std::move(bytes));
} else {
_document->owner().cache().putIfEmpty(
_document->goodThumbnailCacheKey(),
Storage::Cache::Database::TaggedValue(
std::move(bytes),
Data::kImageCacheTag));
_document->refreshGoodThumbnail();
}
const auto length = bytes.size();
if (!length || length > Storage::kMaxFileInMemory) {
LOG(("App Error: Bad thumbnail data for saving to cache."));
bytes = "(failed)";
}
crl::on_main(guard, [=] {
if (const auto active = document->activeMediaView()) {
active->setGoodThumbnail(image);
}
document->owner().cache().putIfEmpty(
document->goodThumbnailCacheKey(),
Storage::Cache::Database::TaggedValue(
base::duplicate(bytes),
Data::kImageCacheTag));
});
});
}
void Document::waitingCallback() {

View File

@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "data/data_file_origin.h"
#include "data/data_media_rotation.h"
#include "data/data_document_media.h"
#include "window/themes/window_theme_preview.h"
#include "window/window_peer_menu.h"
#include "window/window_session_controller.h"
@ -2177,7 +2178,8 @@ void OverlayWidget::startStreamingPlayer() {
void OverlayWidget::initStreamingThumbnail() {
Expects(_doc != nullptr);
const auto good = _doc->goodThumbnail();
const auto media = _doc->activeMediaView();
const auto good = media ? media->goodThumbnail() : nullptr;
const auto useGood = (good && good->loaded());
const auto thumb = _doc->thumbnail();
const auto useThumb = (thumb && thumb->loaded());

View File

@ -348,6 +348,7 @@ private:
PhotoData *_photo = nullptr;
DocumentData *_doc = nullptr;
std::shared_ptr<Data::DocumentMedia> _docMedia;
int _rotation = 0;
std::unique_ptr<SharedMedia> _sharedMedia;
std::optional<SharedMediaWithLastSlice> _sharedMediaData;

View File

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/view/media_view_playback_progress.h"
#include "media/audio/media_audio.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_file_origin.h"
#include "data/data_session.h"
#include "data/data_media_rotation.h"
@ -806,20 +807,20 @@ void PipPanel::updateDecorations() {
Pip::Pip(
not_null<Delegate*> delegate,
not_null<DocumentData*> document,
not_null<DocumentData*> data,
FullMsgId contextId,
std::shared_ptr<Streaming::Document> shared,
FnMut<void()> closeAndContinue,
FnMut<void()> destroy)
: _delegate(delegate)
, _document(document)
, _data(data)
, _contextId(contextId)
, _instance(std::move(shared), [=] { waitingAnimationCallback(); })
, _panel(
_delegate->pipParentWidget(),
[=](QPainter &p, const FrameRequest &request) { paint(p, request); })
, _playbackProgress(std::make_unique<PlaybackProgress>())
, _rotation(document->owner().mediaRotation().get(document))
, _rotation(data->owner().mediaRotation().get(data))
, _roundRect(ImageRoundRadius::Large, st::radialBg)
, _closeAndContinue(std::move(closeAndContinue))
, _destroy(std::move(destroy)) {
@ -835,9 +836,10 @@ void Pip::setupPanel() {
if (!_instance.info().video.size.isEmpty()) {
return _instance.info().video.size;
}
const auto good = _document->goodThumbnail();
const auto media = _data->activeMediaView();
const auto good = media ? media->goodThumbnail() : nullptr;
const auto useGood = (good && good->loaded());
const auto original = useGood ? good->size() : _document->dimensions;
const auto original = useGood ? good->size() : _data->dimensions;
return original.isEmpty() ? QSize(1, 1) : original;
}();
_panel.setAspectRatio(FlipSizeByRotation(size, _rotation));
@ -1369,11 +1371,12 @@ QImage Pip::videoFrame(const FrameRequest &request) const {
return _instance.frame(request);
}
const auto &cover = _instance.info().video.cover;
const auto good = _document->goodThumbnail();
const auto media = _data->activeMediaView();
const auto good = media ? media->goodThumbnail() : nullptr;
const auto useGood = (good && good->loaded());
const auto thumb = _document->thumbnail();
const auto thumb = _data->thumbnail();
const auto useThumb = (thumb && thumb->loaded());
const auto blurred = _document->thumbnailInline();
const auto blurred = _data->thumbnailInline();
const auto state = !cover.isNull()
? ThumbState::Cover
: useGood

View File

@ -14,6 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtCore/QPointer>
namespace Data {
class DocumentMedia;
} // namespace Data
namespace Ui {
class IconButton;
template <typename Widget>
@ -121,7 +125,7 @@ public:
Pip(
not_null<Delegate*> delegate,
not_null<DocumentData*> document,
not_null<DocumentData*> data,
FullMsgId contextId,
std::shared_ptr<Streaming::Document> shared,
FnMut<void()> closeAndContinue,
@ -197,12 +201,13 @@ private:
void seekFinish(float64 value);
const not_null<Delegate*> _delegate;
not_null<DocumentData*> _document;
not_null<DocumentData*> _data;
FullMsgId _contextId;
Streaming::Instance _instance;
PipPanel _panel;
QSize _size;
std::unique_ptr<PlaybackProgress> _playbackProgress;
std::shared_ptr<Data::DocumentMedia> _dataMedia;
bool _showPause = false;
bool _startPaused = false;

View File

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_media_types.h"
#include "data/data_peer.h"
#include "data/data_file_origin.h"
#include "data/data_document_media.h"
#include "styles/style_overview.h"
#include "styles/style_history.h"
#include "core/file_utilities.h"
@ -417,13 +418,10 @@ Video::Video(
, _duration(formatDurationText(_data->getDuration())) {
setDocumentLinks(_data);
_data->loadThumbnail(parent->fullId());
if (_data->hasThumbnail() && !_data->thumbnail()->loaded()) {
if (const auto good = _data->goodThumbnail()) {
good->load({});
}
}
}
Video::~Video() = default;
void Video::initDimensions() {
_maxw = 2 * st::overviewPhotoMinSize;
_minh = _maxw;
@ -436,12 +434,14 @@ int32 Video::resizeGetHeight(int32 width) {
}
void Video::paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) {
ensureDataMediaCreated();
const auto selected = (selection == FullSelection);
const auto blurred = _data->thumbnailInline();
const auto goodLoaded = _data->goodThumbnail()
&& _data->goodThumbnail()->loaded();
const auto thumbLoaded = _data->hasThumbnail()
&& _data->thumbnail()->loaded();
const auto goodLoaded = _dataMedia->goodThumbnail()
&& _dataMedia->goodThumbnail()->loaded();
bool loaded = _data->loaded(), displayLoading = _data->displayLoading();
if (displayLoading) {
@ -459,7 +459,7 @@ void Video::paint(Painter &p, const QRect &clip, TextSelection selection, const
|| (_pixBlurred && (thumbLoaded || goodLoaded)))) {
auto size = _width * cIntRetinaFactor();
auto img = goodLoaded
? _data->goodThumbnail()->original()
? _dataMedia->goodThumbnail()->original()
: thumbLoaded
? _data->thumbnail()->original()
: Images::prepareBlur(blurred->original());
@ -543,6 +543,14 @@ void Video::paint(Painter &p, const QRect &clip, TextSelection selection, const
paintCheckbox(p, { checkLeft, checkTop }, selected, context);
}
void Video::ensureDataMediaCreated() const {
if (_dataMedia) {
return;
}
_dataMedia = _data->createMediaView();
_dataMedia->goodThumbnailWanted();
}
float64 Video::dataProgress() const {
return _data->progress();
}

View File

@ -19,6 +19,7 @@ struct RoundCheckbox;
namespace Data {
class Media;
class DocumentMedia;
} // namespace Data
namespace Overview {
@ -220,11 +221,12 @@ private:
};
class Video : public RadialProgressItem {
class Video final : public RadialProgressItem {
public:
Video(
not_null<HistoryItem*> parent,
not_null<DocumentData*> video);
~Video();
void initDimensions() override;
int32 resizeGetHeight(int32 width) override;
@ -240,15 +242,17 @@ protected:
bool iconAnimated() const override;
private:
void ensureDataMediaCreated() const;
void updateStatusText();
not_null<DocumentData*> _data;
mutable std::shared_ptr<Data::DocumentMedia> _dataMedia;
StatusText _status;
QString _duration;
QPixmap _pix;
bool _pixBlurred = true;
void updateStatusText();
};
class Voice : public RadialProgressItem {

View File

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/localimageloader.h"
#include "storage/file_download.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_photo.h"
#include "data/data_session.h"
#include "main/main_session.h"
@ -193,9 +194,18 @@ void Uploader::upload(
std::move(file->thumb));
document->uploadingData = std::make_unique<Data::UploadState>(
document->size);
document->setGoodThumbnailOnUpload(
std::move(file->goodThumbnail),
std::move(file->goodThumbnailBytes));
if (!file->goodThumbnail.isNull()) {
if (const auto active = document->activeMediaView()) {
active->setGoodThumbnail(std::move(file->goodThumbnail));
}
}
if (!file->goodThumbnailBytes.isEmpty()) {
document->owner().cache().putIfEmpty(
document->goodThumbnailCacheKey(),
Storage::Cache::Database::TaggedValue(
std::move(file->goodThumbnailBytes),
Data::kImageCacheTag));
}
if (!file->content.isEmpty()) {
document->setDataAndCache(file->content);
if (file->type == SendMediaType::ThemeFile) {