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

View File

@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwindow.h"
#include "ui/toast/toast.h"
#include "ui/emoji_config.h"
#include "lottie/lottie_animation.h"
#include "styles/style_chat_helpers.h"
namespace Stickers {
@ -1086,4 +1087,24 @@ RecentStickerPack &GetRecentPack() {
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

View File

@ -9,6 +9,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/sender.h"
class DocumentData;
namespace Lottie {
class Animation;
} // namespace Lottie
namespace Stickers {
constexpr auto DefaultSetId = 0; // for backward compatibility
@ -103,4 +109,17 @@ QString GetSetTitle(const MTPDstickerSet &s);
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

View File

@ -1380,6 +1380,12 @@ void StickersListWidget::setupLottie(Set &set, int section, int index) {
}, 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) {
auto &sticker = set.stickers[index];
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 ppos = pos + QPoint((_singleSize.width() - w) / 2, (_singleSize.height() - h) / 2);
if (sticker.animated && sticker.animated->ready()) {
const auto size = QSize(w, h);
auto request = Lottie::FrameRequest();
request.resize = size * cIntRetinaFactor();
request.box = boundingBoxSize() * cIntRetinaFactor();
const auto paused = controller()->isGifPausedAtLeastFor(
Window::GifPauseReason::SavedGifs);
if (!paused) {
sticker.animated->markFrameShown();
}
p.drawImage(
QRect(ppos, size),
QRect(ppos, QSize(w, h)),
sticker.animated->frame(request));
} else if (const auto image = document->getStickerSmall()) {
if (image->loaded()) {

View File

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

View File

@ -668,6 +668,21 @@ void DocumentData::setGoodThumbnailOnUpload(
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 {
return (type == StickerDocument && size < Storage::kMaxStickerInMemory)
|| (isAnimation() && size < Storage::kMaxAnimationInMemory)

View File

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

View File

@ -1101,7 +1101,7 @@ std::shared_ptr<::Media::Streaming::Reader> Session::documentStreamedReader(
return nullptr;
}
auto result = std::make_shared<::Media::Streaming::Reader>(
this,
&cacheBigFile(),
std::move(loader));
if (!PruneDestroyedAndSet(_streamedReaders, 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);
} else if (lottieReady) {
auto request = Lottie::FrameRequest();
request.resize = QSize(_pixw, _pixh) * cIntRetinaFactor();
request.box = QSize(st::maxStickerSize, st::maxStickerSize)
* cIntRetinaFactor();
if (selected) {
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_frame_renderer.h"
#include "storage/cache/storage_cache_database.h"
#include "base/algorithm.h"
#include "zlib.h"
#include "logs.h"
@ -70,6 +71,17 @@ std::unique_ptr<Animation> FromData(const QByteArray &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)
-> base::variant<std::unique_ptr<SharedState>, Error> {
if (content.size() > kMaxFileSize) {

View File

@ -23,6 +23,13 @@ class QImage;
class QString;
class QByteArray;
namespace Storage {
namespace Cache {
class Database;
struct Key;
} // namespace Cache
} // namespace Storage
namespace Lottie {
constexpr auto kMaxFileSize = 1024 * 1024;
@ -33,6 +40,12 @@ class FrameRenderer;
std::unique_ptr<Animation> FromFile(const QString &path);
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);

View File

@ -47,18 +47,26 @@ enum class Error {
};
struct FrameRequest {
QSize resize;
QSize box;
std::optional<QColor> colored;
bool empty() const {
return resize.isEmpty();
[[nodiscard]] bool empty() const {
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 {
return (resize == other.resize)
[[nodiscard]] bool operator==(const FrameRequest &other) const {
return (box == other.box)
&& (colored == other.colored);
}
bool operator!=(const FrameRequest &other) const {
[[nodiscard]] bool operator!=(const FrameRequest &other) const {
return !(*this == other);
}
};

View File

@ -77,22 +77,25 @@ private:
[[nodiscard]] bool GoodForRequest(
const QImage &image,
const FrameRequest &request) {
if (request.resize.isEmpty()) {
if (request.box.isEmpty()) {
return true;
} else if (request.colored.has_value()) {
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(
const QImage &original,
const FrameRequest &request,
QImage storage) {
Expects(!request.resize.isEmpty());
Expects(!request.box.isEmpty());
if (!GoodStorageForFrame(storage, request.resize)) {
storage = CreateFrameStorage(request.resize);
const auto size = request.size(original.size());
if (!GoodStorageForFrame(storage, size)) {
storage = CreateFrameStorage(size);
}
storage.fill(Qt::transparent);
@ -101,7 +104,7 @@ private:
p.setRenderHint(QPainter::Antialiasing);
p.setRenderHint(QPainter::SmoothPixmapTransform);
p.setRenderHint(QPainter::HighQualityAntialiasing);
p.drawImage(QRect(QPoint(), request.resize), original);
p.drawImage(QRect(QPoint(), size), original);
}
if (request.colored.has_value()) {
storage = Images::prepareColored(*request.colored, std::move(storage));
@ -211,7 +214,7 @@ void SharedState::renderFrame(
return;
}
const auto size = request.resize.isEmpty() ? _size : request.resize;
const auto size = request.box.isEmpty() ? _size : request.size(_size);
if (!GoodStorageForFrame(image, 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_loader.h"
#include "storage/cache/storage_cache_database.h"
#include "data/data_session.h"
namespace Media {
namespace Streaming {
@ -846,9 +845,9 @@ Reader::SerializedSlice Reader::Slices::unloadToCache() {
}
Reader::Reader(
not_null<Data::Session*> owner,
not_null<Storage::Cache::Database*> cache,
std::unique_ptr<Loader> loader)
: _owner(owner)
: _cache(cache)
, _loader(std::move(loader))
, _cacheHelper(InitCacheHelper(_loader->baseCacheKey()))
, _slices(_loader->size(), _cacheHelper != nullptr) {
@ -1140,7 +1139,7 @@ void Reader::readFromCache(int sliceNumber) {
for (auto i = 0; i != count; ++i) {
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) {
@ -1158,9 +1157,7 @@ void Reader::putToCache(SerializedSlice &&slice) {
Expects(_cacheHelper != nullptr);
Expects(slice.number >= 0);
_owner->cacheBigFile().put(
_cacheHelper->key(slice.number),
std::move(slice.data));
_cache->put(_cacheHelper->key(slice.number), std::move(slice.data));
}
int Reader::size() const {
@ -1359,7 +1356,7 @@ void Reader::finalizeCache() {
putToCache(std::move(toCache));
toCache = _slices.unloadToCache();
}
_owner->cacheBigFile().sync();
_cache->sync();
}
Reader::~Reader() {

View File

@ -19,13 +19,10 @@ class StreamedFileDownloader;
namespace Storage {
namespace Cache {
struct Key;
class Database;
} // namespace Cache
} // namespace Storage
namespace Data {
class Session;
} // namespace Data
namespace Media {
namespace Streaming {
@ -36,7 +33,9 @@ enum class Error;
class Reader final : public base::has_weak_ptr {
public:
// 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.
[[nodiscard]] int size() const;
@ -222,7 +221,7 @@ private:
static std::shared_ptr<CacheHelper> InitCacheHelper(
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::shared_ptr<CacheHelper> _cacheHelper;

View File

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

View File

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