2019-04-27 13:12:53 +04:00
|
|
|
/*
|
|
|
|
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 "lottie/lottie_animation.h"
|
|
|
|
|
|
|
|
#include "lottie/lottie_frame_renderer.h"
|
|
|
|
#include "base/algorithm.h"
|
|
|
|
|
2019-05-14 00:57:59 +03:00
|
|
|
#include <crl/crl_async.h>
|
|
|
|
#include <crl/crl_on_main.h>
|
2019-04-27 13:12:53 +04:00
|
|
|
#include <QJsonDocument>
|
|
|
|
#include <QFile>
|
|
|
|
|
|
|
|
#include "rasterrenderer/lottierasterrenderer.h"
|
|
|
|
|
|
|
|
namespace Lottie {
|
|
|
|
|
|
|
|
bool ValidateFile(const QString &path) {
|
|
|
|
if (!path.endsWith(qstr(".json"), Qt::CaseInsensitive)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<Animation> FromFile(const QString &path) {
|
|
|
|
if (!path.endsWith(qstr(".json"), Qt::CaseInsensitive)) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
auto f = QFile(path);
|
|
|
|
if (!f.open(QIODevice::ReadOnly)) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
const auto content = f.readAll();
|
|
|
|
if (content.isEmpty()) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2019-05-10 18:47:48 +03:00
|
|
|
return FromData(content);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<Animation> FromData(const QByteArray &data) {
|
|
|
|
return std::make_unique<Lottie::Animation>(data);
|
2019-04-27 13:12:53 +04:00
|
|
|
}
|
|
|
|
|
2019-05-14 00:57:59 +03:00
|
|
|
Animation::Animation(const QByteArray &content)
|
|
|
|
: _timer([=] { checkNextFrame(); }) {
|
|
|
|
const auto weak = base::make_weak(this);
|
|
|
|
crl::async([=] {
|
|
|
|
auto error = QJsonParseError();
|
|
|
|
const auto document = QJsonDocument::fromJson(content, &error);
|
|
|
|
if (error.error != QJsonParseError::NoError) {
|
|
|
|
qCWarning(lcLottieQtBodymovinParser)
|
|
|
|
<< "Lottie Error: Parse failed with code "
|
|
|
|
<< error.error
|
|
|
|
<< "( " << error.errorString() << ")";
|
|
|
|
crl::on_main(weak, [=] {
|
|
|
|
parseFailed();
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
auto state = std::make_unique<SharedState>(document.object());
|
|
|
|
crl::on_main(weak, [this, result = std::move(state)]() mutable {
|
|
|
|
parseDone(std::move(result));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
2019-04-27 13:12:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
Animation::~Animation() {
|
2019-05-14 00:57:59 +03:00
|
|
|
if (_renderer) {
|
|
|
|
Assert(_state != nullptr);
|
|
|
|
_renderer->remove(_state);
|
|
|
|
}
|
2019-04-27 13:12:53 +04:00
|
|
|
}
|
|
|
|
|
2019-05-14 00:57:59 +03:00
|
|
|
void Animation::parseDone(std::unique_ptr<SharedState> state) {
|
|
|
|
Expects(state != nullptr);
|
|
|
|
|
|
|
|
auto information = state->information();
|
|
|
|
if (!information.frameRate
|
|
|
|
|| information.framesCount <= 0
|
|
|
|
|| information.size.isEmpty()) {
|
|
|
|
_updates.fire_error(Error::NotSupported);
|
|
|
|
} else {
|
|
|
|
_state = state.get();
|
|
|
|
_state->start(this, crl::now());
|
|
|
|
_renderer = FrameRenderer::Instance();
|
|
|
|
_renderer->append(std::move(state));
|
|
|
|
_updates.fire({ std::move(information) });
|
2019-04-27 13:12:53 +04:00
|
|
|
}
|
2019-05-14 00:57:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void Animation::parseFailed() {
|
|
|
|
_updates.fire_error(Error::ParseFailed);
|
|
|
|
}
|
|
|
|
|
|
|
|
QImage Animation::frame(const FrameRequest &request) const {
|
|
|
|
Expects(_renderer != nullptr);
|
|
|
|
|
|
|
|
const auto frame = _state->frameForPaint();
|
|
|
|
const auto changed = (frame->request != request)
|
|
|
|
&& (request.strict || !frame->request.strict);
|
|
|
|
if (changed) {
|
|
|
|
frame->request = request;
|
|
|
|
_renderer->updateFrameRequest(_state, request);
|
2019-04-27 13:12:53 +04:00
|
|
|
}
|
2019-05-14 00:57:59 +03:00
|
|
|
return PrepareFrameByRequest(frame, !changed);
|
2019-04-27 13:12:53 +04:00
|
|
|
}
|
|
|
|
|
2019-05-14 00:57:59 +03:00
|
|
|
rpl::producer<Update, Error> Animation::updates() const {
|
|
|
|
return _updates.events();
|
2019-04-27 13:12:53 +04:00
|
|
|
}
|
|
|
|
|
2019-05-14 00:57:59 +03:00
|
|
|
bool Animation::ready() const {
|
|
|
|
return (_renderer != nullptr);
|
2019-05-02 16:42:47 +04:00
|
|
|
}
|
|
|
|
|
2019-05-14 00:57:59 +03:00
|
|
|
crl::time Animation::markFrameDisplayed(crl::time now) {
|
|
|
|
Expects(_renderer != nullptr);
|
|
|
|
|
|
|
|
const auto result = _state->markFrameDisplayed(now);
|
|
|
|
|
|
|
|
return result;
|
2019-04-27 13:12:53 +04:00
|
|
|
}
|
|
|
|
|
2019-05-14 00:57:59 +03:00
|
|
|
crl::time Animation::markFrameShown() {
|
|
|
|
Expects(_renderer != nullptr);
|
|
|
|
|
|
|
|
const auto result = _state->markFrameShown();
|
|
|
|
_renderer->frameShown(_state);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
2019-04-27 13:12:53 +04:00
|
|
|
|
2019-05-14 00:57:59 +03:00
|
|
|
void Animation::checkNextFrame() {
|
|
|
|
Expects(_renderer != nullptr);
|
|
|
|
|
|
|
|
const auto time = _state->nextFrameDisplayTime();
|
|
|
|
if (time == kTimeUnknown) {
|
2019-04-27 13:12:53 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-05-14 00:57:59 +03:00
|
|
|
const auto now = crl::now();
|
|
|
|
if (time > now) {
|
|
|
|
_timer.callOnce(time - now);
|
|
|
|
} else {
|
|
|
|
_timer.cancel();
|
|
|
|
|
|
|
|
const auto position = markFrameDisplayed(now);
|
|
|
|
_updates.fire({ DisplayFrameRequest{ position } });
|
|
|
|
}
|
2019-04-27 13:12:53 +04:00
|
|
|
}
|
|
|
|
|
2019-05-14 00:57:59 +03:00
|
|
|
//void Animation::play(const PlaybackOptions &options) {
|
|
|
|
// _options = options;
|
|
|
|
// _started = crl::now();
|
|
|
|
//}
|
|
|
|
|
2019-04-27 13:12:53 +04:00
|
|
|
} // namespace Lottie
|