From 250b7240f6b94eb958319e19f3e04c286b8fb859 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 23 Sep 2019 13:41:02 +0300 Subject: [PATCH] Add lib_[r]lottie as submodules. --- .gitmodules | 8 +- .../SourceFiles/lottie/lottie_animation.cpp | 264 -------- .../SourceFiles/lottie/lottie_animation.h | 79 --- Telegram/SourceFiles/lottie/lottie_cache.cpp | 626 ------------------ Telegram/SourceFiles/lottie/lottie_cache.h | 126 ---- Telegram/SourceFiles/lottie/lottie_common.cpp | 41 -- Telegram/SourceFiles/lottie/lottie_common.h | 66 -- .../lottie/lottie_frame_renderer.cpp | 612 ----------------- .../lottie/lottie_frame_renderer.h | 160 ----- .../lottie/lottie_multi_player.cpp | 389 ----------- .../SourceFiles/lottie/lottie_multi_player.h | 114 ---- Telegram/SourceFiles/lottie/lottie_player.h | 35 - .../lottie/lottie_single_player.cpp | 158 ----- .../SourceFiles/lottie/lottie_single_player.h | 79 --- Telegram/ThirdParty/rlottie | 2 +- Telegram/gyp/Telegram.gyp | 2 +- Telegram/gyp/generate.py | 1 + Telegram/gyp/lib_ffmpeg.gyp | 1 + Telegram/gyp/lib_lottie.gyp | 65 -- Telegram/gyp/lib_rlottie.gyp | 134 ---- Telegram/lib_lottie | 1 + Telegram/lib_rlottie | 1 + 22 files changed, 13 insertions(+), 2951 deletions(-) delete mode 100644 Telegram/SourceFiles/lottie/lottie_animation.cpp delete mode 100644 Telegram/SourceFiles/lottie/lottie_animation.h delete mode 100644 Telegram/SourceFiles/lottie/lottie_cache.cpp delete mode 100644 Telegram/SourceFiles/lottie/lottie_cache.h delete mode 100644 Telegram/SourceFiles/lottie/lottie_common.cpp delete mode 100644 Telegram/SourceFiles/lottie/lottie_common.h delete mode 100644 Telegram/SourceFiles/lottie/lottie_frame_renderer.cpp delete mode 100644 Telegram/SourceFiles/lottie/lottie_frame_renderer.h delete mode 100644 Telegram/SourceFiles/lottie/lottie_multi_player.cpp delete mode 100644 Telegram/SourceFiles/lottie/lottie_multi_player.h delete mode 100644 Telegram/SourceFiles/lottie/lottie_player.h delete mode 100644 Telegram/SourceFiles/lottie/lottie_single_player.cpp delete mode 100644 Telegram/SourceFiles/lottie/lottie_single_player.h delete mode 100644 Telegram/gyp/lib_lottie.gyp delete mode 100644 Telegram/gyp/lib_rlottie.gyp create mode 160000 Telegram/lib_lottie create mode 160000 Telegram/lib_rlottie diff --git a/.gitmodules b/.gitmodules index 1c097c1d61..2e49012beb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -15,7 +15,7 @@ url = https://github.com/Cyan4973/xxHash.git [submodule "Telegram/ThirdParty/rlottie"] path = Telegram/ThirdParty/rlottie - url = https://github.com/john-preston/rlottie + url = https://github.com/desktop-app/rlottie.git [submodule "Telegram/ThirdParty/lz4"] path = Telegram/ThirdParty/lz4 url = https://github.com/lz4/lz4.git @@ -37,3 +37,9 @@ [submodule "Telegram/lib_ui"] path = Telegram/lib_ui url = https://github.com/desktop-app/lib_ui.git +[submodule "Telegram/lib_rlottie"] + path = Telegram/lib_rlottie + url = https://github.com/desktop-app/lib_rlottie.git +[submodule "Telegram/lib_lottie"] + path = Telegram/lib_lottie + url = https://github.com/desktop-app/lib_lottie.git diff --git a/Telegram/SourceFiles/lottie/lottie_animation.cpp b/Telegram/SourceFiles/lottie/lottie_animation.cpp deleted file mode 100644 index f681c9220d..0000000000 --- a/Telegram/SourceFiles/lottie/lottie_animation.cpp +++ /dev/null @@ -1,264 +0,0 @@ -/* -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 - -namespace Lottie { -namespace { - -const auto kIdealSize = QSize(512, 512); - -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) { - LOG(("Lottie Error: Too large file: %1").arg(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, - Quality quality, - const ColorReplacements *replacements) { - if (const auto error = ContentError(content)) { - return *error; - } - auto animation = details::CreateFromContent(content, replacements); - return animation - ? CheckSharedState(std::make_unique( - std::move(animation), - request.empty() ? FrameRequest{ kIdealSize } : request, - quality)) - : Error::ParseFailed; -} - -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 cache = std::make_unique(cached, request, std::move(put)); - const auto prepare = !cache->framesCount() - || (cache->framesReady() < cache->framesCount()); - auto animation = prepare - ? details::CreateFromContent(content, replacements) - : nullptr; - return (!prepare || animation) - ? CheckSharedState(std::make_unique( - content, - replacements, - std::move(animation), - std::move(cache), - request, - quality)) - : Error::ParseFailed; -} - -} // namespace - -namespace details { - -std::unique_ptr CreateFromContent( - const QByteArray &content, - const ColorReplacements *replacements) { - const auto string = UnpackGzip(content); - Assert(string.size() <= kMaxFileSize); - - auto result = rlottie::Animation::loadFromData( - string, - std::string(), - std::string(), - false, - (replacements - ? replacements->replacements - : std::vector>())); - if (!result) { - LOG(("Lottie Error: Parse failed.")); - } - return result; -} - -} // namespace details - -std::shared_ptr MakeFrameRenderer() { - return FrameRenderer::CreateIndependent(); -} - -QImage ReadThumbnail(const QByteArray &content) { - return Init(content, FrameRequest(), Quality::High, nullptr).match([]( - 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) { - 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) -: _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)); - }); - }); - }); -} - -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 { - 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() - }; -} - -} // namespace Lottie diff --git a/Telegram/SourceFiles/lottie/lottie_animation.h b/Telegram/SourceFiles/lottie/lottie_animation.h deleted file mode 100644 index 09d16f4eae..0000000000 --- a/Telegram/SourceFiles/lottie/lottie_animation.h +++ /dev/null @@ -1,79 +0,0 @@ -/* -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 -*/ -#pragma once - -#include "lottie/lottie_common.h" -#include "base/weak_ptr.h" - -#include - -class QString; -class QByteArray; - -namespace rlottie { -class Animation; -} // namespace rlottie - -namespace Lottie { - -class Player; -class SharedState; -class FrameRenderer; - -std::shared_ptr MakeFrameRenderer(); - -QImage ReadThumbnail(const QByteArray &content); - -namespace details { - -using InitData = base::variant, Error>; - -std::unique_ptr CreateFromContent( - const QByteArray &content, - const ColorReplacements *replacements); - -} // namespace details - -class Animation final : public base::has_weak_ptr { -public: - struct FrameInfo { - QImage image; - int index = 0; - }; - - Animation( - not_null player, - const QByteArray &content, - const FrameRequest &request, - Quality quality, - const ColorReplacements *replacements = nullptr); - Animation( - not_null player, - FnMut)> get, // Main thread. - FnMut put, // Unknown thread. - const QByteArray &content, - const FrameRequest &request, - Quality quality, - const ColorReplacements *replacements = nullptr); - - [[nodiscard]] bool ready() const; - [[nodiscard]] QImage frame() const; - [[nodiscard]] QImage frame(const FrameRequest &request) const; - [[nodiscard]] FrameInfo frameInfo(const FrameRequest &request) const; - -private: - void initDone(details::InitData &&data); - void parseDone(std::unique_ptr state); - void parseFailed(Error error); - - not_null _player; - SharedState *_state = nullptr; - -}; - -} // namespace Lottie diff --git a/Telegram/SourceFiles/lottie/lottie_cache.cpp b/Telegram/SourceFiles/lottie/lottie_cache.cpp deleted file mode 100644 index 13d2723818..0000000000 --- a/Telegram/SourceFiles/lottie/lottie_cache.cpp +++ /dev/null @@ -1,626 +0,0 @@ -/* -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_cache.h" - -#include "lottie/lottie_frame_renderer.h" -#include "ffmpeg/ffmpeg_utility.h" -#include "base/bytes.h" - -#include -#include -#include -#include - -namespace Lottie { -namespace { - -constexpr auto kAlignStorage = 16; - -// Must not exceed max database allowed entry size. -constexpr auto kMaxCacheSize = 10 * 1024 * 1024; - -void Xor(EncodedStorage &to, const EncodedStorage &from) { - Expects(to.size() == from.size()); - - using Block = std::conditional_t< - sizeof(void*) == sizeof(uint64), - uint64, - uint32>; - constexpr auto kBlockSize = sizeof(Block); - const auto amount = from.size(); - const auto fromBytes = reinterpret_cast(from.data()); - const auto toBytes = reinterpret_cast(to.data()); - const auto blocks = amount / kBlockSize; - const auto fromBlocks = reinterpret_cast(fromBytes); - const auto toBlocks = reinterpret_cast(toBytes); - for (auto i = 0; i != blocks; ++i) { - toBlocks[i] ^= fromBlocks[i]; - } - const auto left = amount - (blocks * kBlockSize); - for (auto i = amount - left; i != amount; ++i) { - toBytes[i] ^= fromBytes[i]; - } -} - -bool UncompressToRaw(EncodedStorage &to, bytes::const_span from) { - if (from.empty() || from.size() > to.size()) { - return false; - } else if (from.size() == to.size()) { - memcpy(to.data(), from.data(), from.size()); - return true; - } - const auto result = LZ4_decompress_safe( - reinterpret_cast(from.data()), - to.data(), - from.size(), - to.size()); - return (result == to.size()); -} - -void CompressFromRaw(QByteArray &to, const EncodedStorage &from) { - const auto size = from.size(); - const auto max = sizeof(qint32) + LZ4_compressBound(size); - to.reserve(max); - to.resize(max); - const auto compressed = LZ4_compress_default( - from.data(), - to.data() + sizeof(qint32), - size, - to.size() - sizeof(qint32)); - Assert(compressed > 0); - if (compressed >= size + sizeof(qint32)) { - to.resize(size + sizeof(qint32)); - memcpy(to.data() + sizeof(qint32), from.data(), size); - } else { - to.resize(compressed + sizeof(qint32)); - } - const auto length = qint32(to.size() - sizeof(qint32)); - bytes::copy( - bytes::make_detached_span(to), - bytes::object_as_span(&length)); -} - -void CompressAndSwapFrame( - QByteArray &to, - QByteArray *additional, - EncodedStorage &frame, - EncodedStorage &previous) { - CompressFromRaw(to, frame); - std::swap(frame, previous); - if (!additional) { - return; - } - - // Check if XOR-d delta compresses better. - Xor(frame, previous); - CompressFromRaw(*additional, frame); - if (additional->size() >= to.size()) { - return; - } - std::swap(to, *additional); - - // Negative length means we XOR-d with the previous frame. - const auto negativeLength = -qint32(to.size() - sizeof(qint32)); - bytes::copy( - bytes::make_detached_span(to), - bytes::object_as_span(&negativeLength)); -} - -void DecodeYUV2RGB( - QImage &to, - const EncodedStorage &from, - FFmpeg::SwscalePointer &context) { - context = FFmpeg::MakeSwscalePointer( - to.size(), - AV_PIX_FMT_YUV420P, - to.size(), - AV_PIX_FMT_BGRA, - &context); - Assert(context != nullptr); - - // AV_NUM_DATA_POINTERS defined in AVFrame struct - const uint8_t *src[AV_NUM_DATA_POINTERS] = { - from.yData(), - from.uData(), - from.vData(), - nullptr - }; - int srcLineSize[AV_NUM_DATA_POINTERS] = { - from.yBytesPerLine(), - from.uBytesPerLine(), - from.vBytesPerLine(), - 0 - }; - uint8_t *dst[AV_NUM_DATA_POINTERS] = { to.bits(), nullptr }; - int dstLineSize[AV_NUM_DATA_POINTERS] = { to.bytesPerLine(), 0 }; - - const auto lines = sws_scale( - context.get(), - src, - srcLineSize, - 0, - to.height(), - dst, - dstLineSize); - - Ensures(lines == to.height()); -} - -void DecodeAlpha(QImage &to, const EncodedStorage &from) { - auto bytes = to.bits(); - auto alpha = from.aData(); - const auto perLine = to.bytesPerLine(); - const auto width = to.width(); - const auto height = to.height(); - for (auto i = 0; i != height; ++i) { - auto ints = reinterpret_cast(bytes); - const auto till = ints + width; - while (ints != till) { - const auto value = uint32(*alpha++); - *ints = (*ints & 0x00FFFFFFU) - | ((value & 0xF0U) << 24) - | ((value & 0xF0U) << 20); - ++ints; - *ints = (*ints & 0x00FFFFFFU) - | (value << 28) - | ((value & 0x0FU) << 24); - ++ints; - } - bytes += perLine; - } -} - -void Decode( - QImage &to, - const EncodedStorage &from, - const QSize &fromSize, - FFmpeg::SwscalePointer &context) { - if (!FFmpeg::GoodStorageForFrame(to, fromSize)) { - to = FFmpeg::CreateFrameStorage(fromSize); - } - DecodeYUV2RGB(to, from, context); - DecodeAlpha(to, from); - FFmpeg::PremultiplyInplace(to); -} - -void EncodeRGB2YUV( - EncodedStorage &to, - const QImage &from, - FFmpeg::SwscalePointer &context) { - context = FFmpeg::MakeSwscalePointer( - from.size(), - AV_PIX_FMT_BGRA, - from.size(), - AV_PIX_FMT_YUV420P, - &context); - Assert(context != nullptr); - - // AV_NUM_DATA_POINTERS defined in AVFrame struct - const uint8_t *src[AV_NUM_DATA_POINTERS] = { from.bits(), nullptr }; - int srcLineSize[AV_NUM_DATA_POINTERS] = { from.bytesPerLine(), 0 }; - uint8_t *dst[AV_NUM_DATA_POINTERS] = { - to.yData(), - to.uData(), - to.vData(), - nullptr - }; - int dstLineSize[AV_NUM_DATA_POINTERS] = { - to.yBytesPerLine(), - to.uBytesPerLine(), - to.vBytesPerLine(), - 0 - }; - - const auto lines = sws_scale( - context.get(), - src, - srcLineSize, - 0, - from.height(), - dst, - dstLineSize); - - Ensures(lines == from.height()); -} - -void EncodeAlpha(EncodedStorage &to, const QImage &from) { - auto bytes = from.bits(); - auto alpha = to.aData(); - const auto perLine = from.bytesPerLine(); - const auto width = from.width(); - const auto height = from.height(); - for (auto i = 0; i != height; ++i) { - auto ints = reinterpret_cast(bytes); - const auto till = ints + width; - for (; ints != till; ints += 2) { - *alpha++ = (((*ints) >> 24) & 0xF0U) | ((*(ints + 1)) >> 28); - } - bytes += perLine; - } -} - -void Encode( - EncodedStorage &to, - const QImage &from, - QImage &cache, - FFmpeg::SwscalePointer &context) { - FFmpeg::UnPremultiply(cache, from); - EncodeRGB2YUV(to, cache, context); - EncodeAlpha(to, cache); -} - -int YLineSize(int width) { - return ((width + kAlignStorage - 1) / kAlignStorage) * kAlignStorage; -} - -int UVLineSize(int width) { - return (((width / 2) + kAlignStorage - 1) / kAlignStorage) * kAlignStorage; -} - -int YSize(int width, int height) { - return YLineSize(width) * height; -} - -int UVSize(int width, int height) { - return UVLineSize(width) * (height / 2); -} - -int ASize(int width, int height) { - return (width * height) / 2; -} - -} // namespace - -void EncodedStorage::allocate(int width, int height) { - Expects((width % 2) == 0 && (height % 2) == 0); - - if (YSize(width, height) != YSize(_width, _height) - || UVSize(width, height) != UVSize(_width, _height) - || ASize(width, height) != ASize(_width, _height)) { - _width = width; - _height = height; - reallocate(); - } -} - -void EncodedStorage::reallocate() { - const auto total = YSize(_width, _height) - + 2 * UVSize(_width, _height) - + ASize(_width, _height); - _data = QByteArray(total + kAlignStorage - 1, 0); -} - -int EncodedStorage::width() const { - return _width; -} - -int EncodedStorage::height() const { - return _height; -} - -int EncodedStorage::size() const { - return YSize(_width, _height) - + 2 * UVSize(_width, _height) - + ASize(_width, _height); -} - -char *EncodedStorage::data() { - const auto result = reinterpret_cast(_data.data()); - return reinterpret_cast(kAlignStorage - * ((result + kAlignStorage - 1) / kAlignStorage)); -} - -const char *EncodedStorage::data() const { - const auto result = reinterpret_cast(_data.data()); - return reinterpret_cast(kAlignStorage - * ((result + kAlignStorage - 1) / kAlignStorage)); -} - -uint8_t *EncodedStorage::yData() { - return reinterpret_cast(data()); -} - -const uint8_t *EncodedStorage::yData() const { - return reinterpret_cast(data()); -} - -int EncodedStorage::yBytesPerLine() const { - return YLineSize(_width); -} - -uint8_t *EncodedStorage::uData() { - return yData() + YSize(_width, _height); -} - -const uint8_t *EncodedStorage::uData() const { - return yData() + YSize(_width, _height); -} - -int EncodedStorage::uBytesPerLine() const { - return UVLineSize(_width); -} - -uint8_t *EncodedStorage::vData() { - return uData() + UVSize(_width, _height); -} - -const uint8_t *EncodedStorage::vData() const { - return uData() + UVSize(_width, _height); -} - -int EncodedStorage::vBytesPerLine() const { - return UVLineSize(_width); -} - -uint8_t *EncodedStorage::aData() { - return uData() + 2 * UVSize(_width, _height); -} - -const uint8_t *EncodedStorage::aData() const { - return uData() + 2 * UVSize(_width, _height); -} - -int EncodedStorage::aBytesPerLine() const { - return _width / 2; -} - -Cache::Cache( - const QByteArray &data, - const FrameRequest &request, - FnMut put) -: _data(data) -, _put(std::move(put)) { - if (!readHeader(request)) { - _framesReady = 0; - _data = QByteArray(); - } -} - -void Cache::init( - QSize original, - int frameRate, - int framesCount, - const FrameRequest &request) { - _size = request.size(original); - _original = original; - _frameRate = frameRate; - _framesCount = framesCount; - _framesReady = 0; - prepareBuffers(); -} - -int Cache::frameRate() const { - return _frameRate; -} - -int Cache::framesReady() const { - return _framesReady; -} - -int Cache::framesCount() const { - return _framesCount; -} - -QSize Cache::originalSize() const { - return _original; -} - -bool Cache::readHeader(const FrameRequest &request) { - if (_data.isEmpty()) { - return false; - - } - QDataStream stream(&_data, QIODevice::ReadOnly); - - auto encoder = qint32(0); - stream >> encoder; - if (static_cast(encoder) != Encoder::YUV420A4_LZ4) { - return false; - } - auto size = QSize(); - auto original = QSize(); - auto frameRate = qint32(0); - auto framesCount = qint32(0); - auto framesReady = qint32(0); - stream - >> size - >> original - >> frameRate - >> framesCount - >> framesReady; - if (stream.status() != QDataStream::Ok - || original.isEmpty() - || (original.width() > kMaxSize) - || (original.height() > kMaxSize) - || (frameRate <= 0) - || (frameRate > kNormalFrameRate && frameRate != kMaxFrameRate) - || (framesCount <= 0) - || (framesCount > kMaxFramesCount) - || (framesReady <= 0) - || (framesReady > framesCount) - || request.size(original) != size) { - return false; - } - _encoder = static_cast(encoder); - _size = size; - _original = original; - _frameRate = frameRate; - _framesCount = framesCount; - _framesReady = framesReady; - prepareBuffers(); - return renderFrame(_firstFrame, request, 0); -} - -QImage Cache::takeFirstFrame() { - return std::move(_firstFrame); -} - -bool Cache::renderFrame( - QImage &to, - const FrameRequest &request, - int index) { - Expects(index >= _framesReady - || index == _offsetFrameIndex - || index == 0); - - if (index >= _framesReady) { - return false; - } else if (request.size(_original) != _size) { - return false; - } else if (index == 0) { - _offset = headerSize(); - _offsetFrameIndex = 0; - } - const auto [ok, xored] = readCompressedFrame(); - if (!ok || (xored && index == 0)) { - _framesReady = 0; - _data = QByteArray(); - return false; - } else if (index + 1 == _framesReady && _data.size() > _offset) { - _data.resize(_offset); - } - if (xored) { - Xor(_previous, _uncompressed); - } else { - std::swap(_uncompressed, _previous); - } - Decode(to, _previous, _size, _decodeContext); - return true; -} - -void Cache::appendFrame( - const QImage &frame, - const FrameRequest &request, - int index) { - if (request.size(_original) != _size) { - _framesReady = 0; - _data = QByteArray(); - } - if (index != _framesReady) { - return; - } - if (index == 0) { - _size = request.size(_original); - _encode = EncodeFields(); - _encode.compressedFrames.reserve(_framesCount); - prepareBuffers(); - } - Assert(frame.size() == _size); - Encode(_uncompressed, frame, _encode.cache, _encode.context); - CompressAndSwapFrame( - _encode.compressBuffer, - (index != 0) ? &_encode.xorCompressBuffer : nullptr, - _uncompressed, - _previous); - const auto compressed = _encode.compressBuffer; - const auto nowSize = (_data.isEmpty() ? headerSize() : _data.size()) - + _encode.totalSize; - const auto totalSize = nowSize + compressed.size(); - if (nowSize <= kMaxCacheSize && totalSize > kMaxCacheSize) { - // Write to cache while we still can. - finalizeEncoding(); - } - _encode.totalSize += compressed.size(); - _encode.compressedFrames.push_back(compressed); - _encode.compressedFrames.back().detach(); - if (++_framesReady == _framesCount) { - finalizeEncoding(); - } -} - -void Cache::finalizeEncoding() { - if (_encode.compressedFrames.empty()) { - return; - } - const auto size = (_data.isEmpty() ? headerSize() : _data.size()) - + _encode.totalSize; - if (_data.isEmpty()) { - _data.reserve(size); - writeHeader(); - } else { - updateFramesReadyCount(); - } - const auto offset = _data.size(); - _data.resize(size); - auto to = _data.data() + offset; - for (const auto &block : _encode.compressedFrames) { - const auto amount = qint32(block.size()); - memcpy(to, block.data(), amount); - to += amount; - } - if (_data.size() <= kMaxCacheSize) { - _put(QByteArray(_data)); - } - _encode = EncodeFields(); -} - -int Cache::headerSize() const { - return 8 * sizeof(qint32); -} - -void Cache::writeHeader() { - Expects(_data.isEmpty()); - - QDataStream stream(&_data, QIODevice::WriteOnly); - - stream - << static_cast(_encoder) - << _size - << _original - << qint32(_frameRate) - << qint32(_framesCount) - << qint32(_framesReady); -} - -void Cache::updateFramesReadyCount() { - Expects(_data.size() >= headerSize()); - - QDataStream stream(&_data, QIODevice::ReadWrite); - stream.device()->seek(headerSize() - sizeof(qint32)); - stream << qint32(_framesReady); -} - -void Cache::prepareBuffers() { - // 12 bit per pixel in YUV420P. - const auto bytesPerLine = _size.width(); - - _uncompressed.allocate(bytesPerLine, _size.height()); - _previous.allocate(bytesPerLine, _size.height()); -} - -Cache::ReadResult Cache::readCompressedFrame() { - if (_data.size() < _offset) { - return { false }; - } - auto length = qint32(0); - const auto part = bytes::make_span(_data).subspan(_offset); - if (part.size() < sizeof(length)) { - return { false }; - } - bytes::copy( - bytes::object_as_span(&length), - part.subspan(0, sizeof(length))); - const auto bytes = part.subspan(sizeof(length)); - - const auto xored = (length < 0); - if (xored) { - length = -length; - } - _offset += sizeof(length) + length; - ++_offsetFrameIndex; - const auto ok = (length <= bytes.size()) - ? UncompressToRaw(_uncompressed, bytes.subspan(0, length)) - : false; - return { ok, xored }; -} - -Cache::~Cache() { - finalizeEncoding(); -} - -} // namespace Lottie diff --git a/Telegram/SourceFiles/lottie/lottie_cache.h b/Telegram/SourceFiles/lottie/lottie_cache.h deleted file mode 100644 index bb8cc2bb2d..0000000000 --- a/Telegram/SourceFiles/lottie/lottie_cache.h +++ /dev/null @@ -1,126 +0,0 @@ -/* -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 -*/ -#pragma once - -#include "ffmpeg/ffmpeg_utility.h" - -#include -#include -#include - -namespace Lottie { - -struct FrameRequest; - -class EncodedStorage { -public: - void allocate(int width, int height); - - int width() const; - int height() const; - - char *data(); - const char *data() const; - int size() const; - - uint8_t *yData(); - const uint8_t *yData() const; - int yBytesPerLine() const; - uint8_t *uData(); - const uint8_t *uData() const; - int uBytesPerLine() const; - uint8_t *vData(); - const uint8_t *vData() const; - int vBytesPerLine() const; - uint8_t *aData(); - const uint8_t *aData() const; - int aBytesPerLine() const; - -private: - void reallocate(); - - int _width = 0; - int _height = 0; - QByteArray _data; - -}; - -class Cache { -public: - enum class Encoder : qint8 { - YUV420A4_LZ4, - }; - - Cache( - const QByteArray &data, - const FrameRequest &request, - FnMut put); - - void init( - QSize original, - int frameRate, - int framesCount, - const FrameRequest &request); - [[nodiscard]] int frameRate() const; - [[nodiscard]] int framesReady() const; - [[nodiscard]] int framesCount() const; - [[nodiscard]] QSize originalSize() const; - [[nodiscard]] QImage takeFirstFrame(); - - [[nodiscard]] bool renderFrame( - QImage &to, - const FrameRequest &request, - int index); - void appendFrame( - const QImage &frame, - const FrameRequest &request, - int index); - - ~Cache(); - -private: - struct ReadResult { - bool ok = false; - bool xored = false; - }; - struct EncodeFields { - std::vector compressedFrames; - QByteArray compressBuffer; - QByteArray xorCompressBuffer; - QImage cache; - FFmpeg::SwscalePointer context; - int totalSize = 0; - }; - int headerSize() const; - void prepareBuffers(); - void finalizeEncoding(); - - void writeHeader(); - void updateFramesReadyCount(); - [[nodiscard]] bool readHeader(const FrameRequest &request); - [[nodiscard]] ReadResult readCompressedFrame(); - - QByteArray _data; - EncodeFields _encode; - QSize _size; - QSize _original; - EncodedStorage _uncompressed; - EncodedStorage _previous; - FFmpeg::SwscalePointer _decodeContext; - QImage _firstFrame; - int _frameRate = 0; - int _framesCount = 0; - int _framesReady = 0; - int _offset = 0; - int _offsetFrameIndex = 0; - Encoder _encoder = Encoder::YUV420A4_LZ4; - FnMut _put; - -}; - -} // namespace Lottie diff --git a/Telegram/SourceFiles/lottie/lottie_common.cpp b/Telegram/SourceFiles/lottie/lottie_common.cpp deleted file mode 100644 index 32068634d8..0000000000 --- a/Telegram/SourceFiles/lottie/lottie_common.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/* -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_common.h" - -#include "base/algorithm.h" - -#include - -namespace Lottie { -namespace { - -QByteArray ReadFile(const QString &filepath) { - auto f = QFile(filepath); - return (f.size() <= kMaxFileSize && f.open(QIODevice::ReadOnly)) - ? f.readAll() - : QByteArray(); -} - -} // namespace - -QSize FrameRequest::size(const QSize &original) const { - Expects(!empty()); - - const auto result = original.scaled(box, Qt::KeepAspectRatio); - const auto skipw = result.width() % 8; - const auto skiph = result.height() % 8; - return QSize( - std::max(result.width() - skipw, 8), - std::max(result.height() - skiph, 8)); -} - -QByteArray ReadContent(const QByteArray &data, const QString &filepath) { - return data.isEmpty() ? ReadFile(filepath) : base::duplicate(data); -} - -} // namespace Lottie diff --git a/Telegram/SourceFiles/lottie/lottie_common.h b/Telegram/SourceFiles/lottie/lottie_common.h deleted file mode 100644 index bfa875cf99..0000000000 --- a/Telegram/SourceFiles/lottie/lottie_common.h +++ /dev/null @@ -1,66 +0,0 @@ -/* -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 -*/ -#pragma once - -#include "base/basic_types.h" -#include "base/variant.h" - -#include -#include -#include -#include - -namespace Lottie { - -inline constexpr auto kTimeUnknown = std::numeric_limits::min(); -inline constexpr auto kMaxFileSize = 2 * 1024 * 1024; - -class Animation; - -struct Information { - int frameRate = 0; - int framesCount = 0; - QSize size; -}; - -enum class Error { - ParseFailed, - NotSupported, -}; - -struct FrameRequest { - QSize box; - std::optional colored; - - [[nodiscard]] bool empty() const { - return box.isEmpty(); - } - [[nodiscard]] QSize size(const QSize &original) const; - - [[nodiscard]] bool operator==(const FrameRequest &other) const { - return (box == other.box) - && (colored == other.colored); - } - [[nodiscard]] bool operator!=(const FrameRequest &other) const { - return !(*this == other); - } -}; - -enum class Quality : char { - Default, - High, -}; - -struct ColorReplacements { - std::vector> replacements; - uint8 tag = 0; -}; - -QByteArray ReadContent(const QByteArray &data, const QString &filepath); - -} // namespace Lottie diff --git a/Telegram/SourceFiles/lottie/lottie_frame_renderer.cpp b/Telegram/SourceFiles/lottie/lottie_frame_renderer.cpp deleted file mode 100644 index caae93a9d5..0000000000 --- a/Telegram/SourceFiles/lottie/lottie_frame_renderer.cpp +++ /dev/null @@ -1,612 +0,0 @@ -/* -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_frame_renderer.h" - -#include "lottie/lottie_player.h" -#include "lottie/lottie_animation.h" -#include "lottie/lottie_cache.h" -#include "base/flat_map.h" -#include "logs.h" - -#include -#include -#include -#include - -namespace Images { -QImage prepareColored(QColor add, QImage image); -} // namespace Images - -namespace Lottie { -namespace { - -std::weak_ptr GlobalInstance; - -constexpr auto kImageFormat = QImage::Format_ARGB32_Premultiplied; - -bool GoodStorageForFrame(const QImage &storage, QSize size) { - return !storage.isNull() - && (storage.format() == kImageFormat) - && (storage.size() == size) - && storage.isDetached(); -} - -QImage CreateFrameStorage(QSize size) { - return QImage(size, kImageFormat); -} - -int GetLottieFrameRate(not_null animation, Quality quality) { - const auto rate = int(qRound(animation->frameRate())); - return (quality == Quality::Default && rate == 60) ? (rate / 2) : rate; -} - -int GetLottieFramesCount(not_null animation, Quality quality) { - const auto rate = int(qRound(animation->frameRate())); - const auto count = int(animation->totalFrame()); - return (quality == Quality::Default && rate == 60) ? (count / 2) : count; -} - -int GetLottieFrameIndex(not_null animation, Quality quality, int index) { - const auto rate = int(qRound(animation->frameRate())); - return (quality == Quality::Default && rate == 60) ? (index * 2) : index; -} - -} // namespace - -class FrameRendererObject final { -public: - explicit FrameRendererObject( - crl::weak_on_queue weak); - - void append( - std::unique_ptr entry, - const FrameRequest &request); - void frameShown(); - void updateFrameRequest( - not_null entry, - const FrameRequest &request); - void remove(not_null entry); - -private: - struct Entry { - std::unique_ptr state; - FrameRequest request; - }; - - static not_null StateFromEntry(const Entry &entry) { - return entry.state.get(); - } - - void queueGenerateFrames(); - void generateFrames(); - - crl::weak_on_queue _weak; - std::vector _entries; - bool _queued = false; - -}; - -[[nodiscard]] bool GoodForRequest( - const QImage &image, - const FrameRequest &request) { - if (request.box.isEmpty()) { - return true; - } else if (request.colored.has_value()) { - return false; - } - const auto size = image.size(); - return (request.box.width() == size.width()) - || (request.box.height() == size.height()); -} - -[[nodiscard]] QImage PrepareByRequest( - const QImage &original, - const FrameRequest &request, - QImage storage) { - Expects(!request.box.isEmpty()); - - const auto size = request.size(original.size()); - if (!GoodStorageForFrame(storage, size)) { - storage = CreateFrameStorage(size); - } - storage.fill(Qt::transparent); - - { - QPainter p(&storage); - p.setRenderHint(QPainter::Antialiasing); - p.setRenderHint(QPainter::SmoothPixmapTransform); - p.setRenderHint(QPainter::HighQualityAntialiasing); - p.drawImage(QRect(QPoint(), size), original); - } - if (request.colored.has_value()) { - storage = Images::prepareColored(*request.colored, std::move(storage)); - } - return storage; -} - -QImage PrepareFrameByRequest( - not_null frame, - bool useExistingPrepared = false) { - Expects(!frame->original.isNull()); - - if (GoodForRequest(frame->original, frame->request)) { - return frame->original; - } else if (frame->prepared.isNull() || !useExistingPrepared) { - frame->prepared = PrepareByRequest( - frame->original, - frame->request, - std::move(frame->prepared)); - } - return frame->prepared; -} - -FrameRendererObject::FrameRendererObject( - crl::weak_on_queue weak) -: _weak(std::move(weak)) { -} - -void FrameRendererObject::append( - std::unique_ptr state, - const FrameRequest &request) { - _entries.push_back({ std::move(state), request }); - queueGenerateFrames(); -} - -void FrameRendererObject::frameShown() { - queueGenerateFrames(); -} - -void FrameRendererObject::updateFrameRequest( - not_null entry, - const FrameRequest &request) { - const auto i = ranges::find(_entries, entry, &StateFromEntry); - Assert(i != end(_entries)); - i->request = request; -} - -void FrameRendererObject::remove(not_null entry) { - const auto i = ranges::find(_entries, entry, &StateFromEntry); - Assert(i != end(_entries)); - _entries.erase(i); -} - -void FrameRendererObject::generateFrames() { - auto players = base::flat_map>(); - const auto renderOne = [&](const Entry &entry) { - const auto result = entry.state->renderNextFrame(entry.request); - if (const auto player = result.notify.get()) { - players.emplace(player, result.notify); - } - return result.rendered; - }; - const auto rendered = ranges::count_if(_entries, renderOne); - if (rendered) { - if (!players.empty()) { - crl::on_main([players = std::move(players)] { - for (const auto &[player, weak] : players) { - if (weak) { - weak->checkStep(); - } - } - }); - } - queueGenerateFrames(); - } -} - -void FrameRendererObject::queueGenerateFrames() { - if (_queued) { - return; - } - _queued = true; - _weak.with([](FrameRendererObject &that) { - that._queued = false; - that.generateFrames(); - }); -} - - -Information SharedState::CalculateInformation( - Quality quality, - rlottie::Animation *animation, - Cache *cache) { - Expects(animation != nullptr || cache != nullptr); - - auto width = size_t(0); - auto height = size_t(0); - if (animation) { - animation->size(width, height); - } else { - width = cache->originalSize().width(); - height = cache->originalSize().height(); - } - const auto rate = animation - ? GetLottieFrameRate(animation, quality) - : cache->frameRate(); - const auto count = animation - ? GetLottieFramesCount(animation, quality) - : cache->framesCount(); - - auto result = Information(); - result.size = QSize( - (width > 0 && width <= kMaxSize) ? int(width) : 0, - (height > 0 && height <= kMaxSize) ? int(height) : 0); - result.frameRate = (rate > 0 && rate <= kMaxFrameRate) ? int(rate) : 0; - result.framesCount = (count > 0 && count <= kMaxFramesCount) - ? int(count) - : 0; - return result; -} - -SharedState::SharedState( - std::unique_ptr animation, - const FrameRequest &request, - Quality quality) -: _info(CalculateInformation(quality, animation.get(), nullptr)) -, _quality(quality) -, _animation(std::move(animation)) { - construct(request); -} - -SharedState::SharedState( - const QByteArray &content, - const ColorReplacements *replacements, - std::unique_ptr animation, - std::unique_ptr cache, - const FrameRequest &request, - Quality quality) -: _info(CalculateInformation(quality, animation.get(), cache.get())) -, _quality(quality) -, _cache(std::move(cache)) -, _animation(std::move(animation)) -, _content(content) -, _replacements(replacements) { - construct(request); -} - -void SharedState::construct(const FrameRequest &request) { - if (!isValid()) { - return; - } - auto cover = _cache ? _cache->takeFirstFrame() : QImage(); - if (!cover.isNull()) { - init(std::move(cover), request); - return; - } - if (_cache) { - _cache->init( - _info.size, - _info.frameRate, - _info.framesCount, - request); - } - renderFrame(cover, request, 0); - init(std::move(cover), request); -} - -bool SharedState::isValid() const { - return (_info.framesCount > 0) - && (_info.frameRate > 0) - && !_info.size.isEmpty(); -} - -void SharedState::renderFrame( - QImage &image, - const FrameRequest &request, - int index) { - if (!isValid()) { - return; - } - - const auto size = request.box.isEmpty() - ? _info.size - : request.size(_info.size); - if (!GoodStorageForFrame(image, size)) { - image = CreateFrameStorage(size); - } - if (_cache && _cache->renderFrame(image, request, index)) { - return; - } else if (!_animation) { - _animation = details::CreateFromContent(_content, _replacements); - } - - image.fill(Qt::transparent); - auto surface = rlottie::Surface( - reinterpret_cast(image.bits()), - image.width(), - image.height(), - image.bytesPerLine()); - _animation->renderSync( - GetLottieFrameIndex(_animation.get(), _quality, index), - surface); - if (_cache) { - _cache->appendFrame(image, request, index); - if (_cache->framesReady() == _cache->framesCount()) { - _animation = nullptr; - } - } -} - -void SharedState::init(QImage cover, const FrameRequest &request) { - Expects(!initialized()); - - _frames[0].request = request; - _frames[0].original = std::move(cover); -} - -void SharedState::start( - not_null owner, - crl::time started, - crl::time delay, - int skippedFrames) { - _owner = owner; - _started = started; - _delay = delay; - _skippedFrames = skippedFrames; - _counter.store(0, std::memory_order_release); -} - -bool IsRendered(not_null frame) { - return (frame->displayed == kTimeUnknown); -} - -void SharedState::renderNextFrame( - not_null frame, - const FrameRequest &request) { - Expects(_info.framesCount > 0); - - renderFrame( - frame->original, - request, - (++_frameIndex) % _info.framesCount); - frame->request = request; - PrepareFrameByRequest(frame); - frame->index = _frameIndex; - frame->displayed = kTimeUnknown; -} - -auto SharedState::renderNextFrame(const FrameRequest &request) --> RenderResult { - const auto prerender = [&](int index) -> RenderResult { - const auto frame = getFrame(index); - const auto next = getFrame((index + 1) % kFramesCount); - if (!IsRendered(frame)) { - renderNextFrame(frame, request); - return { true }; - } else if (!IsRendered(next)) { - renderNextFrame(next, request); - return { true }; - } - return { false }; - }; - const auto present = [&](int counter, int index) -> RenderResult { - const auto frame = getFrame(index); - if (!IsRendered(frame)) { - renderNextFrame(frame, request); - } - frame->display = countFrameDisplayTime(frame->index); - - // Release this frame to the main thread for rendering. - _counter.store( - (counter + 1) % (2 * kFramesCount), - std::memory_order_release); - return { true, _owner }; - }; - - switch (counter()) { - case 0: return present(0, 1); - case 1: return prerender(2); - case 2: return present(2, 2); - case 3: return prerender(3); - case 4: return present(4, 3); - case 5: return prerender(0); - case 6: return present(6, 0); - case 7: return prerender(1); - } - Unexpected("Counter value in Lottie::SharedState::renderNextFrame."); -} - -crl::time SharedState::countFrameDisplayTime(int index) const { - return _started - + _delay - + crl::time(1000) * (_skippedFrames + index) / _info.frameRate; -} - -int SharedState::counter() const { - return _counter.load(std::memory_order_acquire); -} - -bool SharedState::initialized() const { - return (counter() != kCounterUninitialized); -} - -not_null SharedState::getFrame(int index) { - Expects(index >= 0 && index < kFramesCount); - - return &_frames[index]; -} - -not_null SharedState::getFrame(int index) const { - Expects(index >= 0 && index < kFramesCount); - - return &_frames[index]; -} - -Information SharedState::information() const { - return isValid() ? _info : Information(); -} - -not_null SharedState::frameForPaint() { - const auto result = getFrame(counter() / 2); - Assert(!result->original.isNull()); - Assert(result->displayed != kTimeUnknown); - - return result; -} - -int SharedState::framesCount() const { - return _info.framesCount; -} - -crl::time SharedState::nextFrameDisplayTime() const { - const auto frameDisplayTime = [&](int counter) { - const auto next = (counter + 1) % (2 * kFramesCount); - const auto index = next / 2; - const auto frame = getFrame(index); - if (frame->displayed != kTimeUnknown) { - // Frame already displayed, but not yet shown. - return kFrameDisplayTimeAlreadyDone; - } - Assert(IsRendered(frame)); - Assert(frame->display != kTimeUnknown); - - return frame->display; - }; - - switch (counter()) { - case 0: return kTimeUnknown; - case 1: return frameDisplayTime(1); - case 2: return kTimeUnknown; - case 3: return frameDisplayTime(3); - case 4: return kTimeUnknown; - case 5: return frameDisplayTime(5); - case 6: return kTimeUnknown; - case 7: return frameDisplayTime(7); - } - Unexpected("Counter value in VideoTrack::Shared::nextFrameDisplayTime."); -} - -void SharedState::addTimelineDelay(crl::time delayed, int skippedFrames) { - if (!delayed && !skippedFrames) { - return; - } - - const auto recountCurrentFrame = [&](int counter) { - _delay += delayed; - _skippedFrames += skippedFrames; - - const auto next = (counter + 1) % (2 * kFramesCount); - const auto index = next / 2; - const auto frame = getFrame(index); - if (frame->displayed != kTimeUnknown) { - // Frame already displayed. - return; - } - Assert(IsRendered(frame)); - Assert(frame->display != kTimeUnknown); - frame->display = countFrameDisplayTime(frame->index); - }; - - switch (counter()) { - case 0: Unexpected("Value 0 in SharedState::addTimelineDelay."); - case 1: return recountCurrentFrame(1); - case 2: Unexpected("Value 2 in SharedState::addTimelineDelay."); - case 3: return recountCurrentFrame(3); - case 4: Unexpected("Value 4 in SharedState::addTimelineDelay."); - case 5: return recountCurrentFrame(5); - case 6: Unexpected("Value 6 in SharedState::addTimelineDelay."); - case 7: return recountCurrentFrame(7); - } - Unexpected("Counter value in VideoTrack::Shared::nextFrameDisplayTime."); -} - -void SharedState::markFrameDisplayed(crl::time now) { - const auto mark = [&](int counter) { - const auto next = (counter + 1) % (2 * kFramesCount); - const auto index = next / 2; - const auto frame = getFrame(index); - if (frame->displayed == kTimeUnknown) { - frame->displayed = now; - } - }; - - switch (counter()) { - case 0: Unexpected("Value 0 in SharedState::markFrameDisplayed."); - case 1: return mark(1); - case 2: Unexpected("Value 2 in SharedState::markFrameDisplayed."); - case 3: return mark(3); - case 4: Unexpected("Value 4 in SharedState::markFrameDisplayed."); - case 5: return mark(5); - case 6: Unexpected("Value 6 in SharedState::markFrameDisplayed."); - case 7: return mark(7); - } - Unexpected("Counter value in Lottie::SharedState::markFrameDisplayed."); -} - -bool SharedState::markFrameShown() { - const auto jump = [&](int counter) { - const auto next = (counter + 1) % (2 * kFramesCount); - const auto index = next / 2; - const auto frame = getFrame(index); - if (frame->displayed == kTimeUnknown) { - return false; - } - _counter.store( - next, - std::memory_order_release); - return true; - }; - - switch (counter()) { - case 0: return false; - case 1: return jump(1); - case 2: return false; - case 3: return jump(3); - case 4: return false; - case 5: return jump(5); - case 6: return false; - case 7: return jump(7); - } - Unexpected("Counter value in Lottie::SharedState::markFrameShown."); -} - -SharedState::~SharedState() = default; - -std::shared_ptr FrameRenderer::CreateIndependent() { - return std::make_shared(); -} - -std::shared_ptr FrameRenderer::Instance() { - if (auto result = GlobalInstance.lock()) { - return result; - } - auto result = CreateIndependent(); - GlobalInstance = result; - return result; -} - -void FrameRenderer::append( - std::unique_ptr entry, - const FrameRequest &request) { - _wrapped.with([=, entry = std::move(entry)]( - FrameRendererObject &unwrapped) mutable { - unwrapped.append(std::move(entry), request); - }); -} - -void FrameRenderer::frameShown() { - _wrapped.with([=](FrameRendererObject &unwrapped) { - unwrapped.frameShown(); - }); -} - -void FrameRenderer::updateFrameRequest( - not_null entry, - const FrameRequest &request) { - _wrapped.with([=](FrameRendererObject &unwrapped) { - unwrapped.updateFrameRequest(entry, request); - }); -} - -void FrameRenderer::remove(not_null entry) { - _wrapped.with([=](FrameRendererObject &unwrapped) { - unwrapped.remove(entry); - }); -} - -} // namespace Lottie diff --git a/Telegram/SourceFiles/lottie/lottie_frame_renderer.h b/Telegram/SourceFiles/lottie/lottie_frame_renderer.h deleted file mode 100644 index 60d0336c1d..0000000000 --- a/Telegram/SourceFiles/lottie/lottie_frame_renderer.h +++ /dev/null @@ -1,160 +0,0 @@ -/* -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 -*/ -#pragma once - -#include "base/basic_types.h" -#include "base/weak_ptr.h" -#include "lottie/lottie_common.h" - -#include -#include -#include -#include -#include - -namespace rlottie { -class Animation; -} // namespace rlottie - -namespace Lottie { - -// Frame rate can be 1, 2, ... , 29, 30 or 60. -inline constexpr auto kNormalFrameRate = 30; -inline constexpr auto kMaxFrameRate = 60; -inline constexpr auto kMaxSize = 4096; -inline constexpr auto kMaxFramesCount = 210; -inline constexpr auto kFrameDisplayTimeAlreadyDone - = std::numeric_limits::max(); -inline constexpr auto kDisplayedInitial = crl::time(-1); - -class Player; -class Cache; - -struct Frame { - QImage original; - crl::time displayed = kDisplayedInitial; - crl::time display = kTimeUnknown; - int index = 0; - - FrameRequest request; - QImage prepared; -}; - -QImage PrepareFrameByRequest( - not_null frame, - bool useExistingPrepared); - -class SharedState { -public: - SharedState( - std::unique_ptr animation, - const FrameRequest &request, - Quality quality); - SharedState( - const QByteArray &content, - const ColorReplacements *replacements, - std::unique_ptr animation, - std::unique_ptr cache, - const FrameRequest &request, - Quality quality); - - void start( - not_null owner, - crl::time now, - crl::time delay = 0, - int skippedFrames = 0); - - [[nodiscard]] Information information() const; - [[nodiscard]] bool initialized() const; - - [[nodiscard]] not_null frameForPaint(); - [[nodiscard]] int framesCount() const; - [[nodiscard]] crl::time nextFrameDisplayTime() const; - void addTimelineDelay(crl::time delayed, int skippedFrames = 0); - void markFrameDisplayed(crl::time now); - bool markFrameShown(); - - void renderFrame(QImage &image, const FrameRequest &request, int index); - - struct RenderResult { - bool rendered = false; - base::weak_ptr notify; - }; - [[nodiscard]] RenderResult renderNextFrame(const FrameRequest &request); - - ~SharedState(); - -private: - static Information CalculateInformation( - Quality quality, - rlottie::Animation *animation, - Cache *cache); - - void construct(const FrameRequest &request); - bool isValid() const; - void init(QImage cover, const FrameRequest &request); - void renderNextFrame( - not_null frame, - const FrameRequest &request); - [[nodiscard]] crl::time countFrameDisplayTime(int index) const; - [[nodiscard]] not_null getFrame(int index); - [[nodiscard]] not_null getFrame(int index) const; - [[nodiscard]] int counter() const; - - // crl::queue changes 0,2,4,6 to 1,3,5,7. - // main thread changes 1,3,5,7 to 2,4,6,0. - static constexpr auto kCounterUninitialized = -1; - std::atomic _counter = kCounterUninitialized; - - static constexpr auto kFramesCount = 4; - std::array _frames; - - base::weak_ptr _owner; - crl::time _started = kTimeUnknown; - - // (_counter % 2) == 1 main thread can write _delay. - // (_counter % 2) == 0 crl::queue can read _delay. - crl::time _delay = kTimeUnknown; - - int _frameIndex = 0; - int _skippedFrames = 0; - const Information _info; - const Quality _quality = Quality::Default; - - const std::unique_ptr _cache; - - std::unique_ptr _animation; - const QByteArray _content; - const ColorReplacements *_replacements = nullptr; - -}; - -class FrameRendererObject; - -class FrameRenderer final { -public: - static std::shared_ptr CreateIndependent(); - static std::shared_ptr Instance(); - - void append( - std::unique_ptr entry, - const FrameRequest &request); - - void updateFrameRequest( - not_null entry, - const FrameRequest &request); - void frameShown(); - void remove(not_null state); - -private: - using Implementation = FrameRendererObject; - crl::object_on_queue _wrapped; - -}; - -} // namespace Lottie diff --git a/Telegram/SourceFiles/lottie/lottie_multi_player.cpp b/Telegram/SourceFiles/lottie/lottie_multi_player.cpp deleted file mode 100644 index 8067b6c4b6..0000000000 --- a/Telegram/SourceFiles/lottie/lottie_multi_player.cpp +++ /dev/null @@ -1,389 +0,0 @@ -/* -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_multi_player.h" - -#include "lottie/lottie_frame_renderer.h" -#include "lottie/lottie_animation.h" -#include "logs.h" - -#include - -namespace Lottie { - -MultiPlayer::MultiPlayer( - Quality quality, - std::shared_ptr renderer) -: _quality(quality) -, _timer([=] { checkNextFrameRender(); }) -, _renderer(renderer ? std::move(renderer) : FrameRenderer::Instance()) { - crl::on_main_update_requests( - ) | rpl::start_with_next([=] { - checkStep(); - }, _lifetime); -} - -MultiPlayer::~MultiPlayer() { - for (const auto &[animation, state] : _active) { - _renderer->remove(state); - } - for (const auto &[animation, info] : _paused) { - _renderer->remove(info.state); - } -} - -not_null MultiPlayer::append( - FnMut)> get, // Main thread. - FnMut put, // Unknown thread. - const QByteArray &content, - const FrameRequest &request) { - _animations.push_back(std::make_unique( - this, - std::move(get), - std::move(put), - content, - request, - _quality)); - return _animations.back().get(); -} - -not_null MultiPlayer::append( - const QByteArray &content, - const FrameRequest &request) { - _animations.push_back(std::make_unique( - this, - content, - request, - _quality)); - return _animations.back().get(); -} - -void MultiPlayer::startAtRightTime(std::unique_ptr state) { - if (_started == kTimeUnknown) { - _started = crl::now(); - _lastSyncTime = kTimeUnknown; - _delay = 0; - } - const auto lastSyncTime = (_lastSyncTime != kTimeUnknown) - ? _lastSyncTime - : _started; - const auto frameIndex = countFrameIndex( - state.get(), - lastSyncTime, - _delay); - state->start(this, _started, _delay, frameIndex); - const auto request = state->frameForPaint()->request; - _renderer->append(std::move(state), request); -} - -int MultiPlayer::countFrameIndex( - not_null state, - crl::time time, - crl::time delay) const { - Expects(time != kTimeUnknown); - - const auto rate = state->information().frameRate; - Assert(rate != 0); - - const auto framesTime = time - _started - delay; - return ((framesTime + 1) * rate - 1) / 1000; -} - -void MultiPlayer::start( - not_null animation, - std::unique_ptr state) { - Expects(state != nullptr); - - const auto paused = _pausedBeforeStart.remove(animation); - auto info = StartingInfo{ std::move(state), paused }; - if (_active.empty() - || (_lastSyncTime == kTimeUnknown - && _nextFrameTime == kTimeUnknown)) { - addNewToActive(animation, std::move(info)); - } else { - // We always try to mark as shown at the same time, so we start a new - // animation at the same time we mark all existing as shown. - _pendingToStart.emplace(animation, std::move(info)); - } - _updates.fire({}); -} - -void MultiPlayer::addNewToActive( - not_null animation, - StartingInfo &&info) { - _active.emplace(animation, info.state.get()); - startAtRightTime(std::move(info.state)); - if (info.paused) { - _pendingPause.emplace(animation); - } -} - -void MultiPlayer::processPending() { - Expects(_lastSyncTime != kTimeUnknown); - - for (const auto &animation : base::take(_pendingPause)) { - pauseAndSaveState(animation); - } - for (const auto &animation : base::take(_pendingUnpause)) { - unpauseAndKeepUp(animation); - } - for (auto &[animation, info] : base::take(_pendingToStart)) { - addNewToActive(animation, std::move(info)); - } - for (const auto &animation : base::take(_pendingRemove)) { - removeNow(animation); - } -} - -void MultiPlayer::remove(not_null animation) { - if (!_active.empty()) { - _pendingRemove.emplace(animation); - } else { - removeNow(animation); - } -} - -void MultiPlayer::removeNow(not_null animation) { - const auto i = _active.find(animation); - if (i != end(_active)) { - _renderer->remove(i->second); - _active.erase(i); - } - const auto j = _paused.find(animation); - if (j != end(_paused)) { - _renderer->remove(j->second.state); - _paused.erase(j); - } - - _pendingRemove.remove(animation); - _pendingToStart.remove(animation); - _pendingPause.remove(animation); - _pendingUnpause.remove(animation); - _pausedBeforeStart.remove(animation); - _animations.erase( - ranges::remove( - _animations, - animation.get(), - &std::unique_ptr::get), - end(_animations)); - - if (_active.empty()) { - _nextFrameTime = kTimeUnknown; - _timer.cancel(); - if (_paused.empty()) { - _started = kTimeUnknown; - _lastSyncTime = kTimeUnknown; - _delay = 0; - } - } -} - -void MultiPlayer::pause(not_null animation) { - if (_active.contains(animation)) { - _pendingPause.emplace(animation); - } else if (_paused.contains(animation)) { - _pendingUnpause.remove(animation); - } else if (const auto i = _pendingToStart.find(animation); i != end(_pendingToStart)) { - i->second.paused = true; - } else { - _pausedBeforeStart.emplace(animation); - } -} - -void MultiPlayer::unpause(not_null animation) { - if (const auto i = _paused.find(animation); i != end(_paused)) { - if (_active.empty()) { - unpauseFirst(animation, i->second.state); - _paused.erase(i); - } else { - _pendingUnpause.emplace(animation); - } - } else if (_pendingPause.contains(animation)) { - _pendingPause.remove(animation); - } else { - const auto i = _pendingToStart.find(animation); - if (i != end(_pendingToStart)) { - i->second.paused = false; - } else { - _pausedBeforeStart.remove(animation); - } - } -} - -void MultiPlayer::unpauseFirst( - not_null animation, - not_null state) { - Expects(_lastSyncTime != kTimeUnknown); - - _active.emplace(animation, state); - - const auto now = crl::now(); - addTimelineDelay(now - _lastSyncTime); - _lastSyncTime = now; - - markFrameShown(); -} - -void MultiPlayer::pauseAndSaveState(not_null animation) { - Expects(_lastSyncTime != kTimeUnknown); - - const auto i = _active.find(animation); - Assert(i != end(_active)); - _paused.emplace( - animation, - PausedInfo{ i->second, _lastSyncTime, _delay }); - _active.erase(i); -} - -void MultiPlayer::unpauseAndKeepUp(not_null animation) { - Expects(_lastSyncTime != kTimeUnknown); - - const auto i = _paused.find(animation); - Assert(i != end(_paused)); - const auto state = i->second.state; - const auto frameIndexAtPaused = countFrameIndex( - state, - i->second.pauseTime, - i->second.pauseDelay); - const auto frameIndexNow = countFrameIndex( - state, - _lastSyncTime, - _delay); - state->addTimelineDelay( - (_delay - i->second.pauseDelay), - frameIndexNow - frameIndexAtPaused); - _active.emplace(animation, state); - _paused.erase(i); -} - -void MultiPlayer::failed(not_null animation, Error error) { - //_updates.fire({ animation, error }); -} - -rpl::producer MultiPlayer::updates() const { - return _updates.events(); -} - -void MultiPlayer::checkStep() { - if (_active.empty() || _nextFrameTime == kFrameDisplayTimeAlreadyDone) { - return; - } else if (_nextFrameTime != kTimeUnknown) { - checkNextFrameRender(); - } else { - checkNextFrameAvailability(); - } -} - -void MultiPlayer::checkNextFrameAvailability() { - Expects(_nextFrameTime == kTimeUnknown); - - auto next = kTimeUnknown; - for (const auto &[animation, state] : _active) { - const auto time = state->nextFrameDisplayTime(); - if (time == kTimeUnknown) { - for (const auto &[animation, state] : _active) { - if (state->nextFrameDisplayTime() != kTimeUnknown) { - break; - } - } - return; - } else if (time == kFrameDisplayTimeAlreadyDone) { - continue; - } - if (next == kTimeUnknown || next > time) { - next = time; - } - } - if (next == kTimeUnknown) { - return; - } - _nextFrameTime = next; - checkNextFrameRender(); -} - -void MultiPlayer::checkNextFrameRender() { - Expects(_nextFrameTime != kTimeUnknown); - - const auto now = crl::now(); - if (now < _nextFrameTime) { - if (!_timer.isActive()) { - _timer.callOnce(_nextFrameTime - now); - } - } else { - _timer.cancel(); - - markFrameDisplayed(now); - addTimelineDelay(now - _nextFrameTime); - _lastSyncTime = now; - _nextFrameTime = kFrameDisplayTimeAlreadyDone; - processPending(); - _updates.fire({}); - } -} - -void MultiPlayer::updateFrameRequest( - not_null animation, - const FrameRequest &request) { - const auto state = [&]() -> Lottie::SharedState* { - const auto key = animation; - if (const auto i = _active.find(animation); i != end(_active)) { - return i->second; - } else if (const auto j = _paused.find(animation); - j != end(_paused)) { - return j->second.state; - } else if (const auto k = _pendingToStart.find(animation); - k != end(_pendingToStart)) { - return nullptr; - } - Unexpected("Animation in MultiPlayer::updateFrameRequest."); - }(); - if (state) { - _renderer->updateFrameRequest(state, request); - } -} - -void MultiPlayer::markFrameDisplayed(crl::time now) { - Expects(!_active.empty()); - - for (const auto &[animation, state] : _active) { - const auto time = state->nextFrameDisplayTime(); - Assert(time != kTimeUnknown); - if (time == kFrameDisplayTimeAlreadyDone) { - continue; - } else if (now >= time) { - state->markFrameDisplayed(now); - } - } -} - -void MultiPlayer::addTimelineDelay(crl::time delayed) { - Expects(!_active.empty()); - - for (const auto &[animation, state] : _active) { - state->addTimelineDelay(delayed); - } - _delay += delayed; -} - -bool MultiPlayer::markFrameShown() { - if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) { - _nextFrameTime = kTimeUnknown; - } - auto count = 0; - for (const auto &[animation, state] : _active) { - if (state->markFrameShown()) { - ++count; - } - } - if (count) { - _renderer->frameShown(); - return true; - } - return false; -} - -} // namespace Lottie diff --git a/Telegram/SourceFiles/lottie/lottie_multi_player.h b/Telegram/SourceFiles/lottie/lottie_multi_player.h deleted file mode 100644 index 2cf9fbd598..0000000000 --- a/Telegram/SourceFiles/lottie/lottie_multi_player.h +++ /dev/null @@ -1,114 +0,0 @@ -/* -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 -*/ -#pragma once - -#include "lottie/lottie_player.h" -#include "base/timer.h" -#include "base/algorithm.h" -#include "base/flat_set.h" -#include "base/flat_map.h" - -#include - -namespace Lottie { - -class Animation; -class FrameRenderer; - -struct MultiUpdate { - //base::variant< - // std::pair, - // DisplayMultiFrameRequest, - // std::pair> data; -}; - -class MultiPlayer final : public Player { -public: - MultiPlayer( - Quality quality = Quality::Default, - std::shared_ptr renderer = nullptr); - ~MultiPlayer(); - - void start( - not_null animation, - std::unique_ptr state) override; - void failed(not_null animation, Error error) override; - void updateFrameRequest( - not_null animation, - const FrameRequest &request) override; - bool markFrameShown() override; - void checkStep() override; - - not_null append( - FnMut)> get, // Main thread. - FnMut put, // Unknown thread. - const QByteArray &content, - const FrameRequest &request); - not_null append( - const QByteArray &content, - const FrameRequest &request); - - rpl::producer updates() const; - - void remove(not_null animation); - - void pause(not_null animation); - void unpause(not_null animation); - -private: - struct PausedInfo { - not_null state; - crl::time pauseTime = kTimeUnknown; - crl::time pauseDelay = kTimeUnknown; - }; - struct StartingInfo { - std::unique_ptr state; - bool paused = false; - }; - - void addNewToActive( - not_null animation, - StartingInfo &&info); - [[nodiscard]] int countFrameIndex( - not_null state, - crl::time time, - crl::time delay) const; - void startAtRightTime(std::unique_ptr state); - void processPending(); - void markFrameDisplayed(crl::time now); - void addTimelineDelay(crl::time delayed); - void checkNextFrameAvailability(); - void checkNextFrameRender(); - void unpauseFirst( - not_null animation, - not_null state); - void pauseAndSaveState(not_null animation); - void unpauseAndKeepUp(not_null animation); - void removeNow(not_null animation); - - Quality _quality = Quality::Default; - base::Timer _timer; - const std::shared_ptr _renderer; - std::vector> _animations; - base::flat_map, not_null> _active; - base::flat_map, PausedInfo> _paused; - base::flat_set> _pendingPause; - base::flat_set> _pendingUnpause; - base::flat_set> _pausedBeforeStart; - base::flat_set> _pendingRemove; - base::flat_map, StartingInfo> _pendingToStart; - crl::time _started = kTimeUnknown; - crl::time _lastSyncTime = kTimeUnknown; - crl::time _delay = 0; - crl::time _nextFrameTime = kTimeUnknown; - rpl::event_stream _updates; - rpl::lifetime _lifetime; - -}; - -} // namespace Lottie diff --git a/Telegram/SourceFiles/lottie/lottie_player.h b/Telegram/SourceFiles/lottie/lottie_player.h deleted file mode 100644 index 2216d6708d..0000000000 --- a/Telegram/SourceFiles/lottie/lottie_player.h +++ /dev/null @@ -1,35 +0,0 @@ -/* -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 -*/ -#pragma once - -#include "lottie/lottie_common.h" -#include "base/weak_ptr.h" - -#include - -namespace Lottie { - -class SharedState; - -class Player : public base::has_weak_ptr { -public: - virtual void start( - not_null animation, - std::unique_ptr state) = 0; - virtual void failed(not_null animation, Error error) = 0; - virtual void updateFrameRequest( - not_null animation, - const FrameRequest &request) = 0; - virtual bool markFrameShown() = 0; - virtual void checkStep() = 0; - - virtual ~Player() = default; - -}; - -} // namespace Lottie diff --git a/Telegram/SourceFiles/lottie/lottie_single_player.cpp b/Telegram/SourceFiles/lottie/lottie_single_player.cpp deleted file mode 100644 index d88a2cb596..0000000000 --- a/Telegram/SourceFiles/lottie/lottie_single_player.cpp +++ /dev/null @@ -1,158 +0,0 @@ -/* -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_single_player.h" - -#include "lottie/lottie_frame_renderer.h" - -namespace Lottie { - -SinglePlayer::SinglePlayer( - const QByteArray &content, - const FrameRequest &request, - Quality quality, - const ColorReplacements *replacements, - std::shared_ptr renderer) -: _animation(this, content, request, quality, replacements) -, _timer([=] { checkNextFrameRender(); }) -, _renderer(renderer ? renderer : FrameRenderer::Instance()) { -} - -SinglePlayer::SinglePlayer( - FnMut)> get, // Main thread. - FnMut put, // Unknown thread. - const QByteArray &content, - const FrameRequest &request, - Quality quality, - const ColorReplacements *replacements, - std::shared_ptr renderer) -: _animation( - this, - std::move(get), - std::move(put), - content, - request, - quality, - replacements) -, _timer([=] { checkNextFrameRender(); }) -, _renderer(renderer ? renderer : FrameRenderer::Instance()) { -} - -SinglePlayer::~SinglePlayer() { - if (_state) { - _renderer->remove(_state); - } -} - -void SinglePlayer::start( - not_null animation, - std::unique_ptr state) { - Expects(animation == &_animation); - - _state = state.get(); - auto information = state->information(); - state->start(this, crl::now()); - const auto request = state->frameForPaint()->request; - _renderer->append(std::move(state), request); - _updates.fire({ std::move(information) }); - - crl::on_main_update_requests( - ) | rpl::start_with_next([=] { - checkStep(); - }, _lifetime); -} - -void SinglePlayer::failed(not_null animation, Error error) { - Expects(animation == &_animation); - - _updates.fire_error(std::move(error)); -} - -rpl::producer SinglePlayer::updates() const { - return _updates.events(); -} - -bool SinglePlayer::ready() const { - return _animation.ready(); -} - -QImage SinglePlayer::frame() const { - return _animation.frame(); -} - -QImage SinglePlayer::frame(const FrameRequest &request) const { - return _animation.frame(request); -} - -Animation::FrameInfo SinglePlayer::frameInfo( - const FrameRequest &request) const { - return _animation.frameInfo(request); -} - -void SinglePlayer::checkStep() { - if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) { - return; - } else if (_nextFrameTime != kTimeUnknown) { - checkNextFrameRender(); - } else { - checkNextFrameAvailability(); - } -} - -void SinglePlayer::checkNextFrameAvailability() { - Expects(_state != nullptr); - Expects(_nextFrameTime == kTimeUnknown); - - _nextFrameTime = _state->nextFrameDisplayTime(); - Assert(_nextFrameTime != kFrameDisplayTimeAlreadyDone); - if (_nextFrameTime != kTimeUnknown) { - checkNextFrameRender(); - } -} - -void SinglePlayer::checkNextFrameRender() { - Expects(_nextFrameTime != kTimeUnknown); - - const auto now = crl::now(); - if (now < _nextFrameTime) { - if (!_timer.isActive()) { - _timer.callOnce(_nextFrameTime - now); - } - } else { - _timer.cancel(); - - _state->markFrameDisplayed(now); - _state->addTimelineDelay(now - _nextFrameTime); - - _nextFrameTime = kFrameDisplayTimeAlreadyDone; - _updates.fire({ DisplayFrameRequest() }); - } -} - -void SinglePlayer::updateFrameRequest( - not_null animation, - const FrameRequest &request) { - Expects(animation == &_animation); - Expects(_state != nullptr); - - _renderer->updateFrameRequest(_state, request); -} - -bool SinglePlayer::markFrameShown() { - Expects(_state != nullptr); - - if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) { - _nextFrameTime = kTimeUnknown; - } - if (_state->markFrameShown()) { - _renderer->frameShown(); - return true; - } - return false; -} - -} // namespace Lottie diff --git a/Telegram/SourceFiles/lottie/lottie_single_player.h b/Telegram/SourceFiles/lottie/lottie_single_player.h deleted file mode 100644 index 772e7743ac..0000000000 --- a/Telegram/SourceFiles/lottie/lottie_single_player.h +++ /dev/null @@ -1,79 +0,0 @@ -/* -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 -*/ -#pragma once - -#include "lottie/lottie_player.h" -#include "lottie/lottie_animation.h" -#include "base/timer.h" - -#include - -namespace Lottie { - -class FrameRenderer; - -struct DisplayFrameRequest { -}; - -struct Update { - base::variant< - Information, - DisplayFrameRequest> data; -}; - -class SinglePlayer final : public Player { -public: - SinglePlayer( - const QByteArray &content, - const FrameRequest &request, - Quality quality = Quality::Default, - const ColorReplacements *replacements = nullptr, - std::shared_ptr renderer = nullptr); - SinglePlayer( - FnMut)> get, // Main thread. - FnMut put, // Unknown thread. - const QByteArray &content, - const FrameRequest &request, - Quality quality = Quality::Default, - const ColorReplacements *replacements = nullptr, - std::shared_ptr renderer = nullptr); - ~SinglePlayer(); - - void start( - not_null animation, - std::unique_ptr state) override; - void failed(not_null animation, Error error) override; - void updateFrameRequest( - not_null animation, - const FrameRequest &request) override; - bool markFrameShown() override; - void checkStep() override; - - rpl::producer updates() const; - - [[nodiscard]] bool ready() const; - [[nodiscard]] QImage frame() const; - [[nodiscard]] QImage frame(const FrameRequest &request) const; - [[nodiscard]] Animation::FrameInfo frameInfo( - const FrameRequest &request) const; - -private: - void checkNextFrameAvailability(); - void checkNextFrameRender(); - - Animation _animation; - base::Timer _timer; - const std::shared_ptr _renderer; - SharedState *_state = nullptr; - crl::time _nextFrameTime = kTimeUnknown; - rpl::event_stream _updates; - rpl::lifetime _lifetime; - -}; - -} // namespace Lottie diff --git a/Telegram/ThirdParty/rlottie b/Telegram/ThirdParty/rlottie index 589db026ec..fceedfb5e1 160000 --- a/Telegram/ThirdParty/rlottie +++ b/Telegram/ThirdParty/rlottie @@ -1 +1 @@ -Subproject commit 589db026ec211bc4979e3bffe074f6e48ce7cedc +Subproject commit fceedfb5e1a226934771d146839518c6dc4780c0 diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp index 3ee5fbd0b0..3cfba863d8 100644 --- a/Telegram/gyp/Telegram.gyp +++ b/Telegram/gyp/Telegram.gyp @@ -77,11 +77,11 @@ '<(submodules_loc)/lib_base/lib_base.gyp:lib_base', '<(submodules_loc)/lib_ui/lib_ui.gyp:lib_ui', '<(third_party_loc)/libtgvoip/libtgvoip.gyp:libtgvoip', + '<(submodules_loc)/lib_lottie/lib_lottie.gyp:lib_lottie', 'tests/tests.gyp:tests', 'utils.gyp:Updater', 'lib_export.gyp:lib_export', 'lib_storage.gyp:lib_storage', - 'lib_lottie.gyp:lib_lottie', 'lib_ffmpeg.gyp:lib_ffmpeg', 'lib_mtproto.gyp:lib_mtproto', ], diff --git a/Telegram/gyp/generate.py b/Telegram/gyp/generate.py index 4194e1666b..b6c6df7b4f 100644 --- a/Telegram/gyp/generate.py +++ b/Telegram/gyp/generate.py @@ -84,6 +84,7 @@ gypArguments.append('--generator-output=..') gypArguments.append('-Goutput_dir=../out') gypArguments.append('-Dapi_id=' + apiId) gypArguments.append('-Dapi_hash=' + apiHash) +gypArguments.append('-Dlottie_use_cache=1') gypArguments.append('-Dofficial_build_target=' + officialTarget) if 'TDESKTOP_BUILD_DEFINES' in os.environ: buildDefines = os.environ['TDESKTOP_BUILD_DEFINES'] diff --git a/Telegram/gyp/lib_ffmpeg.gyp b/Telegram/gyp/lib_ffmpeg.gyp index 47e00d237c..8517076408 100644 --- a/Telegram/gyp/lib_ffmpeg.gyp +++ b/Telegram/gyp/lib_ffmpeg.gyp @@ -32,6 +32,7 @@ ], 'direct_dependent_settings': { 'include_dirs': [ + '<(src_loc)', '<(libs_loc)/ffmpeg', ], }, diff --git a/Telegram/gyp/lib_lottie.gyp b/Telegram/gyp/lib_lottie.gyp deleted file mode 100644 index ca4158a06e..0000000000 --- a/Telegram/gyp/lib_lottie.gyp +++ /dev/null @@ -1,65 +0,0 @@ -# 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 - -{ - 'includes': [ - 'helpers/common/common.gypi', - ], - 'targets': [{ - 'target_name': 'lib_lottie', - 'includes': [ - 'helpers/common/library.gypi', - 'helpers/modules/openssl.gypi', - 'helpers/modules/qt.gypi', - ], - 'variables': { - 'src_loc': '../SourceFiles', - 'res_loc': '../Resources', - }, - 'dependencies': [ - '<(submodules_loc)/lib_base/lib_base.gyp:lib_base', - 'lib_rlottie.gyp:lib_rlottie', - 'lib_ffmpeg.gyp:lib_ffmpeg', - 'lib_lz4.gyp:lib_lz4', - ], - 'export_dependent_settings': [ - '<(submodules_loc)/lib_base/lib_base.gyp:lib_base', - 'lib_rlottie.gyp:lib_rlottie', - 'lib_ffmpeg.gyp:lib_ffmpeg', - 'lib_lz4.gyp:lib_lz4', - ], - 'defines': [ - 'LOT_BUILD', - ], - 'include_dirs': [ - '<(src_loc)', - '<(libs_loc)/zlib', - ], - 'sources': [ - '<(src_loc)/lottie/lottie_animation.cpp', - '<(src_loc)/lottie/lottie_animation.h', - '<(src_loc)/lottie/lottie_cache.cpp', - '<(src_loc)/lottie/lottie_cache.h', - '<(src_loc)/lottie/lottie_common.cpp', - '<(src_loc)/lottie/lottie_common.h', - '<(src_loc)/lottie/lottie_frame_renderer.cpp', - '<(src_loc)/lottie/lottie_frame_renderer.h', - '<(src_loc)/lottie/lottie_multi_player.cpp', - '<(src_loc)/lottie/lottie_multi_player.h', - '<(src_loc)/lottie/lottie_player.h', - '<(src_loc)/lottie/lottie_single_player.cpp', - '<(src_loc)/lottie/lottie_single_player.h', - ], - 'conditions': [[ 'build_macold', { - 'xcode_settings': { - 'OTHER_CPLUSPLUSFLAGS': [ '-nostdinc++' ], - }, - 'include_dirs': [ - '/usr/local/macold/include/c++/v1', - ], - }]], - }], -} diff --git a/Telegram/gyp/lib_rlottie.gyp b/Telegram/gyp/lib_rlottie.gyp deleted file mode 100644 index 44634c7be5..0000000000 --- a/Telegram/gyp/lib_rlottie.gyp +++ /dev/null @@ -1,134 +0,0 @@ -# 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 - -{ - 'includes': [ - 'helpers/common/common.gypi', - ], - 'variables': { - 'build_standard_win': 'c++14', - }, - 'targets': [{ - 'target_name': 'lib_rlottie', - 'includes': [ - 'helpers/common/library.gypi', - ], - 'variables': { - 'build_standard_win': 'c++14', - 'rlottie_loc': '<(third_party_loc)/rlottie', - 'rlottie_src': '<(rlottie_loc)/src', - }, - 'defines': [ - '_USE_MATH_DEFINES', - 'RAPIDJSON_ASSERT=(void)', - 'LOT_BUILD', - ], - 'include_dirs': [ - '<(rlottie_loc)/inc', - '<(rlottie_src)/lottie', - '<(rlottie_src)/vector', - '<(rlottie_src)/vector/pixman', - '<(rlottie_src)/vector/freetype', - ], - 'direct_dependent_settings': { - 'include_dirs': [ - '<(rlottie_loc)/inc', - ], - }, - 'sources': [ - '<(rlottie_loc)/inc/rlottie.h', - '<(rlottie_loc)/inc/rlottie_capi.h', - '<(rlottie_loc)/inc/rlottiecommon.h', - - '<(rlottie_src)/lottie/lottieanimation.cpp', - '<(rlottie_src)/lottie/lottieitem.cpp', - '<(rlottie_src)/lottie/lottieitem.h', - '<(rlottie_src)/lottie/lottiekeypath.cpp', - '<(rlottie_src)/lottie/lottiekeypath.h', - '<(rlottie_src)/lottie/lottieloader.cpp', - '<(rlottie_src)/lottie/lottieloader.h', - '<(rlottie_src)/lottie/lottiemodel.cpp', - '<(rlottie_src)/lottie/lottiemodel.h', - '<(rlottie_src)/lottie/lottieparser.cpp', - '<(rlottie_src)/lottie/lottieparser.h', - '<(rlottie_src)/lottie/lottieproxymodel.cpp', - '<(rlottie_src)/lottie/lottieproxymodel.h', - - '<(rlottie_src)/vector/freetype/v_ft_math.cpp', - '<(rlottie_src)/vector/freetype/v_ft_math.h', - '<(rlottie_src)/vector/freetype/v_ft_raster.cpp', - '<(rlottie_src)/vector/freetype/v_ft_raster.h', - '<(rlottie_src)/vector/freetype/v_ft_stroker.cpp', - '<(rlottie_src)/vector/freetype/v_ft_stroker.h', - '<(rlottie_src)/vector/freetype/v_ft_types.h', - - #'<(rlottie_src)/vector/pixman/pixman-arm-neon-asm.h', - #'<(rlottie_src)/vector/pixman/pixman-arm-neon-asm.S', - '<(rlottie_src)/vector/pixman/vregion.cpp', - '<(rlottie_src)/vector/pixman/vregion.h', - - '<(rlottie_src)/vector/config.h', - '<(rlottie_src)/vector/vbezier.cpp', - '<(rlottie_src)/vector/vbezier.h', - '<(rlottie_src)/vector/vbitmap.cpp', - '<(rlottie_src)/vector/vbitmap.h', - '<(rlottie_src)/vector/vbrush.cpp', - '<(rlottie_src)/vector/vbrush.h', - '<(rlottie_src)/vector/vcompositionfunctions.cpp', - '<(rlottie_src)/vector/vcowptr.h', - '<(rlottie_src)/vector/vdasher.cpp', - '<(rlottie_src)/vector/vdasher.h', - '<(rlottie_src)/vector/vdebug.cpp', - '<(rlottie_src)/vector/vdebug.h', - '<(rlottie_src)/vector/vdrawable.cpp', - '<(rlottie_src)/vector/vdrawable.h', - '<(rlottie_src)/vector/vdrawhelper.cpp', - '<(rlottie_src)/vector/vdrawhelper.h', - '<(rlottie_src)/vector/vdrawhelper_neon.cpp', - '<(rlottie_src)/vector/vdrawhelper_sse2.cpp', - '<(rlottie_src)/vector/velapsedtimer.cpp', - '<(rlottie_src)/vector/velapsedtimer.h', - '<(rlottie_src)/vector/vglobal.h', - '<(rlottie_src)/vector/vimageloader.cpp', - '<(rlottie_src)/vector/vimageloader.h', - '<(rlottie_src)/vector/vinterpolator.cpp', - '<(rlottie_src)/vector/vinterpolator.h', - '<(rlottie_src)/vector/vline.h', - '<(rlottie_src)/vector/vmatrix.cpp', - '<(rlottie_src)/vector/vmatrix.h', - '<(rlottie_src)/vector/vpainter.cpp', - '<(rlottie_src)/vector/vpainter.h', - '<(rlottie_src)/vector/vpath.cpp', - '<(rlottie_src)/vector/vpath.h', - '<(rlottie_src)/vector/vpathmesure.cpp', - '<(rlottie_src)/vector/vpathmesure.h', - '<(rlottie_src)/vector/vpoint.h', - '<(rlottie_src)/vector/vraster.cpp', - '<(rlottie_src)/vector/vraster.h', - '<(rlottie_src)/vector/vrect.cpp', - '<(rlottie_src)/vector/vrect.h', - '<(rlottie_src)/vector/vrle.cpp', - '<(rlottie_src)/vector/vrle.h', - '<(rlottie_src)/vector/vstackallocator.h', - '<(rlottie_src)/vector/vtaskqueue.h', - ], - 'conditions': [[ 'build_macold', { - 'xcode_settings': { - 'OTHER_CPLUSPLUSFLAGS': [ '-nostdinc++' ], - }, - 'include_dirs': [ - '/usr/local/macold/include/c++/v1', - ], - }]], - 'msvs_settings': { - 'VCCLCompilerTool': { - 'AdditionalOptions': [ - '/w44244', # 'initializing': conversion from 'double' to 'float' - ], - }, - }, - }], -} diff --git a/Telegram/lib_lottie b/Telegram/lib_lottie new file mode 160000 index 0000000000..5c820ee0a0 --- /dev/null +++ b/Telegram/lib_lottie @@ -0,0 +1 @@ +Subproject commit 5c820ee0a0902e2511f7c1c040801c133900496d diff --git a/Telegram/lib_rlottie b/Telegram/lib_rlottie new file mode 160000 index 0000000000..b52e0c2cc1 --- /dev/null +++ b/Telegram/lib_rlottie @@ -0,0 +1 @@ +Subproject commit b52e0c2cc1daa8cba6a0cec278ee9914020aabe1