/* 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 "lottie/lottie_cache.h" #include "lottie/lottie_player.h" #include "base/algorithm.h" #include "zlib.h" #include "logs.h" #include #include #include #include #include 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(const_cast(bytes.data())); stream.avail_out = 0; while (!stream.avail_out) { stream.avail_out = result.size(); stream.next_out = reinterpret_cast(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; } std::optional ContentError(const QByteArray &content) { if (content.size() > kMaxFileSize) { qWarning() << "Lottie Error: Too large file: " << content.size(); return Error::ParseFailed; } return std::nullopt; } 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) { if (const auto error = ContentError(content)) { return *error; } auto animation = details::CreateFromContent(content); return animation ? CheckSharedState(std::make_unique( std::move(animation), request)) : Error::ParseFailed; } details::InitData Init( const QByteArray &content, FnMut put, const QByteArray &cached, const FrameRequest &request) { if (const auto error = ContentError(content)) { return *error; } auto cache = std::make_unique(cached, request, std::move(put)); const auto prepare = !cache->framesCount() || (cache->framesReady() < cache->framesCount()); auto animation = prepare ? details::CreateFromContent(content) : nullptr; return (!prepare || animation) ? CheckSharedState(std::make_unique( content, std::move(animation), std::move(cache), request)) : Error::ParseFailed; } } // namespace namespace details { std::unique_ptr CreateFromContent( const QByteArray &content) { const auto string = UnpackGzip(content); Assert(string.size() <= kMaxFileSize); auto result = rlottie::Animation::loadFromData(string, std::string()); if (!result) { qWarning() << "Lottie Error: Parse failed."; } return result; } } // namespace details QImage ReadThumbnail(const QByteArray &content) { return Init(content, FrameRequest()).match([]( const std::unique_ptr &state) { return state->frameForPaint()->original; }, [](Error) { return QImage(); }); } Animation::Animation( not_null player, const QByteArray &content, const FrameRequest &request) : _player(player) { const auto weak = base::make_weak(this); crl::async([=] { crl::on_main(weak, [=, data = Init(content, request)]() mutable { initDone(std::move(data)); }); }); } Animation::Animation( not_null player, FnMut)> get, // Main thread. FnMut put, // Unknown thread. const QByteArray &content, 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); 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) { data.match([&](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 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); } } // namespace Lottie