tdesktop/Telegram/SourceFiles/lottie/lottie_animation.cpp

218 lines
5.5 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 16:18:00 +02:00
#include "lottie/lottie_cache.h"
#include "lottie/lottie_player.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 <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;
}
2019-06-26 16:18:00 +02:00
std::optional<Error> ContentError(const QByteArray &content) {
2019-05-28 17:45:30 +02:00
if (content.size() > kMaxFileSize) {
2019-06-28 17:47:32 +02:00
LOG(("Lottie Error: Too large file: %1").arg(content.size()));
2019-05-28 17:45:30 +02:00
return Error::ParseFailed;
}
2019-06-26 16:18:00 +02:00
return std::nullopt;
}
details::InitData CheckSharedState(std::unique_ptr<SharedState> state) {
Expects(state != nullptr);
2019-06-26 16:18:00 +02:00
auto information = state->information();
2019-05-28 17:45:30 +02:00
if (!information.frameRate
|| information.framesCount <= 0
|| information.size.isEmpty()) {
return Error::NotSupported;
}
2019-06-26 16:18:00 +02:00
return state;
}
2019-06-27 18:57:32 +02:00
details::InitData Init(
const QByteArray &content,
const FrameRequest &request) {
2019-06-26 16:18:00 +02:00
if (const auto error = ContentError(content)) {
return *error;
}
2019-06-26 19:14:46 +02:00
auto animation = details::CreateFromContent(content);
2019-06-26 16:18:00 +02:00
return animation
? CheckSharedState(std::make_unique<SharedState>(
2019-06-27 18:57:32 +02:00
std::move(animation),
request))
2019-06-26 16:18:00 +02:00
: Error::ParseFailed;
2019-05-28 17:45:30 +02:00
}
2019-06-26 16:18:00 +02:00
details::InitData Init(
const QByteArray &content,
FnMut<void(QByteArray &&cached)> put,
2019-06-26 16:18:00 +02:00
const QByteArray &cached,
2019-06-26 19:14:46 +02:00
const FrameRequest &request) {
2019-06-26 16:18:00 +02:00
if (const auto error = ContentError(content)) {
return *error;
}
auto cache = std::make_unique<Cache>(cached, request, std::move(put));
const auto prepare = !cache->framesCount()
|| (cache->framesReady() < cache->framesCount());
2019-06-26 19:14:46 +02:00
auto animation = prepare ? details::CreateFromContent(content) : nullptr;
2019-06-26 16:18:00 +02:00
return (!prepare || animation)
? CheckSharedState(std::make_unique<SharedState>(
2019-06-26 19:14:46 +02:00
content,
std::move(animation),
std::move(cache),
2019-06-26 19:14:46 +02:00
request))
2019-06-26 16:18:00 +02:00
: Error::ParseFailed;
}
} // namespace
2019-06-26 19:14:46 +02:00
namespace details {
std::unique_ptr<rlottie::Animation> CreateFromContent(
const QByteArray &content) {
const auto string = UnpackGzip(content);
Assert(string.size() <= kMaxFileSize);
auto result = rlottie::Animation::loadFromData(string, std::string());
if (!result) {
2019-06-28 17:47:32 +02:00
LOG(("Lottie Error: Parse failed."));
2019-06-26 19:14:46 +02:00
}
return result;
}
} // namespace details
std::shared_ptr<FrameRenderer> MakeFrameRenderer() {
return FrameRenderer::CreateIndependent();
}
2019-06-26 16:18:00 +02:00
QImage ReadThumbnail(const QByteArray &content) {
2019-06-27 18:57:32 +02:00
return Init(content, FrameRequest()).match([](
2019-05-28 17:45:30 +02:00
const std::unique_ptr<SharedState> &state) {
return state->frameForPaint()->original;
}, [](Error) {
return QImage();
});
}
Animation::Animation(
not_null<Player*> player,
const QByteArray &content,
const FrameRequest &request)
: _player(player) {
2019-06-26 16:18:00 +02:00
const auto weak = base::make_weak(this);
crl::async([=] {
2019-06-27 18:57:32 +02:00
crl::on_main(weak, [=, data = Init(content, request)]() mutable {
2019-06-26 16:18:00 +02:00
initDone(std::move(data));
});
});
}
Animation::Animation(
not_null<Player*> player,
FnMut<void(FnMut<void(QByteArray &&cached)>)> get, // Main thread.
FnMut<void(QByteArray &&cached)> put, // Unknown thread.
2019-06-26 16:18:00 +02:00
const QByteArray &content,
2019-06-26 19:14:46 +02:00
const FrameRequest &request)
: _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);
2019-06-26 16:18:00 +02:00
crl::on_main(weak, [=, data = std::move(result)]() mutable {
initDone(std::move(data));
});
2019-05-28 17:45:30 +02:00
});
});
2019-04-27 13:12:53 +04:00
}
bool Animation::ready() const {
return (_state != nullptr);
2019-04-27 13:12:53 +04:00
}
2019-06-26 16:18:00 +02:00
void Animation::initDone(details::InitData &&data) {
data.match([&](std::unique_ptr<SharedState> &state) {
parseDone(std::move(state));
}, [&](Error error) {
parseFailed(error);
});
}
void Animation::parseDone(std::unique_ptr<SharedState> state) {
Expects(state != nullptr);
2019-05-28 13:39:38 +02:00
_state = state.get();
_player->start(this, std::move(state));
}
2019-05-28 17:45:30 +02:00
void Animation::parseFailed(Error error) {
_player->failed(this, error);
}
2019-07-02 15:20:04 +02:00
QImage Animation::frame() const {
Expects(_state != nullptr);
return PrepareFrameByRequest(_state->frameForPaint(), true);
}
QImage Animation::frame(const FrameRequest &request) const {
Expects(_state != nullptr);
const auto frame = _state->frameForPaint();
const auto changed = (frame->request != request);
if (changed) {
frame->request = request;
_player->updateFrameRequest(this, request);
2019-04-27 13:12:53 +04:00
}
return PrepareFrameByRequest(frame, !changed);
2019-04-27 13:12:53 +04:00
}
} // namespace Lottie