Render animated stickers using rlottie.

This commit is contained in:
John Preston 2019-06-25 16:16:38 +02:00
parent b36f7dfdb1
commit b10e6b3508
3 changed files with 61 additions and 39 deletions

View File

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/algorithm.h" #include "base/algorithm.h"
#include "zlib.h" #include "zlib.h"
#include "logs.h" #include "logs.h"
#include "rlottie.h"
#include <QFile> #include <QFile>
#include <crl/crl_async.h> #include <crl/crl_async.h>
@ -82,14 +83,15 @@ auto Init(QByteArray &&content)
<< content.size(); << content.size();
return Error::ParseFailed; return Error::ParseFailed;
} }
const auto document = JsonDocument(std::move(content)); auto animation = rlottie::Animation::loadFromData(
if (const auto error = document.error()) { std::string(content.constData(), content.size()),
std::string());
if (!animation) {
qWarning() qWarning()
<< "Lottie Error: Parse failed with code: " << "Lottie Error: Parse failed.";
<< error;
return Error::ParseFailed; return Error::ParseFailed;
} }
auto result = std::make_unique<SharedState>(document.root()); auto result = std::make_unique<SharedState>(std::move(animation));
auto information = result->information(); auto information = result->information();
if (!information.frameRate if (!information.frameRate
|| information.framesCount <= 0 || information.framesCount <= 0

View File

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lottie/lottie_animation.h" #include "lottie/lottie_animation.h"
#include "rasterrenderer/rasterrenderer.h" #include "rasterrenderer/rasterrenderer.h"
#include "logs.h" #include "logs.h"
#include "rlottie.h"
#include <range/v3/algorithm/find.hpp> #include <range/v3/algorithm/find.hpp>
#include <range/v3/algorithm/count_if.hpp> #include <range/v3/algorithm/count_if.hpp>
@ -23,6 +24,8 @@ namespace Lottie {
namespace { namespace {
constexpr auto kDisplaySkipped = crl::time(-1); constexpr auto kDisplaySkipped = crl::time(-1);
constexpr auto kMaxFrameRate = 120;
constexpr auto kMaxSize = 3096;
std::weak_ptr<FrameRenderer> GlobalInstance; std::weak_ptr<FrameRenderer> GlobalInstance;
@ -171,21 +174,40 @@ void FrameRendererObject::queueGenerateFrames() {
}); });
} }
SharedState::SharedState(const JsonObject &definition) SharedState::SharedState(std::unique_ptr<rlottie::Animation> animation)
: _scene(definition) { : _animation(std::move(animation)) {
if (_scene.isValid()) { Expects(_animation != nullptr);
if (isValid()) {
auto cover = QImage(); auto cover = QImage();
renderFrame(cover, FrameRequest::NonStrict(), 0); renderFrame(cover, FrameRequest::NonStrict(), 0);
init(std::move(cover)); init(std::move(cover));
} }
} }
bool SharedState::isValid() const {
auto width = size_t(0);
auto height = size_t(0);
_animation->size(width, height);
const auto frameRate = int(_animation->frameRate());
return _animation->totalFrame() > 0
&& frameRate > 0
&& frameRate <= kMaxFrameRate
&& width > 0
&& width <= kMaxSize
&& height > 0
&& height <= kMaxSize;
}
void SharedState::renderFrame( void SharedState::renderFrame(
QImage &image, QImage &image,
const FrameRequest &request, const FrameRequest &request,
int index) { int index) {
const auto realSize = QSize(_scene.width(), _scene.height()); auto width = size_t(0);
if (realSize.isEmpty() || _scene.endFrame() <= _scene.startFrame()) { auto height = size_t(0);
_animation->size(width, height);
const auto realSize = QSize(width, height);
if (realSize.isEmpty() || !_animation->totalFrame()) {
return; return;
} }
@ -195,32 +217,19 @@ void SharedState::renderFrame(
} }
image.fill(Qt::transparent); image.fill(Qt::transparent);
QPainter p(&image); auto surface = rlottie::Surface(
p.setRenderHints(QPainter::Antialiasing); reinterpret_cast<uint32_t*>(image.bits()),
p.setRenderHints(QPainter::SmoothPixmapTransform); image.width(),
p.setRenderHint(QPainter::TextAntialiasing); image.height(),
p.setRenderHints(QPainter::HighQualityAntialiasing); image.bytesPerLine());
if (realSize != size) { _animation->renderSync(index, surface);
p.scale(
size.width() / float64(realSize.width()),
size.height() / float64(realSize.height()));
}
const auto frame = std::clamp(
_scene.startFrame() + index,
_scene.startFrame(),
_scene.endFrame() - 1);
_scene.updateProperties(frame);
RasterRenderer renderer(&p);
_scene.render(renderer, frame);
} }
void SharedState::init(QImage cover) { void SharedState::init(QImage cover) {
Expects(!initialized()); Expects(!initialized());
_frameRate = _scene.frameRate(); _frameRate = int(_animation->frameRate());
_framesCount = _scene.endFrame() - _scene.startFrame(); _framesCount = int(_animation->totalFrame());
_duration = crl::time(1000) * _framesCount / _frameRate; _duration = crl::time(1000) * _framesCount / _frameRate;
_frames[0].original = std::move(cover); _frames[0].original = std::move(cover);
@ -319,13 +328,17 @@ not_null<const Frame*> SharedState::getFrame(int index) const {
} }
Information SharedState::information() const { Information SharedState::information() const {
if (!_scene.isValid()) { if (!isValid()) {
return {}; return {};
} }
auto width = size_t(0);
auto height = size_t(0);
_animation->size(width, height);
auto result = Information(); auto result = Information();
result.frameRate = _scene.frameRate(); result.frameRate = int(_animation->frameRate());
result.size = QSize(_scene.width(), _scene.height()); result.size = QSize(width, height);
result.framesCount = _scene.endFrame() - _scene.startFrame(); result.framesCount = int(_animation->totalFrame());
return result; return result;
} }
@ -421,6 +434,8 @@ crl::time SharedState::markFrameShown() {
Unexpected("Counter value in Lottie::SharedState::markFrameShown."); Unexpected("Counter value in Lottie::SharedState::markFrameShown.");
} }
SharedState::~SharedState() = default;
std::shared_ptr<FrameRenderer> FrameRenderer::Instance() { std::shared_ptr<FrameRenderer> FrameRenderer::Instance() {
if (auto result = GlobalInstance.lock()) { if (auto result = GlobalInstance.lock()) {
return result; return result;

View File

@ -10,14 +10,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/basic_types.h" #include "base/basic_types.h"
#include "base/weak_ptr.h" #include "base/weak_ptr.h"
#include "lottie/lottie_common.h" #include "lottie/lottie_common.h"
#include "bmscene.h"
#include <QImage> #include <QImage>
#include <crl/crl_time.h> #include <crl/crl_time.h>
#include <crl/crl_object_on_queue.h> #include <crl/crl_object_on_queue.h>
#include <limits> #include <limits>
class BMBase; namespace rlottie {
class Animation;
} // namespace rlottie
class QImage; class QImage;
namespace Lottie { namespace Lottie {
@ -41,7 +43,7 @@ QImage PrepareFrameByRequest(
class SharedState { class SharedState {
public: public:
explicit SharedState(const JsonObject &definition); explicit SharedState(std::unique_ptr<rlottie::Animation> animation);
void start(not_null<Animation*> owner, crl::time now); void start(not_null<Animation*> owner, crl::time now);
@ -56,7 +58,10 @@ public:
void renderFrame(QImage &image, const FrameRequest &request, int index); void renderFrame(QImage &image, const FrameRequest &request, int index);
[[nodiscard]] bool renderNextFrame(const FrameRequest &request); [[nodiscard]] bool renderNextFrame(const FrameRequest &request);
~SharedState();
private: private:
bool isValid() const;
void init(QImage cover); void init(QImage cover);
void renderNextFrame( void renderNextFrame(
not_null<Frame*> frame, not_null<Frame*> frame,
@ -65,7 +70,7 @@ private:
[[nodiscard]] not_null<const Frame*> getFrame(int index) const; [[nodiscard]] not_null<const Frame*> getFrame(int index) const;
[[nodiscard]] int counter() const; [[nodiscard]] int counter() const;
BMScene _scene; std::unique_ptr<rlottie::Animation> _animation;
static constexpr auto kCounterUninitialized = -1; static constexpr auto kCounterUninitialized = -1;
std::atomic<int> _counter = kCounterUninitialized; std::atomic<int> _counter = kCounterUninitialized;