Add lib_[r]lottie as submodules.

This commit is contained in:
John Preston 2019-09-23 13:41:02 +03:00
parent dad73c0e7b
commit 250b7240f6
22 changed files with 13 additions and 2951 deletions

8
.gitmodules vendored
View File

@ -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

View File

@ -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 <QFile>
#include <rlottie.h>
#include <crl/crl_async.h>
#include <crl/crl_on_main.h>
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<Bytef*>(const_cast<char*>(bytes.data()));
stream.avail_out = 0;
while (!stream.avail_out) {
stream.avail_out = result.size();
stream.next_out = reinterpret_cast<Bytef*>(result.data());
int res = inflate(&stream, Z_NO_FLUSH);
if (res != Z_OK && res != Z_STREAM_END) {
return original();
} else if (!stream.avail_out) {
return original();
}
}
result.resize(result.size() - stream.avail_out);
return result;
}
std::optional<Error> 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<SharedState> 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<SharedState>(
std::move(animation),
request.empty() ? FrameRequest{ kIdealSize } : request,
quality))
: Error::ParseFailed;
}
details::InitData Init(
const QByteArray &content,
FnMut<void(QByteArray &&cached)> 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<Cache>(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<SharedState>(
content,
replacements,
std::move(animation),
std::move(cache),
request,
quality))
: Error::ParseFailed;
}
} // namespace
namespace details {
std::unique_ptr<rlottie::Animation> 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<std::pair<std::uint32_t, std::uint32_t>>()));
if (!result) {
LOG(("Lottie Error: Parse failed."));
}
return result;
}
} // namespace details
std::shared_ptr<FrameRenderer> MakeFrameRenderer() {
return FrameRenderer::CreateIndependent();
}
QImage ReadThumbnail(const QByteArray &content) {
return Init(content, FrameRequest(), Quality::High, nullptr).match([](
const std::unique_ptr<SharedState> &state) {
return state->frameForPaint()->original;
}, [](Error) {
return QImage();
});
}
Animation::Animation(
not_null<Player*> player,
const QByteArray &content,
const FrameRequest &request,
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*> player,
FnMut<void(FnMut<void(QByteArray &&cached)>)> get, // Main thread.
FnMut<void(QByteArray &&cached)> 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<SharedState> &state) {
parseDone(std::move(state));
}, [&](Error error) {
parseFailed(error);
});
}
void Animation::parseDone(std::unique_ptr<SharedState> 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

View File

@ -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 <QtGui/QImage>
class QString;
class QByteArray;
namespace rlottie {
class Animation;
} // namespace rlottie
namespace Lottie {
class Player;
class SharedState;
class FrameRenderer;
std::shared_ptr<FrameRenderer> MakeFrameRenderer();
QImage ReadThumbnail(const QByteArray &content);
namespace details {
using InitData = base::variant<std::unique_ptr<SharedState>, Error>;
std::unique_ptr<rlottie::Animation> 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*> player,
const QByteArray &content,
const FrameRequest &request,
Quality quality,
const ColorReplacements *replacements = nullptr);
Animation(
not_null<Player*> player,
FnMut<void(FnMut<void(QByteArray &&cached)>)> get, // Main thread.
FnMut<void(QByteArray &&cached)> 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<SharedState> state);
void parseFailed(Error error);
not_null<Player*> _player;
SharedState *_state = nullptr;
};
} // namespace Lottie

View File

@ -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 <QDataStream>
#include <lz4.h>
#include <lz4hc.h>
#include <range/v3/numeric/accumulate.hpp>
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<const uchar*>(from.data());
const auto toBytes = reinterpret_cast<uchar*>(to.data());
const auto blocks = amount / kBlockSize;
const auto fromBlocks = reinterpret_cast<const Block*>(fromBytes);
const auto toBlocks = reinterpret_cast<Block*>(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<const char*>(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<uint32*>(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<const uint32*>(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<quintptr>(_data.data());
return reinterpret_cast<char*>(kAlignStorage
* ((result + kAlignStorage - 1) / kAlignStorage));
}
const char *EncodedStorage::data() const {
const auto result = reinterpret_cast<quintptr>(_data.data());
return reinterpret_cast<const char*>(kAlignStorage
* ((result + kAlignStorage - 1) / kAlignStorage));
}
uint8_t *EncodedStorage::yData() {
return reinterpret_cast<uint8_t*>(data());
}
const uint8_t *EncodedStorage::yData() const {
return reinterpret_cast<const uint8_t*>(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<void(QByteArray &&cached)> 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) != 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>(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<qint32>(_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

View File

@ -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 <QImage>
#include <QSize>
#include <QByteArray>
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<void(QByteArray &&cached)> 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<QByteArray> 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<void(QByteArray &&cached)> _put;
};
} // namespace Lottie

View File

@ -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 <QFile>
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

View File

@ -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 <QSize>
#include <QColor>
#include <crl/crl_time.h>
#include <vector>
namespace Lottie {
inline constexpr auto kTimeUnknown = std::numeric_limits<crl::time>::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<QColor> 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<std::pair<std::uint32_t, std::uint32_t>> replacements;
uint8 tag = 0;
};
QByteArray ReadContent(const QByteArray &data, const QString &filepath);
} // namespace Lottie

View File

@ -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 <QPainter>
#include <rlottie.h>
#include <range/v3/algorithm/find.hpp>
#include <range/v3/algorithm/count_if.hpp>
namespace Images {
QImage prepareColored(QColor add, QImage image);
} // namespace Images
namespace Lottie {
namespace {
std::weak_ptr<FrameRenderer> 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<rlottie::Animation*> animation, Quality quality) {
const auto rate = int(qRound(animation->frameRate()));
return (quality == Quality::Default && rate == 60) ? (rate / 2) : rate;
}
int GetLottieFramesCount(not_null<rlottie::Animation*> 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<rlottie::Animation*> 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<FrameRendererObject> weak);
void append(
std::unique_ptr<SharedState> entry,
const FrameRequest &request);
void frameShown();
void updateFrameRequest(
not_null<SharedState*> entry,
const FrameRequest &request);
void remove(not_null<SharedState*> entry);
private:
struct Entry {
std::unique_ptr<SharedState> state;
FrameRequest request;
};
static not_null<SharedState*> StateFromEntry(const Entry &entry) {
return entry.state.get();
}
void queueGenerateFrames();
void generateFrames();
crl::weak_on_queue<FrameRendererObject> _weak;
std::vector<Entry> _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*> 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<FrameRendererObject> weak)
: _weak(std::move(weak)) {
}
void FrameRendererObject::append(
std::unique_ptr<SharedState> state,
const FrameRequest &request) {
_entries.push_back({ std::move(state), request });
queueGenerateFrames();
}
void FrameRendererObject::frameShown() {
queueGenerateFrames();
}
void FrameRendererObject::updateFrameRequest(
not_null<SharedState*> entry,
const FrameRequest &request) {
const auto i = ranges::find(_entries, entry, &StateFromEntry);
Assert(i != end(_entries));
i->request = request;
}
void FrameRendererObject::remove(not_null<SharedState*> entry) {
const auto i = ranges::find(_entries, entry, &StateFromEntry);
Assert(i != end(_entries));
_entries.erase(i);
}
void FrameRendererObject::generateFrames() {
auto players = base::flat_map<Player*, base::weak_ptr<Player>>();
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<rlottie::Animation> 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<rlottie::Animation> animation,
std::unique_ptr<Cache> 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<uint32_t*>(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<Player*> 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<const Frame*> frame) {
return (frame->displayed == kTimeUnknown);
}
void SharedState::renderNextFrame(
not_null<Frame*> 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<Frame*> SharedState::getFrame(int index) {
Expects(index >= 0 && index < kFramesCount);
return &_frames[index];
}
not_null<const Frame*> SharedState::getFrame(int index) const {
Expects(index >= 0 && index < kFramesCount);
return &_frames[index];
}
Information SharedState::information() const {
return isValid() ? _info : Information();
}
not_null<Frame*> 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> FrameRenderer::CreateIndependent() {
return std::make_shared<FrameRenderer>();
}
std::shared_ptr<FrameRenderer> FrameRenderer::Instance() {
if (auto result = GlobalInstance.lock()) {
return result;
}
auto result = CreateIndependent();
GlobalInstance = result;
return result;
}
void FrameRenderer::append(
std::unique_ptr<SharedState> 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<SharedState*> entry,
const FrameRequest &request) {
_wrapped.with([=](FrameRendererObject &unwrapped) {
unwrapped.updateFrameRequest(entry, request);
});
}
void FrameRenderer::remove(not_null<SharedState*> entry) {
_wrapped.with([=](FrameRendererObject &unwrapped) {
unwrapped.remove(entry);
});
}
} // namespace Lottie

View File

@ -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 <QImage>
#include <QSize>
#include <crl/crl_time.h>
#include <crl/crl_object_on_queue.h>
#include <limits>
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<crl::time>::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*> frame,
bool useExistingPrepared);
class SharedState {
public:
SharedState(
std::unique_ptr<rlottie::Animation> animation,
const FrameRequest &request,
Quality quality);
SharedState(
const QByteArray &content,
const ColorReplacements *replacements,
std::unique_ptr<rlottie::Animation> animation,
std::unique_ptr<Cache> cache,
const FrameRequest &request,
Quality quality);
void start(
not_null<Player*> owner,
crl::time now,
crl::time delay = 0,
int skippedFrames = 0);
[[nodiscard]] Information information() const;
[[nodiscard]] bool initialized() const;
[[nodiscard]] not_null<Frame*> 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<Player> 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*> frame,
const FrameRequest &request);
[[nodiscard]] crl::time countFrameDisplayTime(int index) const;
[[nodiscard]] not_null<Frame*> getFrame(int index);
[[nodiscard]] not_null<const Frame*> 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<int> _counter = kCounterUninitialized;
static constexpr auto kFramesCount = 4;
std::array<Frame, kFramesCount> _frames;
base::weak_ptr<Player> _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> _cache;
std::unique_ptr<rlottie::Animation> _animation;
const QByteArray _content;
const ColorReplacements *_replacements = nullptr;
};
class FrameRendererObject;
class FrameRenderer final {
public:
static std::shared_ptr<FrameRenderer> CreateIndependent();
static std::shared_ptr<FrameRenderer> Instance();
void append(
std::unique_ptr<SharedState> entry,
const FrameRequest &request);
void updateFrameRequest(
not_null<SharedState*> entry,
const FrameRequest &request);
void frameShown();
void remove(not_null<SharedState*> state);
private:
using Implementation = FrameRendererObject;
crl::object_on_queue<Implementation> _wrapped;
};
} // namespace Lottie

View File

@ -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 <range/v3/algorithm/remove.hpp>
namespace Lottie {
MultiPlayer::MultiPlayer(
Quality quality,
std::shared_ptr<FrameRenderer> 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<Animation*> MultiPlayer::append(
FnMut<void(FnMut<void(QByteArray &&cached)>)> get, // Main thread.
FnMut<void(QByteArray &&cached)> put, // Unknown thread.
const QByteArray &content,
const FrameRequest &request) {
_animations.push_back(std::make_unique<Animation>(
this,
std::move(get),
std::move(put),
content,
request,
_quality));
return _animations.back().get();
}
not_null<Animation*> MultiPlayer::append(
const QByteArray &content,
const FrameRequest &request) {
_animations.push_back(std::make_unique<Animation>(
this,
content,
request,
_quality));
return _animations.back().get();
}
void MultiPlayer::startAtRightTime(std::unique_ptr<SharedState> 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<SharedState*> 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*> animation,
std::unique_ptr<SharedState> 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*> 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*> animation) {
if (!_active.empty()) {
_pendingRemove.emplace(animation);
} else {
removeNow(animation);
}
}
void MultiPlayer::removeNow(not_null<Animation*> 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<Animation>::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*> 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*> 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*> animation,
not_null<SharedState*> 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*> 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*> 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*> animation, Error error) {
//_updates.fire({ animation, error });
}
rpl::producer<MultiUpdate> 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<const Animation*> 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

View File

@ -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 <rpl/event_stream.h>
namespace Lottie {
class Animation;
class FrameRenderer;
struct MultiUpdate {
//base::variant<
// std::pair<Animation*, Information>,
// DisplayMultiFrameRequest,
// std::pair<Animation*, Error>> data;
};
class MultiPlayer final : public Player {
public:
MultiPlayer(
Quality quality = Quality::Default,
std::shared_ptr<FrameRenderer> renderer = nullptr);
~MultiPlayer();
void start(
not_null<Animation*> animation,
std::unique_ptr<SharedState> state) override;
void failed(not_null<Animation*> animation, Error error) override;
void updateFrameRequest(
not_null<const Animation*> animation,
const FrameRequest &request) override;
bool markFrameShown() override;
void checkStep() override;
not_null<Animation*> append(
FnMut<void(FnMut<void(QByteArray &&cached)>)> get, // Main thread.
FnMut<void(QByteArray &&cached)> put, // Unknown thread.
const QByteArray &content,
const FrameRequest &request);
not_null<Animation*> append(
const QByteArray &content,
const FrameRequest &request);
rpl::producer<MultiUpdate> updates() const;
void remove(not_null<Animation*> animation);
void pause(not_null<Animation*> animation);
void unpause(not_null<Animation*> animation);
private:
struct PausedInfo {
not_null<SharedState*> state;
crl::time pauseTime = kTimeUnknown;
crl::time pauseDelay = kTimeUnknown;
};
struct StartingInfo {
std::unique_ptr<SharedState> state;
bool paused = false;
};
void addNewToActive(
not_null<Animation*> animation,
StartingInfo &&info);
[[nodiscard]] int countFrameIndex(
not_null<SharedState*> state,
crl::time time,
crl::time delay) const;
void startAtRightTime(std::unique_ptr<SharedState> state);
void processPending();
void markFrameDisplayed(crl::time now);
void addTimelineDelay(crl::time delayed);
void checkNextFrameAvailability();
void checkNextFrameRender();
void unpauseFirst(
not_null<Animation*> animation,
not_null<SharedState*> state);
void pauseAndSaveState(not_null<Animation*> animation);
void unpauseAndKeepUp(not_null<Animation*> animation);
void removeNow(not_null<Animation*> animation);
Quality _quality = Quality::Default;
base::Timer _timer;
const std::shared_ptr<FrameRenderer> _renderer;
std::vector<std::unique_ptr<Animation>> _animations;
base::flat_map<not_null<Animation*>, not_null<SharedState*>> _active;
base::flat_map<not_null<Animation*>, PausedInfo> _paused;
base::flat_set<not_null<Animation*>> _pendingPause;
base::flat_set<not_null<Animation*>> _pendingUnpause;
base::flat_set<not_null<Animation*>> _pausedBeforeStart;
base::flat_set<not_null<Animation*>> _pendingRemove;
base::flat_map<not_null<Animation*>, StartingInfo> _pendingToStart;
crl::time _started = kTimeUnknown;
crl::time _lastSyncTime = kTimeUnknown;
crl::time _delay = 0;
crl::time _nextFrameTime = kTimeUnknown;
rpl::event_stream<MultiUpdate> _updates;
rpl::lifetime _lifetime;
};
} // namespace Lottie

View File

@ -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 <rpl/producer.h>
namespace Lottie {
class SharedState;
class Player : public base::has_weak_ptr {
public:
virtual void start(
not_null<Animation*> animation,
std::unique_ptr<SharedState> state) = 0;
virtual void failed(not_null<Animation*> animation, Error error) = 0;
virtual void updateFrameRequest(
not_null<const Animation*> animation,
const FrameRequest &request) = 0;
virtual bool markFrameShown() = 0;
virtual void checkStep() = 0;
virtual ~Player() = default;
};
} // namespace Lottie

View File

@ -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<FrameRenderer> renderer)
: _animation(this, content, request, quality, replacements)
, _timer([=] { checkNextFrameRender(); })
, _renderer(renderer ? renderer : FrameRenderer::Instance()) {
}
SinglePlayer::SinglePlayer(
FnMut<void(FnMut<void(QByteArray &&cached)>)> get, // Main thread.
FnMut<void(QByteArray &&cached)> put, // Unknown thread.
const QByteArray &content,
const FrameRequest &request,
Quality quality,
const ColorReplacements *replacements,
std::shared_ptr<FrameRenderer> 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*> animation,
std::unique_ptr<SharedState> 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*> animation, Error error) {
Expects(animation == &_animation);
_updates.fire_error(std::move(error));
}
rpl::producer<Update, Error> 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<const Animation*> 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

View File

@ -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 <rpl/event_stream.h>
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<FrameRenderer> renderer = nullptr);
SinglePlayer(
FnMut<void(FnMut<void(QByteArray &&cached)>)> get, // Main thread.
FnMut<void(QByteArray &&cached)> put, // Unknown thread.
const QByteArray &content,
const FrameRequest &request,
Quality quality = Quality::Default,
const ColorReplacements *replacements = nullptr,
std::shared_ptr<FrameRenderer> renderer = nullptr);
~SinglePlayer();
void start(
not_null<Animation*> animation,
std::unique_ptr<SharedState> state) override;
void failed(not_null<Animation*> animation, Error error) override;
void updateFrameRequest(
not_null<const Animation*> animation,
const FrameRequest &request) override;
bool markFrameShown() override;
void checkStep() override;
rpl::producer<Update, Error> 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<FrameRenderer> _renderer;
SharedState *_state = nullptr;
crl::time _nextFrameTime = kTimeUnknown;
rpl::event_stream<Update, Error> _updates;
rpl::lifetime _lifetime;
};
} // namespace Lottie

@ -1 +1 @@
Subproject commit 589db026ec211bc4979e3bffe074f6e48ce7cedc
Subproject commit fceedfb5e1a226934771d146839518c6dc4780c0

View File

@ -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',
],

View File

@ -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']

View File

@ -32,6 +32,7 @@
],
'direct_dependent_settings': {
'include_dirs': [
'<(src_loc)',
'<(libs_loc)/ffmpeg',
],
},

View File

@ -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',
],
}]],
}],
}

View File

@ -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'
],
},
},
}],
}

1
Telegram/lib_lottie Submodule

@ -0,0 +1 @@
Subproject commit 5c820ee0a0902e2511f7c1c040801c133900496d

1
Telegram/lib_rlottie Submodule

@ -0,0 +1 @@
Subproject commit b52e0c2cc1daa8cba6a0cec278ee9914020aabe1