// This file is part of Desktop App Toolkit, // a set of libraries for developing nice desktop applications. // // For license and copyright information please follow this link: // https://github.com/desktop-app/legal/blob/master/LEGAL // #include "lottie/lottie_animation.h" #include "lottie/details/lottie_frame_renderer.h" #include "lottie/details/lottie_frame_provider_direct.h" #include "lottie/lottie_player.h" #include "ui/image/image_prepare.h" #include "base/algorithm.h" #include "base/assertion.h" #include "base/variant.h" #ifdef LOTTIE_USE_CACHE #include "lottie/details/lottie_frame_provider_cached.h" #include "lottie/details/lottie_frame_provider_cached_multi.h" #endif // LOTTIE_USE_CACHE #include #include #include #include namespace Lottie { namespace { const auto kIdealSize = QSize(512, 512); details::InitData CheckSharedState(std::unique_ptr state) { Expects(state != nullptr); auto information = state->information(); if (!information.frameRate || information.framesCount <= 0 || information.size.isEmpty()) { return Error::NotSupported; } return state; } details::InitData Init( const QByteArray &content, const FrameRequest &request, Quality quality, const ColorReplacements *replacements) { if (const auto error = ContentError(content)) { return *error; } auto provider = std::make_shared(quality); if (!provider->load(content, replacements)) { return Error::ParseFailed; } return CheckSharedState(std::make_unique( std::move(provider), request.empty() ? FrameRequest{ kIdealSize } : request)); } #ifdef LOTTIE_USE_CACHE details::InitData Init( const QByteArray &content, FnMut put, const QByteArray &cached, const FrameRequest &request, Quality quality, const ColorReplacements *replacements) { Expects(!request.empty()); if (const auto error = ContentError(content)) { return *error; } auto provider = std::make_shared( content, std::move(put), cached, request, quality, replacements); return provider->valid() ? CheckSharedState(std::make_unique( std::move(provider), request.empty() ? FrameRequest{ kIdealSize } : request)) : Error::ParseFailed; } details::InitData Init( const QByteArray &content, FnMut put, std::vector caches, const FrameRequest &request, Quality quality, const ColorReplacements *replacements) { Expects(!request.empty()); if (const auto error = ContentError(content)) { return *error; } auto provider = std::make_shared( content, std::move(put), std::move(caches), request, quality, replacements); return provider->valid() ? CheckSharedState(std::make_unique( std::move(provider), request.empty() ? FrameRequest{ kIdealSize } : request)) : Error::ParseFailed; } #endif // LOTTIE_USE_CACHE details::InitData Init( std::shared_ptr provider, const FrameRequest &request) { Expects(!request.empty()); return provider->valid() ? CheckSharedState(std::make_unique( std::move(provider), request.empty() ? FrameRequest{ kIdealSize } : request)) : Error::ParseFailed; } } // namespace std::shared_ptr MakeFrameRenderer() { return FrameRenderer::CreateIndependent(); } QImage ReadThumbnail(const QByteArray &content) { return v::match(Init(content, FrameRequest(), Quality::High, nullptr), []( const std::unique_ptr &state) { return state->frameForPaint()->original; }, [](Error) { return QImage(); }); } Animation::Animation( not_null player, const QByteArray &content, const FrameRequest &request, Quality quality, const ColorReplacements *replacements) : _player(player) { if (quality == Quality::Synchronous) { initDone(Init(content, request, quality, replacements)); } else { const auto weak = base::make_weak(this); crl::async([=] { auto result = Init(content, request, quality, replacements); crl::on_main(weak, [=, data = std::move(result)]() mutable { initDone(std::move(data)); }); }); } } Animation::Animation( not_null player, FnMut)> get, // Main thread. FnMut put, // Unknown thread. const QByteArray &content, const FrameRequest &request, Quality quality, const ColorReplacements *replacements) #ifdef LOTTIE_USE_CACHE : _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, quality, replacements); crl::on_main(weak, [=, data = std::move(result)]() mutable { initDone(std::move(data)); }); }); }); #else // LOTTIE_USE_CACHE : Animation(player, content, request, quality, replacements) { #endif // LOTTIE_USE_CACHE } Animation::Animation( not_null player, int keysCount, FnMut)> get, FnMut put, const QByteArray &content, const FrameRequest &request, Quality quality, const ColorReplacements *replacements) #ifdef LOTTIE_USE_CACHE : _player(player) { const auto weak = base::make_weak(this); struct State { std::atomic left = 0; std::vector caches; FnMut put; }; const auto state = std::make_shared(); state->left = keysCount; state->put = std::move(put); state->caches.resize(keysCount); for (auto i = 0; i != keysCount; ++i) { get(i, [=](QByteArray &&cached) { state->caches[i] = std::move(cached); if (--state->left) { return; } crl::async([=] { auto result = Init( content, std::move(state->put), std::move(state->caches), request, quality, replacements); crl::on_main(weak, [=, data = std::move(result)]() mutable { initDone(std::move(data)); }); }); }); } #else // LOTTIE_USE_CACHE : Animation(player, content, request, quality, replacements) { #endif // LOTTIE_USE_CACHE } Animation::Animation( not_null player, std::shared_ptr provider, const FrameRequest &request) : _player(player) { const auto weak = base::make_weak(this); crl::async([=, provider = std::move(provider)]() mutable { auto result = Init(std::move(provider), request); crl::on_main(weak, [=, data = std::move(result)]() mutable { initDone(std::move(data)); }); }); } bool Animation::ready() const { return (_state != nullptr); } void Animation::initDone(details::InitData &&data) { v::match(data, [&](std::unique_ptr &state) { parseDone(std::move(state)); }, [&](Error error) { parseFailed(error); }); } void Animation::parseDone(std::unique_ptr state) { Expects(state != nullptr); _state = state.get(); _player->start(this, std::move(state)); } void Animation::parseFailed(Error error) { _player->failed(this, error); } 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); } return PrepareFrameByRequest(frame, !changed); } auto Animation::frameInfo(const FrameRequest &request) const -> FrameInfo { Expects(_state != nullptr); const auto frame = _state->frameForPaint(); const auto changed = (frame->request != request); if (changed) { frame->request = request; _player->updateFrameRequest(this, request); } return { PrepareFrameByRequest(frame, !changed), frame->index % _state->framesCount() }; } int Animation::frameIndex() const { Expects(_state != nullptr); const auto frame = _state->frameForPaint(); return frame->index % _state->framesCount(); } int Animation::framesCount() const { Expects(_state != nullptr); return _state->framesCount(); } Information Animation::information() const { Expects(_state != nullptr); return _state->information(); } std::optional ContentError(const QByteArray &content) { if (content.size() > kMaxFileSize) { return Error::ParseFailed; } return std::nullopt; } } // namespace Lottie