tdesktop/Telegram/SourceFiles/lottie/lottie_animation.cpp

240 lines
5.8 KiB
C++
Raw Normal View History

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"
2019-06-26 12:01:04 +02:00
#include "storage/cache/storage_cache_database.h"
2019-04-27 13:12:53 +04:00
#include "base/algorithm.h"
#include "zlib.h"
#include "logs.h"
2019-04-27 13:12:53 +04:00
#include <QFile>
2019-06-26 10:29:19 +02:00
#include <QDebug>
#include <rlottie.h>
#include <crl/crl_async.h>
#include <crl/crl_on_main.h>
2019-04-27 13:12:53 +04:00
namespace Lottie {
namespace {
std::string UnpackGzip(const QByteArray &bytes) {
const auto original = [&] {
return std::string(bytes.constData(), bytes.size());
};
z_stream stream;
stream.zalloc = nullptr;
stream.zfree = nullptr;
stream.opaque = nullptr;
stream.avail_in = 0;
stream.next_in = nullptr;
int res = inflateInit2(&stream, 16 + MAX_WBITS);
if (res != Z_OK) {
return original();
}
const auto guard = gsl::finally([&] { inflateEnd(&stream); });
auto result = std::string(kMaxFileSize + 1, char(0));
stream.avail_in = bytes.size();
stream.next_in = reinterpret_cast<Bytef*>(const_cast<char*>(bytes.data()));
stream.avail_out = 0;
while (!stream.avail_out) {
stream.avail_out = result.size();
stream.next_out = reinterpret_cast<Bytef*>(result.data());
int res = inflate(&stream, Z_NO_FLUSH);
if (res != Z_OK && res != Z_STREAM_END) {
return original();
} else if (!stream.avail_out) {
return original();
}
}
result.resize(result.size() - stream.avail_out);
return result;
}
} // namespace
2019-04-27 13:12:53 +04:00
std::unique_ptr<Animation> FromFile(const QString &path) {
return FromData([&] {
auto f = QFile(path);
return (f.size() <= kMaxFileSize && f.open(QIODevice::ReadOnly))
? f.readAll()
: QByteArray();
}());
}
std::unique_ptr<Animation> FromData(const QByteArray &data) {
return std::make_unique<Animation>(base::duplicate(data));
2019-04-27 13:12:53 +04:00
}
2019-06-26 12:01:04 +02:00
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);
}
2019-05-28 17:45:30 +02:00
auto Init(QByteArray &&content)
-> base::variant<std::unique_ptr<SharedState>, Error> {
if (content.size() > kMaxFileSize) {
qWarning()
<< "Lottie Error: Too large file: "
<< content.size();
return Error::ParseFailed;
}
const auto string = UnpackGzip(content);
Assert(string.size() <= kMaxFileSize);
auto animation = rlottie::Animation::loadFromData(string, std::string());
if (!animation) {
2019-05-28 17:45:30 +02:00
qWarning()
<< "Lottie Error: Parse failed.";
2019-05-28 17:45:30 +02:00
return Error::ParseFailed;
}
auto result = std::make_unique<SharedState>(std::move(animation));
2019-05-28 17:45:30 +02:00
auto information = result->information();
if (!information.frameRate
|| information.framesCount <= 0
|| information.size.isEmpty()) {
return Error::NotSupported;
}
return std::move(result);
}
QImage ReadThumbnail(QByteArray &&content) {
return Init(std::move(content)).match([](
const std::unique_ptr<SharedState> &state) {
return state->frameForPaint()->original;
}, [](Error) {
return QImage();
});
}
Animation::Animation(QByteArray &&content)
2019-05-28 13:39:38 +02:00
: _timer([=] { checkNextFrameRender(); }) {
const auto weak = base::make_weak(this);
crl::async([=, content = base::take(content)]() mutable {
2019-05-28 17:45:30 +02:00
crl::on_main(weak, [this, result = Init(std::move(content))]() mutable {
result.match([&](std::unique_ptr<SharedState> &state) {
parseDone(std::move(state));
}, [&](Error error) {
parseFailed(error);
});
2019-05-28 17:45:30 +02:00
});
});
2019-04-27 13:12:53 +04:00
}
Animation::~Animation() {
if (_renderer) {
Assert(_state != nullptr);
_renderer->remove(_state);
}
2019-04-27 13:12:53 +04:00
}
void Animation::parseDone(std::unique_ptr<SharedState> state) {
Expects(state != nullptr);
auto information = state->information();
2019-05-28 13:39:38 +02:00
_state = state.get();
_state->start(this, crl::now());
_renderer = FrameRenderer::Instance();
_renderer->append(std::move(state));
_updates.fire({ std::move(information) });
crl::on_main_update_requests(
) | rpl::start_with_next([=] {
checkStep();
}, _lifetime);
}
2019-05-28 17:45:30 +02:00
void Animation::parseFailed(Error error) {
_updates.fire_error(std::move(error));
}
QImage Animation::frame(const FrameRequest &request) const {
Expects(_renderer != nullptr);
const auto frame = _state->frameForPaint();
const auto changed = (frame->request != request);
if (changed) {
frame->request = request;
_renderer->updateFrameRequest(_state, request);
2019-04-27 13:12:53 +04:00
}
return PrepareFrameByRequest(frame, !changed);
2019-04-27 13:12:53 +04:00
}
rpl::producer<Update, Error> Animation::updates() const {
return _updates.events();
2019-04-27 13:12:53 +04:00
}
bool Animation::ready() const {
return (_renderer != nullptr);
2019-05-02 16:42:47 +04: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
}
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-28 13:39:38 +02:00
void Animation::checkStep() {
if (_nextFrameTime != kTimeUnknown) {
checkNextFrameRender();
} else {
checkNextFrameAvailability();
}
}
void Animation::checkNextFrameAvailability() {
Expects(_renderer != nullptr);
2019-05-28 13:39:38 +02:00
_nextFrameTime = _state->nextFrameDisplayTime();
if (_nextFrameTime != kTimeUnknown) {
checkStep();
2019-04-27 13:12:53 +04:00
}
2019-05-28 13:39:38 +02:00
}
void Animation::checkNextFrameRender() {
Expects(_nextFrameTime != kTimeUnknown);
2019-04-27 13:12:53 +04:00
const auto now = crl::now();
2019-05-28 13:39:38 +02:00
if (now < _nextFrameTime) {
if (!_timer.isActive()) {
_timer.callOnce(_nextFrameTime - now);
}
} else {
_timer.cancel();
2019-05-28 13:39:38 +02:00
_nextFrameTime = kTimeUnknown;
const auto position = markFrameDisplayed(now);
_updates.fire({ DisplayFrameRequest{ position } });
}
2019-04-27 13:12:53 +04:00
}
//void Animation::play(const PlaybackOptions &options) {
// _options = options;
// _started = crl::now();
//}
2019-04-27 13:12:53 +04:00
} // namespace Lottie