Prepare lottie animations caching.

This commit is contained in:
John Preston 2019-06-26 12:01:04 +02:00
parent f20d9395d1
commit 35bc2cc2a5
17 changed files with 148 additions and 40 deletions

View File

@ -69,6 +69,8 @@ private:
Ui::Animations::Simple overAnimation; Ui::Animations::Simple overAnimation;
}; };
QSize boundingBoxSize() const;
void paintSticker(Painter &p, int index, QPoint position) const; void paintSticker(Painter &p, int index, QPoint position) const;
void setupLottie(int index); void setupLottie(int index);
@ -505,13 +507,20 @@ void StickerSetBox::Inner::paintEvent(QPaintEvent *e) {
} }
} }
QSize StickerSetBox::Inner::boundingBoxSize() const {
return QSize(
st::stickersSize.width() - st::buttonRadius * 2,
st::stickersSize.height() - st::buttonRadius * 2);
}
void StickerSetBox::Inner::setupLottie(int index) { void StickerSetBox::Inner::setupLottie(int index) {
auto &element = _elements[index]; auto &element = _elements[index];
const auto document = element.document; const auto document = element.document;
element.animated = document->data().isEmpty() element.animated = Stickers::LottieFromDocument(
? Lottie::FromFile(document->filepath()) document,
: Lottie::FromData(document->data()); Stickers::LottieSize::StickerSet,
boundingBoxSize() * cIntRetinaFactor());
const auto animation = element.animated.get(); const auto animation = element.animated.get();
animation->updates( animation->updates(
@ -550,16 +559,15 @@ void StickerSetBox::Inner::paintSticker(
if (h < 1) h = 1; if (h < 1) h = 1;
QPoint ppos = position + QPoint((st::stickersSize.width() - w) / 2, (st::stickersSize.height() - h) / 2); QPoint ppos = position + QPoint((st::stickersSize.width() - w) / 2, (st::stickersSize.height() - h) / 2);
if (element.animated && element.animated->ready()) { if (element.animated && element.animated->ready()) {
const auto size = QSize(w, h);
auto request = Lottie::FrameRequest(); auto request = Lottie::FrameRequest();
request.resize = size * cIntRetinaFactor(); request.box = boundingBoxSize() * cIntRetinaFactor();
const auto paused = _controller->isGifPausedAtLeastFor( const auto paused = _controller->isGifPausedAtLeastFor(
Window::GifPauseReason::Layer); Window::GifPauseReason::Layer);
if (!paused) { if (!paused) {
element.animated->markFrameShown(); element.animated->markFrameShown();
} }
p.drawImage( p.drawImage(
QRect(ppos, size), QRect(ppos, QSize(w, h)),
element.animated->frame(request)); element.animated->frame(request));
} else if (const auto image = document->getStickerSmall()) { } else if (const auto image = document->getStickerSmall()) {
p.drawPixmapLeft( p.drawPixmapLeft(
@ -581,7 +589,7 @@ bool StickerSetBox::Inner::notInstalled() const {
if ((it == Auth().data().stickerSets().cend()) if ((it == Auth().data().stickerSets().cend())
|| !(it->flags & MTPDstickerSet::Flag::f_installed_date) || !(it->flags & MTPDstickerSet::Flag::f_installed_date)
|| (it->flags & MTPDstickerSet::Flag::f_archived)) { || (it->flags & MTPDstickerSet::Flag::f_archived)) {
return _pack.size() > 0; return !_pack.empty();
} }
return false; return false;
} }

View File

@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwindow.h" #include "mainwindow.h"
#include "ui/toast/toast.h" #include "ui/toast/toast.h"
#include "ui/emoji_config.h" #include "ui/emoji_config.h"
#include "lottie/lottie_animation.h"
#include "styles/style_chat_helpers.h" #include "styles/style_chat_helpers.h"
namespace Stickers { namespace Stickers {
@ -1086,4 +1087,24 @@ RecentStickerPack &GetRecentPack() {
return cRefRecentStickers(); return cRefRecentStickers();
} }
std::unique_ptr<Lottie::Animation> LottieFromDocument(
not_null<DocumentData*> document,
LottieSize sizeTag,
QSize box) {
const auto data = document->data();
const auto filepath = document->filepath();
if (const auto key = document->bigFileBaseCacheKey()) {
return Lottie::FromCached(
&document->session().data().cacheBigFile(),
Storage::Cache::Key{ key->high, key->low + int(sizeTag) },
data,
filepath,
box);
} else if (!data.isEmpty()) {
return Lottie::FromData(data);
} else {
return Lottie::FromFile(filepath);
}
}
} // namespace Stickers } // namespace Stickers

View File

@ -9,6 +9,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/sender.h" #include "mtproto/sender.h"
class DocumentData;
namespace Lottie {
class Animation;
} // namespace Lottie
namespace Stickers { namespace Stickers {
constexpr auto DefaultSetId = 0; // for backward compatibility constexpr auto DefaultSetId = 0; // for backward compatibility
@ -103,4 +109,17 @@ QString GetSetTitle(const MTPDstickerSet &s);
RecentStickerPack &GetRecentPack(); RecentStickerPack &GetRecentPack();
enum class LottieSize : uchar {
MessageHistory,
StickerSet,
StickersPanel,
StickersColumn,
MediaPreview,
};
std::unique_ptr<Lottie::Animation> LottieFromDocument(
not_null<DocumentData*> document,
LottieSize sizeTag,
QSize box);
} // namespace Stickers } // namespace Stickers

View File

@ -1380,6 +1380,12 @@ void StickersListWidget::setupLottie(Set &set, int section, int index) {
}, lifetime()); }, lifetime());
} }
QSize StickersListWidget::boundingBoxSize() const {
return QSize(
_singleSize.width() - st::buttonRadius * 2,
_singleSize.height() - st::buttonRadius * 2);
}
void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section, int index, bool selected, bool deleteSelected) { void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section, int index, bool selected, bool deleteSelected) {
auto &sticker = set.stickers[index]; auto &sticker = set.stickers[index];
const auto document = sticker.document; const auto document = sticker.document;
@ -1410,16 +1416,15 @@ void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section,
auto h = qMax(qRound(coef * document->dimensions.height()), 1); auto h = qMax(qRound(coef * document->dimensions.height()), 1);
auto ppos = pos + QPoint((_singleSize.width() - w) / 2, (_singleSize.height() - h) / 2); auto ppos = pos + QPoint((_singleSize.width() - w) / 2, (_singleSize.height() - h) / 2);
if (sticker.animated && sticker.animated->ready()) { if (sticker.animated && sticker.animated->ready()) {
const auto size = QSize(w, h);
auto request = Lottie::FrameRequest(); auto request = Lottie::FrameRequest();
request.resize = size * cIntRetinaFactor(); request.box = boundingBoxSize() * cIntRetinaFactor();
const auto paused = controller()->isGifPausedAtLeastFor( const auto paused = controller()->isGifPausedAtLeastFor(
Window::GifPauseReason::SavedGifs); Window::GifPauseReason::SavedGifs);
if (!paused) { if (!paused) {
sticker.animated->markFrameShown(); sticker.animated->markFrameShown();
} }
p.drawImage( p.drawImage(
QRect(ppos, size), QRect(ppos, QSize(w, h)),
sticker.animated->frame(request)); sticker.animated->frame(request));
} else if (const auto image = document->getStickerSmall()) { } else if (const auto image = document->getStickerSmall()) {
if (image->loaded()) { if (image->loaded()) {

View File

@ -165,6 +165,8 @@ private:
static std::vector<Sticker> PrepareStickers(const Stickers::Pack &pack); static std::vector<Sticker> PrepareStickers(const Stickers::Pack &pack);
QSize boundingBoxSize() const;
template <typename Callback> template <typename Callback>
bool enumerateSections(Callback callback) const; bool enumerateSections(Callback callback) const;
SectionInfo sectionInfo(int section) const; SectionInfo sectionInfo(int section) const;

View File

@ -668,6 +668,21 @@ void DocumentData::setGoodThumbnailOnUpload(
QString(), std::move(bytes), "JPG", std::move(image))); QString(), std::move(bytes), "JPG", std::move(image)));
} }
auto DocumentData::bigFileBaseCacheKey() const
-> std::optional<Storage::Cache::Key> {
if (hasRemoteLocation()) {
return StorageFileLocation(
_dc,
session().userId(),
MTP_inputDocumentFileLocation(
MTP_long(id),
MTP_long(_access),
MTP_bytes(_fileReference),
MTP_string(QString()))).bigFileBaseCacheKey();
}
return std::nullopt;
}
bool DocumentData::saveToCache() const { bool DocumentData::saveToCache() const {
return (type == StickerDocument && size < Storage::kMaxStickerInMemory) return (type == StickerDocument && size < Storage::kMaxStickerInMemory)
|| (isAnimation() && size < Storage::kMaxAnimationInMemory) || (isAnimation() && size < Storage::kMaxAnimationInMemory)

View File

@ -181,6 +181,9 @@ public:
void refreshGoodThumbnail(); void refreshGoodThumbnail();
void replaceGoodThumbnail(std::unique_ptr<Images::Source> &&source); void replaceGoodThumbnail(std::unique_ptr<Images::Source> &&source);
[[nodiscard]] auto bigFileBaseCacheKey() const
-> std::optional<Storage::Cache::Key>;
void setRemoteLocation( void setRemoteLocation(
int32 dc, int32 dc,
uint64 access, uint64 access,

View File

@ -1101,7 +1101,7 @@ std::shared_ptr<::Media::Streaming::Reader> Session::documentStreamedReader(
return nullptr; return nullptr;
} }
auto result = std::make_shared<::Media::Streaming::Reader>( auto result = std::make_shared<::Media::Streaming::Reader>(
this, &cacheBigFile(),
std::move(loader)); std::move(loader));
if (!PruneDestroyedAndSet(_streamedReaders, document, result)) { if (!PruneDestroyedAndSet(_streamedReaders, document, result)) {
_streamedReaders.emplace_or_assign(document, result); _streamedReaders.emplace_or_assign(document, result);

View File

@ -184,7 +184,8 @@ void HistorySticker::draw(Painter &p, const QRect &r, TextSelection selection, c
pixmap); pixmap);
} else if (lottieReady) { } else if (lottieReady) {
auto request = Lottie::FrameRequest(); auto request = Lottie::FrameRequest();
request.resize = QSize(_pixw, _pixh) * cIntRetinaFactor(); request.box = QSize(st::maxStickerSize, st::maxStickerSize)
* cIntRetinaFactor();
if (selected) { if (selected) {
request.colored = st::msgStickerOverlay->c; request.colored = st::msgStickerOverlay->c;
} }

View File

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lottie/lottie_animation.h" #include "lottie/lottie_animation.h"
#include "lottie/lottie_frame_renderer.h" #include "lottie/lottie_frame_renderer.h"
#include "storage/cache/storage_cache_database.h"
#include "base/algorithm.h" #include "base/algorithm.h"
#include "zlib.h" #include "zlib.h"
#include "logs.h" #include "logs.h"
@ -70,6 +71,17 @@ std::unique_ptr<Animation> FromData(const QByteArray &data) {
return std::make_unique<Animation>(base::duplicate(data)); return std::make_unique<Animation>(base::duplicate(data));
} }
std::unique_ptr<Animation> FromCached(
not_null<Storage::Cache::Database*> cache,
Storage::Cache::Key key,
const QByteArray &data,
const QString &filepath,
QSize box) {
return data.isEmpty()
? Lottie::FromFile(filepath)
: Lottie::FromData(data);
}
auto Init(QByteArray &&content) auto Init(QByteArray &&content)
-> base::variant<std::unique_ptr<SharedState>, Error> { -> base::variant<std::unique_ptr<SharedState>, Error> {
if (content.size() > kMaxFileSize) { if (content.size() > kMaxFileSize) {

View File

@ -23,6 +23,13 @@ class QImage;
class QString; class QString;
class QByteArray; class QByteArray;
namespace Storage {
namespace Cache {
class Database;
struct Key;
} // namespace Cache
} // namespace Storage
namespace Lottie { namespace Lottie {
constexpr auto kMaxFileSize = 1024 * 1024; constexpr auto kMaxFileSize = 1024 * 1024;
@ -33,6 +40,12 @@ class FrameRenderer;
std::unique_ptr<Animation> FromFile(const QString &path); std::unique_ptr<Animation> FromFile(const QString &path);
std::unique_ptr<Animation> FromData(const QByteArray &data); std::unique_ptr<Animation> FromData(const QByteArray &data);
std::unique_ptr<Animation> FromCached(
not_null<Storage::Cache::Database*> cache,
Storage::Cache::Key key,
const QByteArray &data,
const QString &filepath,
QSize box);
QImage ReadThumbnail(QByteArray &&content); QImage ReadThumbnail(QByteArray &&content);

View File

@ -47,18 +47,26 @@ enum class Error {
}; };
struct FrameRequest { struct FrameRequest {
QSize resize; QSize box;
std::optional<QColor> colored; std::optional<QColor> colored;
bool empty() const { [[nodiscard]] bool empty() const {
return resize.isEmpty(); return box.isEmpty();
}
[[nodiscard]] QSize size(const QSize &original) const {
Expects(!box.isEmpty());
const auto result = original.scaled(box, Qt::KeepAspectRatio);
return QSize(
std::max(result.width(), 1),
std::max(result.height(), 1));
} }
bool operator==(const FrameRequest &other) const { [[nodiscard]] bool operator==(const FrameRequest &other) const {
return (resize == other.resize) return (box == other.box)
&& (colored == other.colored); && (colored == other.colored);
} }
bool operator!=(const FrameRequest &other) const { [[nodiscard]] bool operator!=(const FrameRequest &other) const {
return !(*this == other); return !(*this == other);
} }
}; };

View File

@ -77,22 +77,25 @@ private:
[[nodiscard]] bool GoodForRequest( [[nodiscard]] bool GoodForRequest(
const QImage &image, const QImage &image,
const FrameRequest &request) { const FrameRequest &request) {
if (request.resize.isEmpty()) { if (request.box.isEmpty()) {
return true; return true;
} else if (request.colored.has_value()) { } else if (request.colored.has_value()) {
return false; return false;
} }
return (request.resize == image.size()); const auto size = image.size();
return (request.box.width() == size.width())
|| (request.box.height() == size.height());
} }
[[nodiscard]] QImage PrepareByRequest( [[nodiscard]] QImage PrepareByRequest(
const QImage &original, const QImage &original,
const FrameRequest &request, const FrameRequest &request,
QImage storage) { QImage storage) {
Expects(!request.resize.isEmpty()); Expects(!request.box.isEmpty());
if (!GoodStorageForFrame(storage, request.resize)) { const auto size = request.size(original.size());
storage = CreateFrameStorage(request.resize); if (!GoodStorageForFrame(storage, size)) {
storage = CreateFrameStorage(size);
} }
storage.fill(Qt::transparent); storage.fill(Qt::transparent);
@ -101,7 +104,7 @@ private:
p.setRenderHint(QPainter::Antialiasing); p.setRenderHint(QPainter::Antialiasing);
p.setRenderHint(QPainter::SmoothPixmapTransform); p.setRenderHint(QPainter::SmoothPixmapTransform);
p.setRenderHint(QPainter::HighQualityAntialiasing); p.setRenderHint(QPainter::HighQualityAntialiasing);
p.drawImage(QRect(QPoint(), request.resize), original); p.drawImage(QRect(QPoint(), size), original);
} }
if (request.colored.has_value()) { if (request.colored.has_value()) {
storage = Images::prepareColored(*request.colored, std::move(storage)); storage = Images::prepareColored(*request.colored, std::move(storage));
@ -211,7 +214,7 @@ void SharedState::renderFrame(
return; return;
} }
const auto size = request.resize.isEmpty() ? _size : request.resize; const auto size = request.box.isEmpty() ? _size : request.size(_size);
if (!GoodStorageForFrame(image, size)) { if (!GoodStorageForFrame(image, size)) {
image = CreateFrameStorage(size); image = CreateFrameStorage(size);
} }

View File

@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/streaming/media_streaming_common.h" #include "media/streaming/media_streaming_common.h"
#include "media/streaming/media_streaming_loader.h" #include "media/streaming/media_streaming_loader.h"
#include "storage/cache/storage_cache_database.h" #include "storage/cache/storage_cache_database.h"
#include "data/data_session.h"
namespace Media { namespace Media {
namespace Streaming { namespace Streaming {
@ -846,9 +845,9 @@ Reader::SerializedSlice Reader::Slices::unloadToCache() {
} }
Reader::Reader( Reader::Reader(
not_null<Data::Session*> owner, not_null<Storage::Cache::Database*> cache,
std::unique_ptr<Loader> loader) std::unique_ptr<Loader> loader)
: _owner(owner) : _cache(cache)
, _loader(std::move(loader)) , _loader(std::move(loader))
, _cacheHelper(InitCacheHelper(_loader->baseCacheKey())) , _cacheHelper(InitCacheHelper(_loader->baseCacheKey()))
, _slices(_loader->size(), _cacheHelper != nullptr) { , _slices(_loader->size(), _cacheHelper != nullptr) {
@ -1140,7 +1139,7 @@ void Reader::readFromCache(int sliceNumber) {
for (auto i = 0; i != count; ++i) { for (auto i = 0; i != count; ++i) {
keys.push_back(_cacheHelper->key(i + 1)); keys.push_back(_cacheHelper->key(i + 1));
} }
_owner->cacheBigFile().getWithSizes(key, std::move(keys), ready); _cache->getWithSizes(key, std::move(keys), ready);
} }
bool Reader::readFromCacheForDownloader(int sliceNumber) { bool Reader::readFromCacheForDownloader(int sliceNumber) {
@ -1158,9 +1157,7 @@ void Reader::putToCache(SerializedSlice &&slice) {
Expects(_cacheHelper != nullptr); Expects(_cacheHelper != nullptr);
Expects(slice.number >= 0); Expects(slice.number >= 0);
_owner->cacheBigFile().put( _cache->put(_cacheHelper->key(slice.number), std::move(slice.data));
_cacheHelper->key(slice.number),
std::move(slice.data));
} }
int Reader::size() const { int Reader::size() const {
@ -1359,7 +1356,7 @@ void Reader::finalizeCache() {
putToCache(std::move(toCache)); putToCache(std::move(toCache));
toCache = _slices.unloadToCache(); toCache = _slices.unloadToCache();
} }
_owner->cacheBigFile().sync(); _cache->sync();
} }
Reader::~Reader() { Reader::~Reader() {

View File

@ -19,13 +19,10 @@ class StreamedFileDownloader;
namespace Storage { namespace Storage {
namespace Cache { namespace Cache {
struct Key; struct Key;
class Database;
} // namespace Cache } // namespace Cache
} // namespace Storage } // namespace Storage
namespace Data {
class Session;
} // namespace Data
namespace Media { namespace Media {
namespace Streaming { namespace Streaming {
@ -36,7 +33,9 @@ enum class Error;
class Reader final : public base::has_weak_ptr { class Reader final : public base::has_weak_ptr {
public: public:
// Main thread. // Main thread.
Reader(not_null<Data::Session*> owner, std::unique_ptr<Loader> loader); Reader(
not_null<Storage::Cache::Database*> cache,
std::unique_ptr<Loader> loader);
// Any thread. // Any thread.
[[nodiscard]] int size() const; [[nodiscard]] int size() const;
@ -222,7 +221,7 @@ private:
static std::shared_ptr<CacheHelper> InitCacheHelper( static std::shared_ptr<CacheHelper> InitCacheHelper(
std::optional<Storage::Cache::Key> baseKey); std::optional<Storage::Cache::Key> baseKey);
const not_null<Data::Session*> _owner; const not_null<Storage::Cache::Database*> _cache;
const std::unique_ptr<Loader> _loader; const std::unique_ptr<Loader> _loader;
const std::shared_ptr<CacheHelper> _cacheHelper; const std::shared_ptr<CacheHelper> _cacheHelper;

View File

@ -880,7 +880,7 @@ void MediaPreviewWidget::paintEvent(QPaintEvent *e) {
return QImage(); return QImage();
} }
auto request = Lottie::FrameRequest(); auto request = Lottie::FrameRequest();
request.resize = currentDimensions() * cIntRetinaFactor(); request.box = currentDimensions() * cIntRetinaFactor();
_lottie->markFrameShown(); _lottie->markFrameShown();
return _lottie->frame(request); return _lottie->frame(request);
}(); }();

View File

@ -29,11 +29,13 @@
'crl.gyp:crl', 'crl.gyp:crl',
'lib_base.gyp:lib_base', 'lib_base.gyp:lib_base',
'lib_rlottie.gyp:lib_rlottie', 'lib_rlottie.gyp:lib_rlottie',
'lib_storage.gyp:lib_storage',
], ],
'export_dependent_settings': [ 'export_dependent_settings': [
'crl.gyp:crl', 'crl.gyp:crl',
'lib_base.gyp:lib_base', 'lib_base.gyp:lib_base',
'lib_rlottie.gyp:lib_rlottie', 'lib_rlottie.gyp:lib_rlottie',
'lib_storage.gyp:lib_storage',
], ],
'defines': [ 'defines': [
'LOT_BUILD', 'LOT_BUILD',