Limit to 30 FPS in small stickers.

This commit is contained in:
John Preston 2019-07-05 19:15:25 +02:00
parent 72a9d61b97
commit 198de85ce5
17 changed files with 91 additions and 35 deletions

View File

@ -485,6 +485,7 @@ void StickerSetBox::Inner::showPreview() {
not_null<Lottie::MultiPlayer*> StickerSetBox::Inner::getLottiePlayer() {
if (!_lottiePlayer) {
_lottiePlayer = std::make_unique<Lottie::MultiPlayer>(
Lottie::Quality::Default,
Lottie::MakeFrameRenderer());
_lottiePlayer->updates(
) | rpl::start_with_next([=] {

View File

@ -971,6 +971,7 @@ void FieldAutocompleteInner::setupLottie(StickerSuggestion &suggestion) {
st::stickerPanSize.width() - st::buttonRadius * 2,
st::stickerPanSize.height() - st::buttonRadius * 2
) * cIntRetinaFactor(),
Lottie::Quality::Default,
getLottieRenderer());
suggestion.animated->updates(

View File

@ -1170,10 +1170,11 @@ std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
not_null<DocumentData*> document,
LottieSize sizeTag,
QSize box,
Lottie::Quality quality,
std::shared_ptr<Lottie::FrameRenderer> renderer) {
const auto method = [&](auto &&...args) {
return std::make_unique<Lottie::SinglePlayer>(
std::forward<decltype(args)>(args)..., std::move(renderer));
std::forward<decltype(args)>(args)..., quality, std::move(renderer));
};
return LottieFromDocument(method, document, sizeTag, box);
}

View File

@ -24,6 +24,7 @@ class SinglePlayer;
class MultiPlayer;
class FrameRenderer;
class Animation;
enum class Quality : char;
} // namespace Lottie
namespace Stickers {
@ -133,6 +134,7 @@ enum class LottieSize : uchar {
not_null<DocumentData*> document,
LottieSize sizeTag,
QSize box,
Lottie::Quality quality = Lottie::Quality(),
std::shared_ptr<Lottie::FrameRenderer> renderer = nullptr);
[[nodiscard]] not_null<Lottie::Animation*> LottieAnimationFromDocument(
not_null<Lottie::MultiPlayer*> player,

View File

@ -1593,6 +1593,7 @@ void StickersListWidget::ensureLottiePlayer(Set &set) {
const auto [i, ok] = _lottieData.emplace(
set.id,
LottieSet{ std::make_unique<Lottie::MultiPlayer>(
Lottie::Quality::Default,
getLottieRenderer()) });
Assert(ok);
const auto raw = set.lottiePlayer = i->second.player.get();

View File

@ -100,7 +100,8 @@ void HistorySticker::setupLottie() {
_lottie = Stickers::LottiePlayerFromDocument(
_data,
Stickers::LottieSize::MessageHistory,
QSize(st::maxStickerSize, st::maxStickerSize) * cIntRetinaFactor());
QSize(st::maxStickerSize, st::maxStickerSize) * cIntRetinaFactor(),
Lottie::Quality::High);
_parent->data()->history()->owner().registerHeavyViewPart(_parent);
_lottie->updates(

View File

@ -78,7 +78,8 @@ details::InitData CheckSharedState(std::unique_ptr<SharedState> state) {
details::InitData Init(
const QByteArray &content,
const FrameRequest &request) {
const FrameRequest &request,
Quality quality) {
if (const auto error = ContentError(content)) {
return *error;
}
@ -86,7 +87,8 @@ details::InitData Init(
return animation
? CheckSharedState(std::make_unique<SharedState>(
std::move(animation),
request))
request,
quality))
: Error::ParseFailed;
}
@ -94,7 +96,8 @@ details::InitData Init(
const QByteArray &content,
FnMut<void(QByteArray &&cached)> put,
const QByteArray &cached,
const FrameRequest &request) {
const FrameRequest &request,
Quality quality) {
if (const auto error = ContentError(content)) {
return *error;
}
@ -107,7 +110,8 @@ details::InitData Init(
content,
std::move(animation),
std::move(cache),
request))
request,
quality))
: Error::ParseFailed;
}
@ -134,7 +138,7 @@ std::shared_ptr<FrameRenderer> MakeFrameRenderer() {
}
QImage ReadThumbnail(const QByteArray &content) {
return Init(content, FrameRequest()).match([](
return Init(content, FrameRequest(), Quality::High).match([](
const std::unique_ptr<SharedState> &state) {
return state->frameForPaint()->original;
}, [](Error) {
@ -145,11 +149,12 @@ QImage ReadThumbnail(const QByteArray &content) {
Animation::Animation(
not_null<Player*> player,
const QByteArray &content,
const FrameRequest &request)
const FrameRequest &request,
Quality quality)
: _player(player) {
const auto weak = base::make_weak(this);
crl::async([=] {
crl::on_main(weak, [=, data = Init(content, request)]() mutable {
crl::on_main(weak, [=, data = Init(content, request, quality)]() mutable {
initDone(std::move(data));
});
});
@ -160,12 +165,13 @@ Animation::Animation(
FnMut<void(FnMut<void(QByteArray &&cached)>)> get, // Main thread.
FnMut<void(QByteArray &&cached)> put, // Unknown thread.
const QByteArray &content,
const FrameRequest &request)
const FrameRequest &request,
Quality quality)
: _player(player) {
const auto weak = base::make_weak(this);
get([=, put = std::move(put)](QByteArray &&cached) mutable {
crl::async([=, put = std::move(put)]() mutable {
auto result = Init(content, std::move(put), cached, request);
auto result = Init(content, std::move(put), cached, request, quality);
crl::on_main(weak, [=, data = std::move(result)]() mutable {
initDone(std::move(data));
});

View File

@ -42,13 +42,15 @@ public:
Animation(
not_null<Player*> player,
const QByteArray &content,
const FrameRequest &request);
const FrameRequest &request,
Quality quality);
Animation(
not_null<Player*> player,
FnMut<void(FnMut<void(QByteArray &&cached)>)> get, // Main thread.
FnMut<void(QByteArray &&cached)> put, // Unknown thread.
const QByteArray &content,
const FrameRequest &request);
const FrameRequest &request,
Quality quality);
[[nodiscard]] bool ready() const;
[[nodiscard]] QImage frame() const;

View File

@ -580,11 +580,9 @@ void Cache::writeHeader() {
void Cache::updateFramesReadyCount() {
Expects(_data.size() >= headerSize());
const auto serialized = qint32(_framesReady);
const auto offset = headerSize() - sizeof(qint32);
bytes::copy(
bytes::make_detached_span(_data).subspan(offset),
bytes::object_as_span(&serialized));
QDataStream stream(&_data, QIODevice::ReadWrite);
stream.device()->seek(headerSize() - sizeof(qint32));
stream << qint32(_framesReady);
}
void Cache::prepareBuffers() {

View File

@ -50,6 +50,11 @@ struct FrameRequest {
}
};
enum class Quality : char {
Default,
High,
};
QByteArray ReadContent(const QByteArray &data, const QString &filepath);
} // namespace Lottie

View File

@ -40,6 +40,22 @@ QImage CreateFrameStorage(QSize size) {
return QImage(size, kImageFormat);
}
int GetLottieFrameRate(not_null<rlottie::Animation*> animation, Quality quality) {
const auto rate = int(qRound(animation->frameRate()));
return (quality == Quality::Default && rate == 60) ? (rate / 2) : rate;
}
int GetLottieFramesCount(not_null<rlottie::Animation*> animation, Quality quality) {
const auto rate = int(qRound(animation->frameRate()));
const auto count = int(animation->totalFrame());
return (quality == Quality::Default && rate == 60) ? (count / 2) : count;
}
int GetLottieFrameIndex(not_null<rlottie::Animation*> animation, Quality quality, int index) {
const auto rate = int(qRound(animation->frameRate()));
return (quality == Quality::Default && rate == 60) ? (index * 2) : index;
}
} // namespace
class FrameRendererObject final {
@ -196,8 +212,10 @@ void FrameRendererObject::queueGenerateFrames() {
SharedState::SharedState(
std::unique_ptr<rlottie::Animation> animation,
const FrameRequest &request)
: _animation(std::move(animation)) {
const FrameRequest &request,
Quality quality)
: _animation(std::move(animation))
, _quality(quality) {
construct(request);
}
@ -205,9 +223,11 @@ SharedState::SharedState(
const QByteArray &content,
std::unique_ptr<rlottie::Animation> animation,
std::unique_ptr<Cache> cache,
const FrameRequest &request)
const FrameRequest &request,
Quality quality)
: _content(content)
, _animation(std::move(animation))
, _quality(quality)
, _cache(std::move(cache)) {
construct(request);
}
@ -241,16 +261,16 @@ void SharedState::calculateProperties() {
height = _cache->originalSize().height();
}
const auto rate = _animation
? _animation->frameRate()
? GetLottieFrameRate(_animation.get(), _quality)
: _cache->frameRate();
const auto count = _animation
? _animation->totalFrame()
? GetLottieFramesCount(_animation.get(), _quality)
: _cache->framesCount();
_size = QSize(
(width > 0 && width < kMaxSize) ? int(width) : 0,
(height > 0 && height < kMaxSize) ? int(height) : 0);
_frameRate = (rate >= 1. && rate <= kMaxFrameRate) ? int(rate) : 0;
_frameRate = (rate > 0 && rate <= kMaxFrameRate) ? int(rate) : 0;
_framesCount = (count > 0 && count <= kMaxFramesCount) ? int(count) : 0;
}
@ -282,7 +302,9 @@ void SharedState::renderFrame(
image.width(),
image.height(),
image.bytesPerLine());
_animation->renderSync(index, surface);
_animation->renderSync(
GetLottieFrameIndex(_animation.get(), _quality, index),
surface);
if (_cache) {
_cache->appendFrame(image, request, index);
if (_cache->framesReady() == _cache->framesCount()) {

View File

@ -51,12 +51,14 @@ class SharedState {
public:
SharedState(
std::unique_ptr<rlottie::Animation> animation,
const FrameRequest &request);
const FrameRequest &request,
Quality quality);
SharedState(
const QByteArray &content,
std::unique_ptr<rlottie::Animation> animation,
std::unique_ptr<Cache> cache,
const FrameRequest &request);
const FrameRequest &request,
Quality quality);
void start(
not_null<Player*> owner,
@ -98,6 +100,7 @@ private:
QByteArray _content;
std::unique_ptr<rlottie::Animation> _animation;
Quality _quality = Quality::Default;
// crl::queue changes 0,2,4,6 to 1,3,5,7.
// main thread changes 1,3,5,7 to 2,4,6,0.

View File

@ -15,8 +15,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Lottie {
MultiPlayer::MultiPlayer(std::shared_ptr<FrameRenderer> renderer)
: _timer([=] { checkNextFrameRender(); })
MultiPlayer::MultiPlayer(
Quality quality,
std::shared_ptr<FrameRenderer> renderer)
: _quality(quality)
, _timer([=] { checkNextFrameRender(); })
, _renderer(renderer ? std::move(renderer) : FrameRenderer::Instance()) {
crl::on_main_update_requests(
) | rpl::start_with_next([=] {
@ -43,7 +46,8 @@ not_null<Animation*> MultiPlayer::append(
std::move(get),
std::move(put),
content,
request));
request,
_quality));
return _animations.back().get();
}
@ -53,7 +57,8 @@ not_null<Animation*> MultiPlayer::append(
_animations.push_back(std::make_unique<Animation>(
this,
content,
request));
request,
_quality));
return _animations.back().get();
}

View File

@ -29,7 +29,9 @@ struct MultiUpdate {
class MultiPlayer final : public Player {
public:
explicit MultiPlayer(std::shared_ptr<FrameRenderer> renderer = nullptr);
MultiPlayer(
Quality quality = Quality::Default,
std::shared_ptr<FrameRenderer> renderer = nullptr);
~MultiPlayer();
void start(
@ -89,6 +91,7 @@ private:
void unpauseAndKeepUp(not_null<Animation*> animation);
void removeNow(not_null<Animation*> animation);
Quality _quality = Quality::Default;
base::Timer _timer;
const std::shared_ptr<FrameRenderer> _renderer;
std::vector<std::unique_ptr<Animation>> _animations;

View File

@ -14,8 +14,9 @@ namespace Lottie {
SinglePlayer::SinglePlayer(
const QByteArray &content,
const FrameRequest &request,
Quality quality,
std::shared_ptr<FrameRenderer> renderer)
: _animation(this, content, request)
: _animation(this, content, request, quality)
, _timer([=] { checkNextFrameRender(); })
, _renderer(renderer ? renderer : FrameRenderer::Instance()) {
}
@ -25,8 +26,9 @@ SinglePlayer::SinglePlayer(
FnMut<void(QByteArray &&cached)> put, // Unknown thread.
const QByteArray &content,
const FrameRequest &request,
Quality quality,
std::shared_ptr<FrameRenderer> renderer)
: _animation(this, std::move(get), std::move(put), content, request)
: _animation(this, std::move(get), std::move(put), content, request, quality)
, _timer([=] { checkNextFrameRender(); })
, _renderer(renderer ? renderer : FrameRenderer::Instance()) {
}

View File

@ -31,12 +31,14 @@ public:
SinglePlayer(
const QByteArray &content,
const FrameRequest &request,
Quality quality = Quality::Default,
std::shared_ptr<FrameRenderer> renderer = nullptr);
SinglePlayer(
FnMut<void(FnMut<void(QByteArray &&cached)>)> get, // Main thread.
FnMut<void(QByteArray &&cached)> put, // Unknown thread.
const QByteArray &content,
const FrameRequest &request,
Quality quality = Quality::Default,
std::shared_ptr<FrameRenderer> renderer = nullptr);
~SinglePlayer();

View File

@ -1049,7 +1049,8 @@ void MediaPreviewWidget::setupLottie() {
_lottie = std::make_unique<Lottie::SinglePlayer>(
Lottie::ReadContent(_document->data(), _document->filepath()),
Lottie::FrameRequest{ currentDimensions() * cIntRetinaFactor() });
Lottie::FrameRequest{ currentDimensions() * cIntRetinaFactor() },
Lottie::Quality::High);
_lottie->updates(
) | rpl::start_with_next([=](Lottie::Update update) {