init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
71
Telegram/lib_lottie/CMakeLists.txt
Normal file
71
Telegram/lib_lottie/CMakeLists.txt
Normal file
@@ -0,0 +1,71 @@
|
||||
# This file is part of Desktop App Toolkit,
|
||||
# a set of libraries for developing nice desktop applications.
|
||||
#
|
||||
# For license and copyright information please follow this link:
|
||||
# https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
|
||||
add_library(lib_lottie OBJECT)
|
||||
add_library(desktop-app::lib_lottie ALIAS lib_lottie)
|
||||
init_target(lib_lottie)
|
||||
|
||||
get_filename_component(src_loc . REALPATH)
|
||||
|
||||
nice_target_sources(lib_lottie ${src_loc}
|
||||
PRIVATE
|
||||
lottie/details/lottie_frame_provider.h
|
||||
lottie/details/lottie_frame_provider_direct.cpp
|
||||
lottie/details/lottie_frame_provider_direct.h
|
||||
lottie/details/lottie_frame_provider_shared.cpp
|
||||
lottie/details/lottie_frame_provider_shared.h
|
||||
lottie/details/lottie_frame_renderer.cpp
|
||||
lottie/details/lottie_frame_renderer.h
|
||||
lottie/lottie_animation.cpp
|
||||
lottie/lottie_animation.h
|
||||
lottie/lottie_common.cpp
|
||||
lottie/lottie_common.h
|
||||
lottie/lottie_frame_generator.cpp
|
||||
lottie/lottie_frame_generator.h
|
||||
lottie/lottie_multi_player.cpp
|
||||
lottie/lottie_multi_player.h
|
||||
lottie/lottie_player.h
|
||||
lottie/lottie_single_player.cpp
|
||||
lottie/lottie_single_player.h
|
||||
lottie/lottie_icon.cpp
|
||||
lottie/lottie_icon.h
|
||||
)
|
||||
|
||||
if (DESKTOP_APP_LOTTIE_USE_CACHE)
|
||||
nice_target_sources(lib_lottie ${src_loc}
|
||||
PRIVATE
|
||||
lottie/details/lottie_cache.cpp
|
||||
lottie/details/lottie_cache.h
|
||||
lottie/details/lottie_cache_frame_storage.cpp
|
||||
lottie/details/lottie_cache_frame_storage.h
|
||||
lottie/details/lottie_frame_provider_cached.cpp
|
||||
lottie/details/lottie_frame_provider_cached.h
|
||||
lottie/details/lottie_frame_provider_cached_multi.cpp
|
||||
lottie/details/lottie_frame_provider_cached_multi.h
|
||||
)
|
||||
target_compile_definitions(lib_lottie PRIVATE LOTTIE_USE_CACHE)
|
||||
target_link_libraries(lib_lottie
|
||||
PRIVATE
|
||||
desktop-app::lib_ffmpeg
|
||||
desktop-app::external_lz4
|
||||
)
|
||||
endif()
|
||||
|
||||
if (DESKTOP_APP_LOTTIE_DISABLE_RECOLORING)
|
||||
target_compile_definitions(lib_lottie PRIVATE LOTTIE_DISABLE_RECOLORING)
|
||||
endif()
|
||||
|
||||
target_include_directories(lib_lottie
|
||||
PUBLIC
|
||||
${src_loc}
|
||||
)
|
||||
|
||||
target_link_libraries(lib_lottie
|
||||
PUBLIC
|
||||
desktop-app::lib_ui
|
||||
PRIVATE
|
||||
desktop-app::external_rlottie
|
||||
)
|
||||
354
Telegram/lib_lottie/lottie/details/lottie_cache.cpp
Normal file
354
Telegram/lib_lottie/lottie/details/lottie_cache.cpp
Normal file
@@ -0,0 +1,354 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "lottie/details/lottie_cache.h"
|
||||
|
||||
#include "lottie/details/lottie_frame_renderer.h"
|
||||
#include "ffmpeg/ffmpeg_utility.h"
|
||||
#include "base/bytes.h"
|
||||
#include "base/assertion.h"
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QIODevice>
|
||||
#include <range/v3/numeric/accumulate.hpp>
|
||||
|
||||
namespace Lottie {
|
||||
namespace {
|
||||
|
||||
// Must not exceed max database allowed entry size.
|
||||
constexpr auto kMaxCacheSize = 10 * 1024 * 1024;
|
||||
|
||||
} // namespace
|
||||
|
||||
Cache::Cache(
|
||||
const QByteArray &data,
|
||||
const FrameRequest &request,
|
||||
FnMut<void(QByteArray &&cached)> put)
|
||||
: _data(data)
|
||||
, _put(std::move(put)) {
|
||||
if (!readHeader(request)) {
|
||||
_framesReady = 0;
|
||||
_framesInData = 0;
|
||||
_data = QByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
Cache::Cache(Cache &&) = default;
|
||||
|
||||
Cache &Cache::operator=(Cache&&) = default;
|
||||
|
||||
Cache::~Cache() {
|
||||
finalizeEncoding();
|
||||
}
|
||||
|
||||
void Cache::init(
|
||||
QSize original,
|
||||
int frameRate,
|
||||
int framesCount,
|
||||
const FrameRequest &request) {
|
||||
_size = request.size(original, sizeRounding());
|
||||
_original = original;
|
||||
_frameRate = frameRate;
|
||||
_framesCount = framesCount;
|
||||
_framesReady = 0;
|
||||
_framesInData = 0;
|
||||
prepareBuffers();
|
||||
}
|
||||
|
||||
int Cache::sizeRounding() const {
|
||||
return 8;
|
||||
}
|
||||
|
||||
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, sizeRounding()) != size) {
|
||||
return false;
|
||||
}
|
||||
_encoder = static_cast<Encoder>(encoder);
|
||||
_size = size;
|
||||
_original = original;
|
||||
_frameRate = frameRate;
|
||||
_framesCount = framesCount;
|
||||
_framesReady = framesReady;
|
||||
_framesInData = framesReady;
|
||||
prepareBuffers();
|
||||
return (renderFrame(_firstFrame, request, 0) == FrameRenderResult::Ok);
|
||||
}
|
||||
|
||||
QImage Cache::takeFirstFrame() {
|
||||
return std::move(_firstFrame);
|
||||
}
|
||||
|
||||
FrameRenderResult Cache::renderFrame(
|
||||
QImage &to,
|
||||
const FrameRequest &request,
|
||||
int index) {
|
||||
const auto result = renderFrame(_readContext, to, request, index);
|
||||
if (result == FrameRenderResult::Ok) {
|
||||
if (index + 1 == _framesReady && _data.size() > _readContext.offset) {
|
||||
_data.resize(_readContext.offset);
|
||||
}
|
||||
} else if (result == FrameRenderResult::BadCacheSize) {
|
||||
_framesReady = 0;
|
||||
_framesInData = 0;
|
||||
_data.clear();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
FrameRenderResult Cache::renderFrame(
|
||||
CacheReadContext &context,
|
||||
QImage &to,
|
||||
const FrameRequest &request,
|
||||
int index) const {
|
||||
Expects(index >= _framesReady
|
||||
|| index == context.offsetFrameIndex
|
||||
|| index == 0);
|
||||
Expects(index >= _framesReady || context.ready());
|
||||
|
||||
if (index >= _framesReady) {
|
||||
return FrameRenderResult::NotReady;
|
||||
} else if (request.size(_original, sizeRounding()) != _size) {
|
||||
return FrameRenderResult::BadCacheSize;
|
||||
} else if (index == 0) {
|
||||
context.offsetFrameIndex = 0;
|
||||
context.offset = headerSize();
|
||||
}
|
||||
const auto [ok, xored] = readCompressedFrame(context);
|
||||
if (!ok || (xored && index == 0)) {
|
||||
return FrameRenderResult::Failed;
|
||||
}
|
||||
if (xored) {
|
||||
Xor(context.previous, context.uncompressed);
|
||||
} else {
|
||||
std::swap(context.uncompressed, context.previous);
|
||||
}
|
||||
Decode(to, context.previous, _size, context.decodeContext);
|
||||
return FrameRenderResult::Ok;
|
||||
}
|
||||
|
||||
void Cache::appendFrame(
|
||||
const QImage &frame,
|
||||
const FrameRequest &request,
|
||||
int index) {
|
||||
if (request.size(_original, sizeRounding()) != _size) {
|
||||
_framesReady = 0;
|
||||
_framesInData = 0;
|
||||
_data = QByteArray();
|
||||
}
|
||||
if (index != _framesReady) {
|
||||
return;
|
||||
} else if (index == 0) {
|
||||
_size = request.size(_original, sizeRounding());
|
||||
_encode = EncodeFields();
|
||||
_encode.compressedFrames.reserve(_framesCount);
|
||||
prepareBuffers();
|
||||
}
|
||||
Assert(frame.size() == _size);
|
||||
Assert(_readContext.ready());
|
||||
Encode(_readContext.uncompressed, frame, _encode.cache, _encode.context);
|
||||
CompressAndSwapFrame(
|
||||
_encode.compressBuffer,
|
||||
(index != 0) ? &_encode.xorCompressBuffer : nullptr,
|
||||
_readContext.uncompressed,
|
||||
_readContext.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();
|
||||
++_readContext.offsetFrameIndex;
|
||||
_readContext.offset += compressed.size();
|
||||
if (++_framesReady == _framesCount) {
|
||||
finalizeEncoding();
|
||||
}
|
||||
}
|
||||
|
||||
void Cache::finalizeEncoding() {
|
||||
if (_encode.compressedFrames.empty() || !_put) {
|
||||
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;
|
||||
}
|
||||
_framesInData = _framesReady;
|
||||
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() {
|
||||
prepareBuffers(_readContext);
|
||||
}
|
||||
|
||||
void Cache::prepareBuffers(CacheReadContext &context) const {
|
||||
if (_size.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 12 bit per pixel in YUV420P.
|
||||
const auto bytesPerLine = _size.width();
|
||||
context.offset = headerSize();
|
||||
context.offsetFrameIndex = 0;
|
||||
context.uncompressed.allocate(bytesPerLine, _size.height());
|
||||
context.previous.allocate(bytesPerLine, _size.height());
|
||||
}
|
||||
|
||||
void Cache::keepUpContext(CacheReadContext &context) const {
|
||||
Expects(!context.ready()
|
||||
|| context.previous.size() == _readContext.previous.size());
|
||||
Expects(!context.ready()
|
||||
|| context.uncompressed.size() == _readContext.uncompressed.size());
|
||||
Expects(&context != &_readContext);
|
||||
|
||||
if (!context.ready()) {
|
||||
prepareBuffers(context);
|
||||
}
|
||||
context.offset = _readContext.offset;
|
||||
context.offsetFrameIndex = _readContext.offsetFrameIndex;
|
||||
memcpy(
|
||||
context.previous.data(),
|
||||
_readContext.previous.data(),
|
||||
_readContext.previous.size());
|
||||
memcpy(
|
||||
context.uncompressed.data(),
|
||||
_readContext.uncompressed.data(),
|
||||
_readContext.uncompressed.size());
|
||||
}
|
||||
|
||||
Cache::ReadResult Cache::readCompressedFrame(
|
||||
CacheReadContext &context) const {
|
||||
Expects(context.ready());
|
||||
|
||||
auto length = qint32(0);
|
||||
const auto part = [&] {
|
||||
if (context.offsetFrameIndex >= _framesInData) {
|
||||
// One reader is still accumulating compressed frames,
|
||||
// while second reader already started reading after the first.
|
||||
const auto readyIndex = context.offsetFrameIndex - _framesInData;
|
||||
Assert(readyIndex < _encode.compressedFrames.size());
|
||||
return bytes::make_span(_encode.compressedFrames[readyIndex]);
|
||||
} else if (_data.size() > context.offset) {
|
||||
return bytes::make_span(_data).subspan(context.offset);
|
||||
} else {
|
||||
return bytes::const_span();
|
||||
}
|
||||
}();
|
||||
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;
|
||||
}
|
||||
const auto ok = (length <= bytes.size())
|
||||
? UncompressToRaw(context.uncompressed, bytes.subspan(0, length))
|
||||
: false;
|
||||
if (ok) {
|
||||
context.offset += sizeof(length) + length;
|
||||
++context.offsetFrameIndex;
|
||||
}
|
||||
return { ok, xored };
|
||||
}
|
||||
|
||||
} // namespace Lottie
|
||||
114
Telegram/lib_lottie/lottie/details/lottie_cache.h
Normal file
114
Telegram/lib_lottie/lottie/details/lottie_cache.h
Normal file
@@ -0,0 +1,114 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "ffmpeg/ffmpeg_utility.h"
|
||||
#include "lottie/details/lottie_cache_frame_storage.h"
|
||||
#include "lottie/lottie_common.h"
|
||||
|
||||
#include <QImage>
|
||||
#include <QSize>
|
||||
#include <QByteArray>
|
||||
|
||||
namespace Lottie {
|
||||
|
||||
struct FrameRequest;
|
||||
|
||||
struct CacheReadContext {
|
||||
EncodedStorage uncompressed;
|
||||
EncodedStorage previous;
|
||||
FFmpeg::SwscalePointer decodeContext;
|
||||
int offset = 0;
|
||||
int offsetFrameIndex = 0;
|
||||
|
||||
[[nodiscard]] bool ready() const {
|
||||
return (offset != 0);
|
||||
}
|
||||
};
|
||||
|
||||
class Cache {
|
||||
public:
|
||||
enum class Encoder : qint8 {
|
||||
YUV420A4_LZ4,
|
||||
};
|
||||
|
||||
Cache(
|
||||
const QByteArray &data,
|
||||
const FrameRequest &request,
|
||||
FnMut<void(QByteArray &&cached)> put);
|
||||
Cache(Cache &&);
|
||||
Cache &operator=(Cache&&);
|
||||
~Cache();
|
||||
|
||||
void init(
|
||||
QSize original,
|
||||
int frameRate,
|
||||
int framesCount,
|
||||
const FrameRequest &request);
|
||||
[[nodiscard]] int sizeRounding() const;
|
||||
[[nodiscard]] int frameRate() const;
|
||||
[[nodiscard]] int framesReady() const;
|
||||
[[nodiscard]] int framesCount() const;
|
||||
[[nodiscard]] QSize originalSize() const;
|
||||
[[nodiscard]] QImage takeFirstFrame();
|
||||
|
||||
void prepareBuffers(CacheReadContext &context) const;
|
||||
void keepUpContext(CacheReadContext &context) const;
|
||||
|
||||
[[nodiscard]] FrameRenderResult renderFrame(
|
||||
QImage &to,
|
||||
const FrameRequest &request,
|
||||
int index);
|
||||
[[nodiscard]] FrameRenderResult renderFrame(
|
||||
CacheReadContext &context,
|
||||
QImage &to,
|
||||
const FrameRequest &request,
|
||||
int index) const;
|
||||
void appendFrame(
|
||||
const QImage &frame,
|
||||
const FrameRequest &request,
|
||||
int index);
|
||||
|
||||
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(
|
||||
CacheReadContext &context) const;
|
||||
|
||||
QByteArray _data;
|
||||
EncodeFields _encode;
|
||||
QSize _size;
|
||||
QSize _original;
|
||||
CacheReadContext _readContext;
|
||||
QImage _firstFrame;
|
||||
int _frameRate = 0;
|
||||
int _framesCount = 0;
|
||||
int _framesReady = 0;
|
||||
int _framesInData = 0;
|
||||
Encoder _encoder = Encoder::YUV420A4_LZ4;
|
||||
FnMut<void(QByteArray &&cached)> _put;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Lottie
|
||||
@@ -0,0 +1,361 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "lottie/details/lottie_cache_frame_storage.h"
|
||||
|
||||
#include "base/assertion.h"
|
||||
|
||||
#include <QtGui/QImage>
|
||||
#include <lz4.h>
|
||||
#include <lz4hc.h>
|
||||
|
||||
namespace Lottie {
|
||||
namespace {
|
||||
|
||||
constexpr auto kAlignStorage = 16;
|
||||
|
||||
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] = { int(to.bytesPerLine()), 0 };
|
||||
|
||||
sws_scale(
|
||||
context.get(),
|
||||
src,
|
||||
srcLineSize,
|
||||
0,
|
||||
to.height(),
|
||||
dst,
|
||||
dstLineSize);
|
||||
}
|
||||
|
||||
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 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] = { int(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
|
||||
};
|
||||
|
||||
sws_scale(
|
||||
context.get(),
|
||||
src,
|
||||
srcLineSize,
|
||||
0,
|
||||
from.height(),
|
||||
dst,
|
||||
dstLineSize);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
void Encode(
|
||||
EncodedStorage &to,
|
||||
const QImage &from,
|
||||
QImage &cache,
|
||||
FFmpeg::SwscalePointer &context) {
|
||||
FFmpeg::UnPremultiply(cache, from);
|
||||
EncodeRGB2YUV(to, cache, context);
|
||||
EncodeAlpha(to, cache);
|
||||
}
|
||||
|
||||
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 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));
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
} // namespace Lottie
|
||||
@@ -0,0 +1,69 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "ffmpeg/ffmpeg_utility.h"
|
||||
|
||||
namespace Lottie {
|
||||
|
||||
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;
|
||||
|
||||
};
|
||||
|
||||
void Xor(EncodedStorage &to, const EncodedStorage &from);
|
||||
|
||||
void Encode(
|
||||
EncodedStorage &to,
|
||||
const QImage &from,
|
||||
QImage &cache,
|
||||
FFmpeg::SwscalePointer &context);
|
||||
|
||||
void Decode(
|
||||
QImage &to,
|
||||
const EncodedStorage &from,
|
||||
const QSize &fromSize,
|
||||
FFmpeg::SwscalePointer &context);
|
||||
|
||||
void CompressFromRaw(QByteArray &to, const EncodedStorage &from);
|
||||
void CompressAndSwapFrame(
|
||||
QByteArray &to,
|
||||
QByteArray *additional,
|
||||
EncodedStorage &frame,
|
||||
EncodedStorage &previous);
|
||||
|
||||
bool UncompressToRaw(EncodedStorage &to, bytes::const_span from);
|
||||
|
||||
} // namespace Lottie
|
||||
45
Telegram/lib_lottie/lottie/details/lottie_frame_provider.h
Normal file
45
Telegram/lib_lottie/lottie/details/lottie_frame_provider.h
Normal file
@@ -0,0 +1,45 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "lottie/lottie_common.h"
|
||||
|
||||
namespace Lottie {
|
||||
|
||||
struct FrameProviderToken {
|
||||
virtual ~FrameProviderToken() = default;
|
||||
|
||||
FrameRenderResult result = FrameRenderResult::Ok;
|
||||
bool exclusive = false;
|
||||
};
|
||||
|
||||
class FrameProvider {
|
||||
public:
|
||||
virtual ~FrameProvider() = default;
|
||||
|
||||
virtual QImage construct(
|
||||
std::unique_ptr<FrameProviderToken> &token,
|
||||
const FrameRequest &request) = 0;
|
||||
[[nodiscard]] virtual const Information &information() = 0;
|
||||
[[nodiscard]] virtual bool valid() = 0;
|
||||
|
||||
[[nodiscard]] virtual int sizeRounding() = 0;
|
||||
|
||||
[[nodiscard]] virtual std::unique_ptr<FrameProviderToken> createToken() {
|
||||
// Used for shared frame provider.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual bool render(
|
||||
const std::unique_ptr<FrameProviderToken> &token,
|
||||
QImage &to,
|
||||
const FrameRequest &request,
|
||||
int index) = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Lottie
|
||||
@@ -0,0 +1,128 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "lottie/details/lottie_frame_provider_cached.h"
|
||||
|
||||
namespace Lottie {
|
||||
|
||||
FrameProviderCached::FrameProviderCached(
|
||||
const QByteArray &content,
|
||||
FnMut<void(QByteArray &&cached)> put,
|
||||
const QByteArray &cached,
|
||||
const FrameRequest &request,
|
||||
Quality quality,
|
||||
const ColorReplacements *replacements)
|
||||
: _cache(cached, request, std::move(put))
|
||||
, _direct(quality)
|
||||
, _content(content)
|
||||
, _replacements(replacements) {
|
||||
if (!_cache.framesCount()
|
||||
|| (_cache.framesReady() < _cache.framesCount())) {
|
||||
if (!_direct.load(content, replacements)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
_direct.setInformation({
|
||||
.size = _cache.originalSize(),
|
||||
.frameRate = _cache.frameRate(),
|
||||
.framesCount = _cache.framesCount(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QImage FrameProviderCached::construct(
|
||||
std::unique_ptr<FrameProviderToken> &token,
|
||||
const FrameRequest &request) {
|
||||
auto cover = _cache.takeFirstFrame();
|
||||
using Token = FrameProviderCachedToken;
|
||||
const auto my = static_cast<Token*>(token.get());
|
||||
if (!my || my->exclusive) {
|
||||
if (!cover.isNull()) {
|
||||
if (my) {
|
||||
_cache.keepUpContext(my->context);
|
||||
}
|
||||
return cover;
|
||||
}
|
||||
const auto &info = information();
|
||||
_cache.init(
|
||||
info.size,
|
||||
info.frameRate,
|
||||
info.framesCount,
|
||||
request);
|
||||
}
|
||||
render(token, cover, request, 0);
|
||||
return cover;
|
||||
}
|
||||
|
||||
const Information &FrameProviderCached::information() {
|
||||
return _direct.information();
|
||||
}
|
||||
|
||||
bool FrameProviderCached::valid() {
|
||||
return _direct.valid();
|
||||
}
|
||||
|
||||
int FrameProviderCached::sizeRounding() {
|
||||
return _cache.sizeRounding();
|
||||
}
|
||||
|
||||
std::unique_ptr<FrameProviderToken> FrameProviderCached::createToken() {
|
||||
auto result = std::make_unique<FrameProviderCachedToken>();
|
||||
_cache.prepareBuffers(result->context);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool FrameProviderCached::render(
|
||||
const std::unique_ptr<FrameProviderToken> &token,
|
||||
QImage &to,
|
||||
const FrameRequest &request,
|
||||
int index) {
|
||||
if (!valid()) {
|
||||
if (token) {
|
||||
token->result = FrameRenderResult::Failed;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto original = information().size;
|
||||
const auto size = request.box.isEmpty()
|
||||
? original
|
||||
: request.size(original, sizeRounding());
|
||||
if (!GoodStorageForFrame(to, size)) {
|
||||
to = CreateFrameStorage(size);
|
||||
}
|
||||
using Token = FrameProviderCachedToken;
|
||||
const auto my = static_cast<Token*>(token.get());
|
||||
if (my && !my->exclusive) {
|
||||
// This must be a thread-safe request.
|
||||
my->result = _cache.renderFrame(my->context, to, request, index);
|
||||
return (my->result == FrameRenderResult::Ok);
|
||||
}
|
||||
const auto result = _cache.renderFrame(to, request, index);
|
||||
if (result == FrameRenderResult::Ok) {
|
||||
if (my) {
|
||||
_cache.keepUpContext(my->context);
|
||||
}
|
||||
return true;
|
||||
} else if (result == FrameRenderResult::Failed
|
||||
// We don't support changing size on the fly for shared providers.
|
||||
|| (result == FrameRenderResult::BadCacheSize && my)
|
||||
|| (!_direct.loaded() && !_direct.load(_content, _replacements))) {
|
||||
_direct.setInformation({});
|
||||
return false;
|
||||
}
|
||||
_direct.renderToPrepared(to, index);
|
||||
_cache.appendFrame(to, request, index);
|
||||
if (_cache.framesReady() == _cache.framesCount()) {
|
||||
_direct.unload();
|
||||
}
|
||||
if (my) {
|
||||
_cache.keepUpContext(my->context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Lottie
|
||||
@@ -0,0 +1,52 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "lottie/details/lottie_frame_provider_direct.h"
|
||||
#include "lottie/details/lottie_cache.h"
|
||||
|
||||
namespace Lottie {
|
||||
|
||||
struct FrameProviderCachedToken : FrameProviderToken {
|
||||
CacheReadContext context;
|
||||
};
|
||||
|
||||
class FrameProviderCached final : public FrameProvider {
|
||||
public:
|
||||
FrameProviderCached(
|
||||
const QByteArray &content,
|
||||
FnMut<void(QByteArray &&cached)> put,
|
||||
const QByteArray &cached,
|
||||
const FrameRequest &request,
|
||||
Quality quality,
|
||||
const ColorReplacements *replacements);
|
||||
|
||||
QImage construct(
|
||||
std::unique_ptr<FrameProviderToken> &token,
|
||||
const FrameRequest &request) override;
|
||||
const Information &information() override;
|
||||
bool valid() override;
|
||||
|
||||
int sizeRounding() override;
|
||||
|
||||
std::unique_ptr<FrameProviderToken> createToken() override;
|
||||
|
||||
bool render(
|
||||
const std::unique_ptr<FrameProviderToken> &token,
|
||||
QImage &to,
|
||||
const FrameRequest &request,
|
||||
int index) override;
|
||||
|
||||
private:
|
||||
Cache _cache;
|
||||
FrameProviderDirect _direct;
|
||||
const QByteArray _content;
|
||||
const ColorReplacements *_replacements = nullptr;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Lottie
|
||||
@@ -0,0 +1,234 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "lottie/details/lottie_frame_provider_cached_multi.h"
|
||||
|
||||
#include "base/assertion.h"
|
||||
|
||||
#include <range/v3/numeric/accumulate.hpp>
|
||||
|
||||
namespace Lottie {
|
||||
|
||||
FrameProviderCachedMulti::FrameProviderCachedMulti(
|
||||
const QByteArray &content,
|
||||
FnMut<void(int index, QByteArray &&cached)> put,
|
||||
std::vector<QByteArray> caches,
|
||||
const FrameRequest &request,
|
||||
Quality quality,
|
||||
const ColorReplacements *replacements)
|
||||
: _content(content)
|
||||
, _replacements(replacements)
|
||||
, _put(std::move(put))
|
||||
, _direct(quality) {
|
||||
Expects(!caches.empty());
|
||||
|
||||
_caches.reserve(caches.size());
|
||||
const auto emplace = [&](const QByteArray &cached) {
|
||||
const auto index = int(_caches.size());
|
||||
_caches.emplace_back(cached, request, [=](QByteArray &&v) {
|
||||
// We capture reference to _put, so the provider is not movable.
|
||||
_put(index, std::move(v));
|
||||
});
|
||||
};
|
||||
const auto load = [&] {
|
||||
if (_direct.loaded() || _direct.load(content, replacements)) {
|
||||
return true;
|
||||
}
|
||||
_caches.clear();
|
||||
return false;
|
||||
};
|
||||
const auto fill = [&] {
|
||||
if (!load()) {
|
||||
return false;
|
||||
}
|
||||
while (_caches.size() < caches.size()) {
|
||||
emplace({});
|
||||
}
|
||||
return true;
|
||||
};
|
||||
for (const auto &cached : caches) {
|
||||
emplace(cached);
|
||||
auto &cache = _caches.back();
|
||||
const auto &first = _caches.front();
|
||||
Assert(cache.sizeRounding() == first.sizeRounding());
|
||||
|
||||
if (!cache.framesCount()) {
|
||||
if (!fill()) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
} else if (cache.framesReady() < cache.framesCount() && !load()) {
|
||||
return;
|
||||
} else if (cache.frameRate() != first.frameRate()
|
||||
|| cache.originalSize() != first.originalSize()) {
|
||||
_caches.pop_back();
|
||||
if (!fill()) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!_direct.loaded()) {
|
||||
_direct.setInformation({
|
||||
.size = _caches.front().originalSize(),
|
||||
.frameRate = _caches.front().frameRate(),
|
||||
.framesCount = ranges::accumulate(
|
||||
_caches,
|
||||
0,
|
||||
std::plus<>(),
|
||||
&Cache::framesCount),
|
||||
});
|
||||
}
|
||||
if (!validateFramesPerCache() && _framesPerCache > 0) {
|
||||
fill();
|
||||
}
|
||||
}
|
||||
|
||||
bool FrameProviderCachedMulti::validateFramesPerCache() {
|
||||
const auto &info = information();
|
||||
const auto count = int(_caches.size());
|
||||
_framesPerCache = (info.framesCount + count - 1) / count;
|
||||
if (!_framesPerCache
|
||||
|| (info.framesCount <= (count - 1) * _framesPerCache)) {
|
||||
_framesPerCache = 0;
|
||||
return false;
|
||||
}
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
const auto cacheFramesCount = _caches[i].framesCount();
|
||||
if (!cacheFramesCount) {
|
||||
break;
|
||||
}
|
||||
const auto shouldBe = (i + 1 == count
|
||||
? (info.framesCount - (count - 1) * _framesPerCache)
|
||||
: _framesPerCache);
|
||||
if (cacheFramesCount != shouldBe) {
|
||||
_caches.erase(begin(_caches) + i, end(_caches));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QImage FrameProviderCachedMulti::construct(
|
||||
std::unique_ptr<FrameProviderToken> &token,
|
||||
const FrameRequest &request) {
|
||||
if (!_framesPerCache) {
|
||||
if (token) {
|
||||
token->result = FrameRenderResult::Failed;
|
||||
}
|
||||
return QImage();
|
||||
}
|
||||
auto cover = QImage();
|
||||
using Token = FrameProviderCachedMultiToken;
|
||||
const auto my = static_cast<Token*>(token.get());
|
||||
if (!my || my->exclusive) {
|
||||
const auto &info = information();
|
||||
const auto count = int(_caches.size());
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
auto cacheCover = _caches[i].takeFirstFrame();
|
||||
if (cacheCover.isNull()) {
|
||||
_caches[i].init(
|
||||
info.size,
|
||||
info.frameRate,
|
||||
(i + 1 == count
|
||||
? (info.framesCount - (count - 1) * _framesPerCache)
|
||||
: _framesPerCache),
|
||||
request);
|
||||
} else if (!i) {
|
||||
cover = std::move(cacheCover);
|
||||
}
|
||||
}
|
||||
if (!cover.isNull()) {
|
||||
if (my) {
|
||||
_caches[0].keepUpContext(my->context);
|
||||
}
|
||||
return cover;
|
||||
}
|
||||
}
|
||||
render(token, cover, request, 0);
|
||||
return cover;
|
||||
}
|
||||
|
||||
const Information &FrameProviderCachedMulti::information() {
|
||||
return _direct.information();
|
||||
}
|
||||
|
||||
bool FrameProviderCachedMulti::valid() {
|
||||
return _direct.valid() && (_framesPerCache > 0);
|
||||
}
|
||||
|
||||
int FrameProviderCachedMulti::sizeRounding() {
|
||||
return _caches.front().sizeRounding();
|
||||
}
|
||||
|
||||
std::unique_ptr<FrameProviderToken> FrameProviderCachedMulti::createToken() {
|
||||
auto result = std::make_unique<FrameProviderCachedMultiToken>();
|
||||
if (!_caches.empty()) {
|
||||
_caches.front().prepareBuffers(result->context);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool FrameProviderCachedMulti::render(
|
||||
const std::unique_ptr<FrameProviderToken> &token,
|
||||
QImage &to,
|
||||
const FrameRequest &request,
|
||||
int index) {
|
||||
if (!valid()) {
|
||||
if (token) {
|
||||
token->result = FrameRenderResult::Failed;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto original = information().size;
|
||||
const auto size = request.box.isEmpty()
|
||||
? original
|
||||
: request.size(original, sizeRounding());
|
||||
if (!GoodStorageForFrame(to, size)) {
|
||||
to = CreateFrameStorage(size);
|
||||
}
|
||||
const auto cacheIndex = index / _framesPerCache;
|
||||
const auto indexInCache = index % _framesPerCache;
|
||||
Assert(cacheIndex < _caches.size());
|
||||
auto &cache = _caches[cacheIndex];
|
||||
using Token = FrameProviderCachedMultiToken;
|
||||
const auto my = static_cast<Token*>(token.get());
|
||||
if (my && !my->exclusive) {
|
||||
// Many threads may get here simultaneously.
|
||||
my->result = cache.renderFrame(
|
||||
my->context,
|
||||
to,
|
||||
request,
|
||||
indexInCache);
|
||||
return (my->result == FrameRenderResult::Ok);
|
||||
}
|
||||
const auto result = cache.renderFrame(to, request, indexInCache);
|
||||
if (result == FrameRenderResult::Ok) {
|
||||
if (my) {
|
||||
cache.keepUpContext(my->context);
|
||||
}
|
||||
return true;
|
||||
} else if (result == FrameRenderResult::Failed
|
||||
// We don't support changing size on the fly for shared providers.
|
||||
|| (result == FrameRenderResult::BadCacheSize && my)
|
||||
|| (!_direct.loaded() && !_direct.load(_content, _replacements))) {
|
||||
_direct.setInformation({});
|
||||
return false;
|
||||
}
|
||||
_direct.renderToPrepared(to, index);
|
||||
cache.appendFrame(to, request, indexInCache);
|
||||
if (cache.framesReady() == cache.framesCount()
|
||||
&& cacheIndex + 1 == _caches.size()) {
|
||||
_direct.unload();
|
||||
}
|
||||
if (my) {
|
||||
cache.keepUpContext(my->context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Lottie
|
||||
@@ -0,0 +1,60 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "lottie/details/lottie_frame_provider_direct.h"
|
||||
#include "lottie/details/lottie_cache.h"
|
||||
|
||||
namespace Lottie {
|
||||
|
||||
struct FrameProviderCachedMultiToken : FrameProviderToken {
|
||||
CacheReadContext context;
|
||||
};
|
||||
|
||||
class FrameProviderCachedMulti final : public FrameProvider {
|
||||
public:
|
||||
FrameProviderCachedMulti(
|
||||
const QByteArray &content,
|
||||
FnMut<void(int index, QByteArray &&cached)> put,
|
||||
std::vector<QByteArray> caches,
|
||||
const FrameRequest &request,
|
||||
Quality quality,
|
||||
const ColorReplacements *replacements);
|
||||
|
||||
FrameProviderCachedMulti(const FrameProviderCachedMulti &) = delete;
|
||||
FrameProviderCachedMulti &operator=(const FrameProviderCachedMulti &)
|
||||
= delete;
|
||||
|
||||
QImage construct(
|
||||
std::unique_ptr<FrameProviderToken> &token,
|
||||
const FrameRequest &request) override;
|
||||
const Information &information() override;
|
||||
bool valid() override;
|
||||
|
||||
int sizeRounding() override;
|
||||
|
||||
std::unique_ptr<FrameProviderToken> createToken() override;
|
||||
|
||||
bool render(
|
||||
const std::unique_ptr<FrameProviderToken> &token,
|
||||
QImage &to,
|
||||
const FrameRequest &request,
|
||||
int index) override;
|
||||
|
||||
private:
|
||||
bool validateFramesPerCache();
|
||||
|
||||
const QByteArray _content;
|
||||
const ColorReplacements *_replacements = nullptr;
|
||||
FnMut<void(int index, QByteArray &&cached)> _put;
|
||||
FrameProviderDirect _direct;
|
||||
std::vector<Cache> _caches;
|
||||
int _framesPerCache = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Lottie
|
||||
@@ -0,0 +1,171 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "lottie/details/lottie_frame_provider_direct.h"
|
||||
|
||||
#include "lottie/lottie_wrap.h"
|
||||
#include "lottie/details/lottie_frame_renderer.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
|
||||
#include <rlottie.h>
|
||||
|
||||
namespace Lottie {
|
||||
namespace {
|
||||
|
||||
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 + 1) / 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;
|
||||
}
|
||||
|
||||
[[nodiscard]] rlottie::FitzModifier MapModifier(SkinModifier modifier) {
|
||||
using Result = rlottie::FitzModifier;
|
||||
switch (modifier) {
|
||||
case SkinModifier::None: return Result::None;
|
||||
case SkinModifier::Color1: return Result::Type12;
|
||||
case SkinModifier::Color2: return Result::Type3;
|
||||
case SkinModifier::Color3: return Result::Type4;
|
||||
case SkinModifier::Color4: return Result::Type5;
|
||||
case SkinModifier::Color5: return Result::Type6;
|
||||
}
|
||||
Unexpected("Unexpected modifier in MapModifier.");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
FrameProviderDirect::FrameProviderDirect(Quality quality)
|
||||
: _quality(quality) {
|
||||
}
|
||||
|
||||
FrameProviderDirect::~FrameProviderDirect() = default;
|
||||
|
||||
bool FrameProviderDirect::load(
|
||||
const QByteArray &content,
|
||||
const ColorReplacements *replacements) {
|
||||
_information = Information();
|
||||
|
||||
const auto string = ReadUtf8(Images::UnpackGzip(content));
|
||||
if (string.size() > kMaxFileSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_animation = LoadAnimationFromData(
|
||||
string,
|
||||
std::string(),
|
||||
std::string(),
|
||||
false,
|
||||
(replacements
|
||||
? replacements->replacements
|
||||
: std::vector<std::pair<std::uint32_t, std::uint32_t>>()),
|
||||
(replacements
|
||||
? MapModifier(replacements->modifier)
|
||||
: rlottie::FitzModifier::None));
|
||||
if (!_animation) {
|
||||
return false;
|
||||
}
|
||||
auto width = size_t(0);
|
||||
auto height = size_t(0);
|
||||
_animation->size(width, height);
|
||||
const auto rate = GetLottieFrameRate(_animation.get(), _quality);
|
||||
const auto count = GetLottieFramesCount(_animation.get(), _quality);
|
||||
return setInformation({
|
||||
.size = QSize(int(width), int(height)),
|
||||
.frameRate = int(rate),
|
||||
.framesCount = int(count),
|
||||
});
|
||||
}
|
||||
|
||||
bool FrameProviderDirect::loaded() const {
|
||||
return (_animation != nullptr);
|
||||
}
|
||||
|
||||
void FrameProviderDirect::unload() {
|
||||
_animation = nullptr;
|
||||
}
|
||||
|
||||
bool FrameProviderDirect::setInformation(Information information) {
|
||||
if (information.size.isEmpty()
|
||||
|| information.size.width() > kMaxSize
|
||||
|| information.size.height() > kMaxSize
|
||||
|| !information.frameRate
|
||||
|| information.frameRate > kMaxFrameRate
|
||||
|| !information.framesCount
|
||||
|| information.framesCount > kMaxFramesCount) {
|
||||
return false;
|
||||
}
|
||||
_information = information;
|
||||
return true;
|
||||
}
|
||||
|
||||
const Information &FrameProviderDirect::information() {
|
||||
return _information;
|
||||
}
|
||||
|
||||
bool FrameProviderDirect::valid() {
|
||||
return _information.framesCount > 0;
|
||||
}
|
||||
|
||||
QImage FrameProviderDirect::construct(
|
||||
std::unique_ptr<FrameProviderToken> &token,
|
||||
const FrameRequest &request) {
|
||||
auto cover = QImage();
|
||||
render(token, cover, request, 0);
|
||||
return cover;
|
||||
}
|
||||
|
||||
int FrameProviderDirect::sizeRounding() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
bool FrameProviderDirect::render(
|
||||
const std::unique_ptr<FrameProviderToken> &token,
|
||||
QImage &to,
|
||||
const FrameRequest &request,
|
||||
int index) {
|
||||
if (token && !token->exclusive) {
|
||||
token->result = FrameRenderResult::NotReady;
|
||||
return false;
|
||||
} else if (!valid()) {
|
||||
return false;
|
||||
}
|
||||
const auto original = information().size;
|
||||
const auto size = request.box.isEmpty()
|
||||
? original
|
||||
: request.size(original, sizeRounding());
|
||||
if (!GoodStorageForFrame(to, size)) {
|
||||
to = CreateFrameStorage(size);
|
||||
}
|
||||
renderToPrepared(to, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
void FrameProviderDirect::renderToPrepared(
|
||||
QImage &to,
|
||||
int index) const {
|
||||
to.fill(Qt::transparent);
|
||||
auto surface = rlottie::Surface(
|
||||
reinterpret_cast<uint32_t*>(to.bits()),
|
||||
to.width(),
|
||||
to.height(),
|
||||
to.bytesPerLine());
|
||||
_animation->renderSync(
|
||||
GetLottieFrameIndex(_animation.get(), _quality, index),
|
||||
surface);
|
||||
}
|
||||
|
||||
} // namespace Lottie
|
||||
@@ -0,0 +1,57 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "lottie/details/lottie_frame_provider.h"
|
||||
#include "lottie/lottie_common.h"
|
||||
|
||||
namespace rlottie {
|
||||
class Animation;
|
||||
} // namespace rlottie
|
||||
|
||||
namespace Lottie {
|
||||
|
||||
class FrameProviderDirect final : public FrameProvider {
|
||||
public:
|
||||
explicit FrameProviderDirect(Quality quality);
|
||||
~FrameProviderDirect();
|
||||
|
||||
bool load(
|
||||
const QByteArray &content,
|
||||
const ColorReplacements *replacements);
|
||||
[[nodiscard]] bool loaded() const;
|
||||
void unload();
|
||||
|
||||
bool setInformation(Information information);
|
||||
|
||||
QImage construct(
|
||||
std::unique_ptr<FrameProviderToken> &token,
|
||||
const FrameRequest &request) override;
|
||||
const Information &information() override;
|
||||
bool valid() override;
|
||||
|
||||
int sizeRounding() override;
|
||||
|
||||
bool render(
|
||||
const std::unique_ptr<FrameProviderToken> &token,
|
||||
QImage &to,
|
||||
const FrameRequest &request,
|
||||
int index) override;
|
||||
void renderToPrepared(QImage &to, int index) const;
|
||||
|
||||
private:
|
||||
const FrameProviderDirect *cthis() const {
|
||||
return this;
|
||||
}
|
||||
|
||||
std::unique_ptr<rlottie::Animation> _animation;
|
||||
Information _information;
|
||||
Quality _quality = Quality::Default;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Lottie
|
||||
@@ -0,0 +1,103 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "lottie/details/lottie_frame_provider_shared.h"
|
||||
|
||||
#include "base/assertion.h"
|
||||
|
||||
#include <crl/crl_on_main.h>
|
||||
|
||||
namespace Lottie {
|
||||
|
||||
FrameProviderShared::FrameProviderShared(
|
||||
FnMut<void(FnMut<void(std::unique_ptr<FrameProvider>)>)> factory) {
|
||||
_mutex.lockForWrite();
|
||||
factory(crl::guard(this, [=](std::unique_ptr<FrameProvider> shared) {
|
||||
_shared = std::move(shared);
|
||||
_mutex.unlock();
|
||||
}));
|
||||
}
|
||||
|
||||
QImage FrameProviderShared::construct(
|
||||
std::unique_ptr<FrameProviderToken> &token,
|
||||
const FrameRequest &request) {
|
||||
QWriteLocker lock(&_mutex);
|
||||
token = createToken();
|
||||
if (token) {
|
||||
token->exclusive = !_constructed;
|
||||
}
|
||||
auto result = _shared->construct(token, request);
|
||||
_constructed = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
const Information &FrameProviderShared::information() {
|
||||
static auto empty = Information();
|
||||
|
||||
QReadLocker lock(&_mutex);
|
||||
return _shared ? _shared->information() : empty;
|
||||
}
|
||||
|
||||
bool FrameProviderShared::valid() {
|
||||
QReadLocker lock(&_mutex);
|
||||
return _shared && _shared->valid();
|
||||
}
|
||||
|
||||
int FrameProviderShared::sizeRounding() {
|
||||
QReadLocker lock(&_mutex);
|
||||
Assert(_shared != nullptr);
|
||||
return _shared->sizeRounding();
|
||||
}
|
||||
|
||||
std::unique_ptr<FrameProviderToken> FrameProviderShared::createToken() {
|
||||
Expects(_shared != nullptr);
|
||||
|
||||
return _shared->createToken();
|
||||
}
|
||||
|
||||
bool FrameProviderShared::render(
|
||||
const std::unique_ptr<FrameProviderToken> &token,
|
||||
QImage &to,
|
||||
const FrameRequest &request,
|
||||
int index) {
|
||||
QReadLocker readLock(&_mutex);
|
||||
if (!_shared) {
|
||||
if (token) {
|
||||
token->result = FrameRenderResult::Failed;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (token) {
|
||||
token->exclusive = false;
|
||||
_shared->render(token, to, request, index);
|
||||
if (token->result == FrameRenderResult::Ok) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
readLock.unlock();
|
||||
|
||||
QWriteLocker lock(&_mutex);
|
||||
if (!_shared) {
|
||||
if (token) {
|
||||
token->result = FrameRenderResult::Failed;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (token) {
|
||||
_shared->render(token, to, request, index);
|
||||
if (token->result == FrameRenderResult::Ok) {
|
||||
return true;
|
||||
} else if (token->result == FrameRenderResult::Failed) {
|
||||
_shared = nullptr;
|
||||
return false;
|
||||
}
|
||||
token->exclusive = true;
|
||||
}
|
||||
return _shared->render(token, to, request, index);
|
||||
}
|
||||
|
||||
} // namespace Lottie
|
||||
@@ -0,0 +1,46 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "lottie/details/lottie_frame_provider.h"
|
||||
#include "base/weak_ptr.h"
|
||||
|
||||
#include <QtCore/QReadWriteLock>
|
||||
|
||||
namespace Lottie {
|
||||
|
||||
class FrameProviderShared final
|
||||
: public FrameProvider
|
||||
, public base::has_weak_ptr {
|
||||
public:
|
||||
explicit FrameProviderShared(
|
||||
FnMut<void(FnMut<void(std::unique_ptr<FrameProvider>)>)> factory);
|
||||
|
||||
QImage construct(
|
||||
std::unique_ptr<FrameProviderToken> &token,
|
||||
const FrameRequest &request) override;
|
||||
const Information &information() override;
|
||||
bool valid() override;
|
||||
|
||||
int sizeRounding() override;
|
||||
|
||||
std::unique_ptr<FrameProviderToken> createToken() override;
|
||||
|
||||
bool render(
|
||||
const std::unique_ptr<FrameProviderToken> &token,
|
||||
QImage &to,
|
||||
const FrameRequest &request,
|
||||
int index) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<FrameProvider> _shared;
|
||||
QReadWriteLock _mutex;
|
||||
bool _constructed = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Lottie
|
||||
488
Telegram/lib_lottie/lottie/details/lottie_frame_renderer.cpp
Normal file
488
Telegram/lib_lottie/lottie/details/lottie_frame_renderer.cpp
Normal file
@@ -0,0 +1,488 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "lottie/details/lottie_frame_renderer.h"
|
||||
|
||||
#include "lottie/lottie_player.h"
|
||||
#include "lottie/lottie_animation.h"
|
||||
#include "lottie/details/lottie_frame_provider.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "base/flat_map.h"
|
||||
#include "base/assertion.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <rlottie.h>
|
||||
#include <range/v3/algorithm/find.hpp>
|
||||
#include <range/v3/algorithm/count_if.hpp>
|
||||
|
||||
namespace Lottie {
|
||||
namespace {
|
||||
|
||||
std::weak_ptr<FrameRenderer> GlobalInstance;
|
||||
|
||||
} // 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.alpha() != 0 || request.mirrorHorizontal) {
|
||||
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,
|
||||
int sizeRounding,
|
||||
QImage storage) {
|
||||
Expects(!request.box.isEmpty());
|
||||
|
||||
const auto size = request.size(
|
||||
original.size(),
|
||||
sizeRounding);
|
||||
if (!GoodStorageForFrame(storage, size)) {
|
||||
storage = CreateFrameStorage(size);
|
||||
}
|
||||
storage.fill(Qt::transparent);
|
||||
|
||||
{
|
||||
QPainter p(&storage);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
p.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
p.drawImage(QRect(QPoint(), size), original);
|
||||
}
|
||||
if (request.mirrorHorizontal) {
|
||||
storage = std::move(storage).mirrored(true, false);
|
||||
}
|
||||
if (request.colored.alpha() != 0) {
|
||||
storage = Images::Colored(std::move(storage), request.colored);
|
||||
}
|
||||
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,
|
||||
frame->sizeRounding,
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
SharedState::SharedState(
|
||||
std::shared_ptr<FrameProvider> provider,
|
||||
const FrameRequest &request)
|
||||
: _provider(std::move(provider)) {
|
||||
if (_provider->valid()) {
|
||||
init(_provider->construct(_token, request), request);
|
||||
}
|
||||
}
|
||||
|
||||
int SharedState::sizeRounding() const {
|
||||
return _provider->sizeRounding();
|
||||
}
|
||||
|
||||
void SharedState::init(QImage cover, const FrameRequest &request) {
|
||||
Expects(!initialized());
|
||||
|
||||
_frames[0].request = request;
|
||||
_frames[0].sizeRounding = sizeRounding();
|
||||
_frames[0].original = std::move(cover);
|
||||
_framesCount = _provider->information().framesCount;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!_framesCount) {
|
||||
return;
|
||||
}
|
||||
const auto rendered = _provider->render(
|
||||
_token,
|
||||
frame->original,
|
||||
request,
|
||||
(++_frameIndex) % _framesCount);
|
||||
if (!rendered) {
|
||||
return;
|
||||
}
|
||||
frame->request = request;
|
||||
frame->sizeRounding = sizeRounding();
|
||||
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 { IsRendered(frame) };
|
||||
} else if (!IsRendered(next)) {
|
||||
renderNextFrame(next, request);
|
||||
return { IsRendered(next) };
|
||||
}
|
||||
return { false };
|
||||
};
|
||||
const auto present = [&](int counter, int index) -> RenderResult {
|
||||
const auto frame = getFrame(index);
|
||||
if (!IsRendered(frame)) {
|
||||
renderNextFrame(frame, request);
|
||||
if (!IsRendered(frame)) {
|
||||
return { false };
|
||||
}
|
||||
}
|
||||
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 {
|
||||
const auto rate = _provider->information().frameRate;
|
||||
return _started
|
||||
+ _delay
|
||||
+ crl::time(1000) * (_skippedFrames + index) / rate;
|
||||
}
|
||||
|
||||
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 _provider->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 _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
|
||||
136
Telegram/lib_lottie/lottie/details/lottie_frame_renderer.h
Normal file
136
Telegram/lib_lottie/lottie/details/lottie_frame_renderer.h
Normal file
@@ -0,0 +1,136 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#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 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 FrameProvider;
|
||||
struct FrameProviderToken;
|
||||
|
||||
struct Frame {
|
||||
QImage original;
|
||||
crl::time displayed = kDisplayedInitial;
|
||||
crl::time display = kTimeUnknown;
|
||||
int index = 0;
|
||||
int sizeRounding = 0;
|
||||
|
||||
FrameRequest request;
|
||||
QImage prepared;
|
||||
};
|
||||
|
||||
QImage PrepareFrameByRequest(
|
||||
not_null<Frame*> frame,
|
||||
bool useExistingPrepared);
|
||||
|
||||
class SharedState {
|
||||
public:
|
||||
SharedState(
|
||||
std::shared_ptr<FrameProvider> provider,
|
||||
const FrameRequest &request);
|
||||
|
||||
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();
|
||||
|
||||
struct RenderResult {
|
||||
bool rendered = false;
|
||||
base::weak_ptr<Player> notify;
|
||||
};
|
||||
[[nodiscard]] RenderResult renderNextFrame(const FrameRequest &request);
|
||||
|
||||
~SharedState();
|
||||
|
||||
private:
|
||||
void init(QImage cover, const FrameRequest &request);
|
||||
void renderNextFrame(
|
||||
not_null<Frame*> frame,
|
||||
const FrameRequest &request);
|
||||
[[nodiscard]] int sizeRounding() const;
|
||||
[[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 _framesCount = 0;
|
||||
int _skippedFrames = 0;
|
||||
const std::shared_ptr<FrameProvider> _provider;
|
||||
std::unique_ptr<FrameProviderToken> _token;
|
||||
|
||||
};
|
||||
|
||||
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
|
||||
334
Telegram/lib_lottie/lottie/lottie_animation.cpp
Normal file
334
Telegram/lib_lottie/lottie/lottie_animation.cpp
Normal file
@@ -0,0 +1,334 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "lottie/lottie_animation.h"
|
||||
|
||||
#include "lottie/details/lottie_frame_renderer.h"
|
||||
#include "lottie/details/lottie_frame_provider_direct.h"
|
||||
#include "lottie/lottie_player.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "base/algorithm.h"
|
||||
#include "base/assertion.h"
|
||||
#include "base/variant.h"
|
||||
|
||||
#ifdef LOTTIE_USE_CACHE
|
||||
#include "lottie/details/lottie_frame_provider_cached.h"
|
||||
#include "lottie/details/lottie_frame_provider_cached_multi.h"
|
||||
#endif // LOTTIE_USE_CACHE
|
||||
|
||||
#include <QFile>
|
||||
#include <rlottie.h>
|
||||
#include <crl/crl_async.h>
|
||||
#include <crl/crl_on_main.h>
|
||||
|
||||
namespace Lottie {
|
||||
namespace {
|
||||
|
||||
const auto kIdealSize = QSize(512, 512);
|
||||
|
||||
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 provider = std::make_shared<FrameProviderDirect>(quality);
|
||||
if (!provider->load(content, replacements)) {
|
||||
return Error::ParseFailed;
|
||||
}
|
||||
return CheckSharedState(std::make_unique<SharedState>(
|
||||
std::move(provider),
|
||||
request.empty() ? FrameRequest{ kIdealSize } : request));
|
||||
}
|
||||
|
||||
#ifdef LOTTIE_USE_CACHE
|
||||
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 provider = std::make_shared<FrameProviderCached>(
|
||||
content,
|
||||
std::move(put),
|
||||
cached,
|
||||
request,
|
||||
quality,
|
||||
replacements);
|
||||
return provider->valid()
|
||||
? CheckSharedState(std::make_unique<SharedState>(
|
||||
std::move(provider),
|
||||
request.empty() ? FrameRequest{ kIdealSize } : request))
|
||||
: Error::ParseFailed;
|
||||
}
|
||||
|
||||
details::InitData Init(
|
||||
const QByteArray &content,
|
||||
FnMut<void(int, QByteArray &&cached)> put,
|
||||
std::vector<QByteArray> caches,
|
||||
const FrameRequest &request,
|
||||
Quality quality,
|
||||
const ColorReplacements *replacements) {
|
||||
Expects(!request.empty());
|
||||
|
||||
if (const auto error = ContentError(content)) {
|
||||
return *error;
|
||||
}
|
||||
auto provider = std::make_shared<FrameProviderCachedMulti>(
|
||||
content,
|
||||
std::move(put),
|
||||
std::move(caches),
|
||||
request,
|
||||
quality,
|
||||
replacements);
|
||||
return provider->valid()
|
||||
? CheckSharedState(std::make_unique<SharedState>(
|
||||
std::move(provider),
|
||||
request.empty() ? FrameRequest{ kIdealSize } : request))
|
||||
: Error::ParseFailed;
|
||||
}
|
||||
#endif // LOTTIE_USE_CACHE
|
||||
|
||||
details::InitData Init(
|
||||
std::shared_ptr<FrameProvider> provider,
|
||||
const FrameRequest &request) {
|
||||
Expects(!request.empty());
|
||||
|
||||
return provider->valid()
|
||||
? CheckSharedState(std::make_unique<SharedState>(
|
||||
std::move(provider),
|
||||
request.empty() ? FrameRequest{ kIdealSize } : request))
|
||||
: Error::ParseFailed;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::shared_ptr<FrameRenderer> MakeFrameRenderer() {
|
||||
return FrameRenderer::CreateIndependent();
|
||||
}
|
||||
|
||||
QImage ReadThumbnail(const QByteArray &content) {
|
||||
return v::match(Init(content, FrameRequest(), Quality::High, nullptr), [](
|
||||
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) {
|
||||
if (quality == Quality::Synchronous) {
|
||||
initDone(Init(content, request, quality, replacements));
|
||||
} else {
|
||||
const auto weak = base::make_weak(this);
|
||||
crl::async([=] {
|
||||
auto result = Init(content, request, quality, replacements);
|
||||
crl::on_main(weak, [=, data = std::move(result)]() mutable {
|
||||
initDone(std::move(data));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Animation::Animation(
|
||||
not_null<Player*> 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)
|
||||
#ifdef LOTTIE_USE_CACHE
|
||||
: _player(player) {
|
||||
const auto weak = base::make_weak(this);
|
||||
get([=, put = std::move(put)](QByteArray &&cached) mutable {
|
||||
crl::async([=, put = std::move(put)]() mutable {
|
||||
auto result = Init(
|
||||
content,
|
||||
std::move(put),
|
||||
cached,
|
||||
request,
|
||||
quality,
|
||||
replacements);
|
||||
crl::on_main(weak, [=, data = std::move(result)]() mutable {
|
||||
initDone(std::move(data));
|
||||
});
|
||||
});
|
||||
});
|
||||
#else // LOTTIE_USE_CACHE
|
||||
: Animation(player, content, request, quality, replacements) {
|
||||
#endif // LOTTIE_USE_CACHE
|
||||
}
|
||||
|
||||
Animation::Animation(
|
||||
not_null<Player*> player,
|
||||
int keysCount,
|
||||
FnMut<void(int, FnMut<void(QByteArray &&)>)> get,
|
||||
FnMut<void(int, QByteArray &&cached)> put,
|
||||
const QByteArray &content,
|
||||
const FrameRequest &request,
|
||||
Quality quality,
|
||||
const ColorReplacements *replacements)
|
||||
#ifdef LOTTIE_USE_CACHE
|
||||
: _player(player) {
|
||||
const auto weak = base::make_weak(this);
|
||||
struct State {
|
||||
std::atomic<int> left = 0;
|
||||
std::vector<QByteArray> caches;
|
||||
FnMut<void(int, QByteArray &&cached)> put;
|
||||
};
|
||||
const auto state = std::make_shared<State>();
|
||||
state->left = keysCount;
|
||||
state->put = std::move(put);
|
||||
state->caches.resize(keysCount);
|
||||
for (auto i = 0; i != keysCount; ++i) {
|
||||
get(i, [=](QByteArray &&cached) {
|
||||
state->caches[i] = std::move(cached);
|
||||
if (--state->left) {
|
||||
return;
|
||||
}
|
||||
crl::async([=] {
|
||||
auto result = Init(
|
||||
content,
|
||||
std::move(state->put),
|
||||
std::move(state->caches),
|
||||
request,
|
||||
quality,
|
||||
replacements);
|
||||
crl::on_main(weak, [=, data = std::move(result)]() mutable {
|
||||
initDone(std::move(data));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
#else // LOTTIE_USE_CACHE
|
||||
: Animation(player, content, request, quality, replacements) {
|
||||
#endif // LOTTIE_USE_CACHE
|
||||
}
|
||||
|
||||
Animation::Animation(
|
||||
not_null<Player*> player,
|
||||
std::shared_ptr<FrameProvider> provider,
|
||||
const FrameRequest &request)
|
||||
: _player(player) {
|
||||
const auto weak = base::make_weak(this);
|
||||
crl::async([=, provider = std::move(provider)]() mutable {
|
||||
auto result = Init(std::move(provider), request);
|
||||
crl::on_main(weak, [=, data = std::move(result)]() mutable {
|
||||
initDone(std::move(data));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bool Animation::ready() const {
|
||||
return (_state != nullptr);
|
||||
}
|
||||
|
||||
void Animation::initDone(details::InitData &&data) {
|
||||
v::match(data, [&](std::unique_ptr<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()
|
||||
};
|
||||
}
|
||||
|
||||
int Animation::frameIndex() const {
|
||||
Expects(_state != nullptr);
|
||||
|
||||
const auto frame = _state->frameForPaint();
|
||||
return frame->index % _state->framesCount();
|
||||
}
|
||||
|
||||
int Animation::framesCount() const {
|
||||
Expects(_state != nullptr);
|
||||
|
||||
return _state->framesCount();
|
||||
}
|
||||
|
||||
Information Animation::information() const {
|
||||
Expects(_state != nullptr);
|
||||
|
||||
return _state->information();
|
||||
}
|
||||
|
||||
std::optional<Error> ContentError(const QByteArray &content) {
|
||||
if (content.size() > kMaxFileSize) {
|
||||
return Error::ParseFailed;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace Lottie
|
||||
94
Telegram/lib_lottie/lottie/lottie_animation.h
Normal file
94
Telegram/lib_lottie/lottie/lottie_animation.h
Normal file
@@ -0,0 +1,94 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "lottie/lottie_common.h"
|
||||
#include "base/weak_ptr.h"
|
||||
|
||||
#include <QtGui/QImage>
|
||||
#include <variant>
|
||||
|
||||
class QString;
|
||||
class QByteArray;
|
||||
|
||||
namespace rlottie {
|
||||
class Animation;
|
||||
} // namespace rlottie
|
||||
|
||||
namespace Lottie {
|
||||
|
||||
class Player;
|
||||
class SharedState;
|
||||
class FrameRenderer;
|
||||
class FrameProvider;
|
||||
|
||||
std::shared_ptr<FrameRenderer> MakeFrameRenderer();
|
||||
|
||||
QImage ReadThumbnail(const QByteArray &content);
|
||||
|
||||
namespace details {
|
||||
|
||||
using InitData = std::variant<std::unique_ptr<SharedState>, Error>;
|
||||
|
||||
} // 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);
|
||||
Animation( // Multi-cache version.
|
||||
not_null<Player*> player,
|
||||
int keysCount,
|
||||
FnMut<void(int, FnMut<void(QByteArray &&)>)> get,
|
||||
FnMut<void(int, QByteArray &&)> put, // Unknown thread.
|
||||
const QByteArray &content,
|
||||
const FrameRequest &request,
|
||||
Quality quality,
|
||||
const ColorReplacements *replacements = nullptr);
|
||||
Animation( // Thread-safe version.
|
||||
not_null<Player*> player,
|
||||
std::shared_ptr<FrameProvider> provider,
|
||||
const FrameRequest &request);
|
||||
|
||||
[[nodiscard]] bool ready() const;
|
||||
[[nodiscard]] QImage frame() const;
|
||||
[[nodiscard]] QImage frame(const FrameRequest &request) const;
|
||||
[[nodiscard]] FrameInfo frameInfo(const FrameRequest &request) const;
|
||||
[[nodiscard]] int frameIndex() const;
|
||||
[[nodiscard]] int framesCount() const;
|
||||
[[nodiscard]] Information information() const;
|
||||
|
||||
private:
|
||||
void initDone(details::InitData &&data);
|
||||
void parseDone(std::unique_ptr<SharedState> state);
|
||||
void parseFailed(Error error);
|
||||
|
||||
const not_null<Player*> _player;
|
||||
SharedState *_state = nullptr;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] std::optional<Error> ContentError(const QByteArray &content);
|
||||
|
||||
} // namespace Lottie
|
||||
81
Telegram/lib_lottie/lottie/lottie_common.cpp
Normal file
81
Telegram/lib_lottie/lottie/lottie_common.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "lottie/lottie_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,
|
||||
int sizeRounding) const {
|
||||
Expects(!empty());
|
||||
Expects(sizeRounding != 0);
|
||||
|
||||
const auto result = original.scaled(box, Qt::KeepAspectRatio);
|
||||
const auto skipw = result.width() % sizeRounding;
|
||||
const auto skiph = result.height() % sizeRounding;
|
||||
return QSize(
|
||||
std::max(result.width() - skipw, sizeRounding),
|
||||
std::max(result.height() - skiph, sizeRounding));
|
||||
}
|
||||
|
||||
QByteArray ReadContent(const QByteArray &data, const QString &filepath) {
|
||||
return data.isEmpty() ? ReadFile(filepath) : base::duplicate(data);
|
||||
}
|
||||
|
||||
std::string ReadUtf8(const QByteArray &data) {
|
||||
//00 00 FE FF UTF-32BE
|
||||
//FF FE 00 00 UTF-32LE
|
||||
//FE FF UTF-16BE
|
||||
//FF FE UTF-16LE
|
||||
//EF BB BF UTF-8
|
||||
if (data.size() < 4) {
|
||||
return data.toStdString();
|
||||
}
|
||||
const auto bom = uint32(uint8(data[0]))
|
||||
| (uint32(uint8(data[1])) << 8)
|
||||
| (uint32(uint8(data[2])) << 16)
|
||||
| (uint32(uint8(data[3])) << 24);
|
||||
const auto skip = ((bom == 0xFFFE0000U) || (bom == 0x0000FEFFU))
|
||||
? 4
|
||||
: (((bom & 0xFFFFU) == 0xFFFEU) || ((bom & 0xFFFFU) == 0xFEFFU))
|
||||
? 2
|
||||
: ((bom & 0xFFFFFFU) == 0xBFBBEFU)
|
||||
? 3
|
||||
: 0;
|
||||
const auto bytes = data.data() + skip;
|
||||
const auto length = data.size() - skip;
|
||||
// Old RapidJSON didn't convert encoding, just skipped BOM.
|
||||
// We emulate old behavior here, so don't convert as well.
|
||||
return std::string(bytes, length);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
} // namespace Lottie
|
||||
95
Telegram/lib_lottie/lottie/lottie_common.h
Normal file
95
Telegram/lib_lottie/lottie/lottie_common.h
Normal file
@@ -0,0 +1,95 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "base/basic_types.h"
|
||||
|
||||
#include <QSize>
|
||||
#include <QColor>
|
||||
#include <QImage>
|
||||
#include <crl/crl_time.h>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
namespace Lottie {
|
||||
|
||||
inline constexpr auto kTimeUnknown = std::numeric_limits<crl::time>::min();
|
||||
inline constexpr auto kMaxFileSize = 2 * 1024 * 1024;
|
||||
|
||||
constexpr auto kImageFormat = QImage::Format_ARGB32_Premultiplied;
|
||||
|
||||
class Animation;
|
||||
|
||||
struct Information {
|
||||
QSize size;
|
||||
int frameRate = 0;
|
||||
int framesCount = 0;
|
||||
};
|
||||
|
||||
enum class Error {
|
||||
ParseFailed,
|
||||
NotSupported,
|
||||
};
|
||||
|
||||
struct FrameRequest {
|
||||
QSize box;
|
||||
QColor colored = QColor(0, 0, 0, 0);
|
||||
bool mirrorHorizontal = false;
|
||||
|
||||
[[nodiscard]] bool empty() const {
|
||||
return box.isEmpty();
|
||||
}
|
||||
[[nodiscard]] QSize size(
|
||||
const QSize &original,
|
||||
int sizeRounding) const;
|
||||
|
||||
[[nodiscard]] bool operator==(const FrameRequest &other) const {
|
||||
return (box == other.box)
|
||||
&& (colored == other.colored)
|
||||
&& (mirrorHorizontal == other.mirrorHorizontal);
|
||||
}
|
||||
[[nodiscard]] bool operator!=(const FrameRequest &other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
enum class Quality : char {
|
||||
Default,
|
||||
High,
|
||||
Synchronous
|
||||
};
|
||||
|
||||
enum class SkinModifier {
|
||||
None,
|
||||
Color1,
|
||||
Color2,
|
||||
Color3,
|
||||
Color4,
|
||||
Color5,
|
||||
};
|
||||
|
||||
struct ColorReplacements {
|
||||
std::vector<std::pair<std::uint32_t, std::uint32_t>> replacements;
|
||||
SkinModifier modifier = SkinModifier::None;
|
||||
uint8 tag = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] QByteArray ReadContent(
|
||||
const QByteArray &data,
|
||||
const QString &filepath);
|
||||
[[nodiscard]] std::string ReadUtf8(const QByteArray &data);
|
||||
[[nodiscard]] bool GoodStorageForFrame(const QImage &storage, QSize size);
|
||||
[[nodiscard]] QImage CreateFrameStorage(QSize size);
|
||||
|
||||
enum class FrameRenderResult {
|
||||
Ok,
|
||||
NotReady,
|
||||
BadCacheSize,
|
||||
Failed,
|
||||
};
|
||||
|
||||
} // namespace Lottie
|
||||
99
Telegram/lib_lottie/lottie/lottie_frame_generator.cpp
Normal file
99
Telegram/lib_lottie/lottie/lottie_frame_generator.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "lottie/lottie_frame_generator.h"
|
||||
|
||||
#include "lottie/lottie_common.h"
|
||||
#include "lottie/lottie_wrap.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
|
||||
#include <rlottie.h>
|
||||
|
||||
namespace Lottie {
|
||||
|
||||
FrameGenerator::FrameGenerator(const QByteArray &bytes)
|
||||
: _rlottie(
|
||||
LoadAnimationFromData(
|
||||
ReadUtf8(Images::UnpackGzip(bytes)),
|
||||
std::string(),
|
||||
std::string(),
|
||||
false)) {
|
||||
if (_rlottie) {
|
||||
const auto rate = _rlottie->frameRate();
|
||||
_multiplier = (rate == 60) ? 2 : 1;
|
||||
auto width = size_t();
|
||||
auto height = size_t();
|
||||
_rlottie->size(width, height);
|
||||
_size = QSize(width, height);
|
||||
_framesCount = (_rlottie->totalFrame() + _multiplier - 1)
|
||||
/ _multiplier;
|
||||
_frameDuration = (rate > 0) ? (1000 * _multiplier / rate) : 0;
|
||||
}
|
||||
if (!_framesCount || !_frameDuration || _size.isEmpty()) {
|
||||
_rlottie = nullptr;
|
||||
_framesCount = _frameDuration = 0;
|
||||
_size = QSize();
|
||||
}
|
||||
}
|
||||
|
||||
FrameGenerator::~FrameGenerator() = default;
|
||||
|
||||
int FrameGenerator::count() {
|
||||
return _framesCount;
|
||||
}
|
||||
|
||||
double FrameGenerator::rate() {
|
||||
return _rlottie ? (_rlottie->frameRate() / _multiplier) : 0.;
|
||||
}
|
||||
|
||||
FrameGenerator::Frame FrameGenerator::renderNext(
|
||||
QImage storage,
|
||||
QSize size,
|
||||
Qt::AspectRatioMode mode) {
|
||||
if (!_framesCount || _frameIndex == _framesCount) {
|
||||
return {};
|
||||
}
|
||||
++_frameIndex;
|
||||
return renderCurrent(std::move(storage), size, mode);
|
||||
}
|
||||
|
||||
FrameGenerator::Frame FrameGenerator::renderCurrent(
|
||||
QImage storage,
|
||||
QSize size,
|
||||
Qt::AspectRatioMode mode) {
|
||||
Expects(_frameIndex > 0);
|
||||
|
||||
const auto index = _frameIndex - 1;
|
||||
if (storage.format() != kImageFormat
|
||||
|| storage.size() != size) {
|
||||
storage = CreateFrameStorage(size);
|
||||
}
|
||||
storage.fill(Qt::transparent);
|
||||
const auto scaled = _size.scaled(size, mode);
|
||||
const auto render = QSize(
|
||||
std::max(scaled.width(), size.width()),
|
||||
std::max(scaled.height(), size.height()));
|
||||
const auto xskip = (size.width() - render.width()) / 2;
|
||||
const auto yskip = (size.height() - render.height());
|
||||
const auto skip = (yskip * storage.bytesPerLine() / 4) + xskip;
|
||||
auto surface = rlottie::Surface(
|
||||
reinterpret_cast<uint32_t*>(storage.bits()) + skip,
|
||||
render.width(),
|
||||
render.height(),
|
||||
storage.bytesPerLine());
|
||||
_rlottie->renderSync(index * _multiplier, std::move(surface));
|
||||
return {
|
||||
.duration = _frameDuration,
|
||||
.image = std::move(storage),
|
||||
.last = (_frameIndex == _framesCount),
|
||||
};
|
||||
}
|
||||
|
||||
void FrameGenerator::jumpToStart() {
|
||||
_frameIndex = 0;
|
||||
}
|
||||
|
||||
} // namespace Lottie
|
||||
47
Telegram/lib_lottie/lottie/lottie_frame_generator.h
Normal file
47
Telegram/lib_lottie/lottie/lottie_frame_generator.h
Normal file
@@ -0,0 +1,47 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "ui/effects/frame_generator.h"
|
||||
|
||||
#include <QtGui/QImage>
|
||||
#include <memory>
|
||||
|
||||
namespace rlottie {
|
||||
class Animation;
|
||||
} // namespace rlottie
|
||||
|
||||
namespace Lottie {
|
||||
|
||||
class FrameGenerator final : public Ui::FrameGenerator {
|
||||
public:
|
||||
explicit FrameGenerator(const QByteArray &bytes);
|
||||
~FrameGenerator();
|
||||
|
||||
int count() override;
|
||||
double rate() override;
|
||||
Frame renderNext(
|
||||
QImage storage,
|
||||
QSize size,
|
||||
Qt::AspectRatioMode mode = Qt::IgnoreAspectRatio) override;
|
||||
Frame renderCurrent(
|
||||
QImage storage,
|
||||
QSize size,
|
||||
Qt::AspectRatioMode mode = Qt::IgnoreAspectRatio) override;
|
||||
void jumpToStart() override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<rlottie::Animation> _rlottie;
|
||||
QSize _size;
|
||||
int _multiplier = 1;
|
||||
int _frameDuration = 0;
|
||||
int _framesCount = 0;
|
||||
int _frameIndex = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Lottie
|
||||
633
Telegram/lib_lottie/lottie/lottie_icon.cpp
Normal file
633
Telegram/lib_lottie/lottie/lottie_icon.cpp
Normal file
@@ -0,0 +1,633 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "lottie/lottie_icon.h"
|
||||
|
||||
#include "lottie/lottie_common.h"
|
||||
#include "lottie/lottie_wrap.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "ui/text/text_custom_emoji.h"
|
||||
#include "ui/style/style_core.h"
|
||||
|
||||
#include <QtGui/QPainter>
|
||||
#include <crl/crl_async.h>
|
||||
#include <crl/crl_semaphore.h>
|
||||
#include <crl/crl_on_main.h>
|
||||
#include <rlottie.h>
|
||||
|
||||
namespace Lottie {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] std::unique_ptr<rlottie::Animation> CreateFromContent(
|
||||
const QByteArray &content,
|
||||
QColor replacement) {
|
||||
auto string = ReadUtf8(Images::UnpackGzip(content));
|
||||
auto list = std::vector<std::pair<std::uint32_t, std::uint32_t>>();
|
||||
if (replacement != Qt::white) {
|
||||
const auto value = (uint32_t(replacement.red()) << 16)
|
||||
| (uint32_t(replacement.green() << 8))
|
||||
| (uint32_t(replacement.blue()));
|
||||
list.push_back({ 0xFFFFFFU, value });
|
||||
}
|
||||
auto result = LoadAnimationFromData(
|
||||
std::move(string),
|
||||
std::string(),
|
||||
std::string(),
|
||||
false,
|
||||
std::move(list));
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] QColor RealRenderedColor(QColor color) {
|
||||
#ifndef LOTTIE_DISABLE_RECOLORING
|
||||
return QColor(color.red(), color.green(), color.blue(), 255);
|
||||
#else
|
||||
return Qt::white;
|
||||
#endif
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray ReadIconContent(
|
||||
const QString &name,
|
||||
const QByteArray &json,
|
||||
const QString &path) {
|
||||
return !json.isEmpty()
|
||||
? json
|
||||
: !path.isEmpty()
|
||||
? ReadContent(json, path)
|
||||
: Images::UnpackGzip(
|
||||
ReadContent({}, u":/animations/"_q + name + u".tgs"_q));
|
||||
}
|
||||
|
||||
class LocalLottieCustomEmoji final
|
||||
: public Ui::Text::CustomEmoji
|
||||
, public base::has_weak_ptr {
|
||||
public:
|
||||
LocalLottieCustomEmoji(
|
||||
Lottie::IconDescriptor &&descriptor,
|
||||
Fn<void()> repaint);
|
||||
~LocalLottieCustomEmoji() override = default;
|
||||
|
||||
int width() override;
|
||||
QString entityData() override;
|
||||
void paint(QPainter &p, const Context &context) override;
|
||||
void unload() override;
|
||||
bool ready() override;
|
||||
bool readyInDefaultState() override;
|
||||
|
||||
private:
|
||||
void startAnimation();
|
||||
void handleAnimationFrame();
|
||||
|
||||
int _width = 0;
|
||||
const QString _entityData;
|
||||
std::unique_ptr<Lottie::Icon> _icon;
|
||||
Fn<void()> _repaint;
|
||||
bool _looped = true;
|
||||
};
|
||||
|
||||
LocalLottieCustomEmoji::LocalLottieCustomEmoji(
|
||||
Lottie::IconDescriptor &&descriptor,
|
||||
Fn<void()> repaint)
|
||||
: _width(descriptor.sizeOverride.width())
|
||||
, _entityData(!descriptor.name.isEmpty()
|
||||
? descriptor.name
|
||||
: descriptor.path.isEmpty()
|
||||
? descriptor.path
|
||||
: u"lottie_custom_emoji"_q)
|
||||
, _icon(Lottie::MakeIcon(std::move(descriptor)))
|
||||
, _repaint(std::move(repaint)) {
|
||||
if (!_width && _icon && _icon->valid()) {
|
||||
_width = _icon->width();
|
||||
startAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
int LocalLottieCustomEmoji::width() {
|
||||
return _width;
|
||||
}
|
||||
|
||||
QString LocalLottieCustomEmoji::entityData() {
|
||||
return _entityData;
|
||||
}
|
||||
|
||||
void LocalLottieCustomEmoji::paint(QPainter &p, const Context &context) {
|
||||
if (!_icon || !_icon->valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto color = context.textColor;
|
||||
const auto position = context.position;
|
||||
const auto paused = context.paused
|
||||
|| context.internal.forceFirstFrame
|
||||
|| context.internal.overrideFirstWithLastFrame;
|
||||
|
||||
if (paused) {
|
||||
const auto frame = context.internal.forceLastFrame
|
||||
? _icon->framesCount() - 1
|
||||
: 0;
|
||||
_icon->jumpTo(frame, _repaint);
|
||||
} else if (!_icon->animating()) {
|
||||
startAnimation();
|
||||
}
|
||||
|
||||
_icon->paint(p, position.x(), position.y(), color);
|
||||
}
|
||||
|
||||
void LocalLottieCustomEmoji::unload() {
|
||||
if (_icon) {
|
||||
_icon->jumpTo(0, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool LocalLottieCustomEmoji::ready() {
|
||||
return _icon && _icon->valid();
|
||||
}
|
||||
|
||||
bool LocalLottieCustomEmoji::readyInDefaultState() {
|
||||
return _icon && _icon->valid() && _icon->frameIndex() == 0;
|
||||
}
|
||||
|
||||
void LocalLottieCustomEmoji::startAnimation() {
|
||||
if (!_icon || !_icon->valid() || _icon->framesCount() <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
_icon->animate(
|
||||
[weak = base::make_weak(this)] {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->handleAnimationFrame();
|
||||
}
|
||||
},
|
||||
0,
|
||||
_icon->framesCount() - 1);
|
||||
}
|
||||
|
||||
void LocalLottieCustomEmoji::handleAnimationFrame() {
|
||||
if (_repaint && _looped && _icon->frameIndex() > 0) {
|
||||
_repaint();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct Icon::Frame {
|
||||
int index = 0;
|
||||
QImage resizedImage;
|
||||
QImage renderedImage;
|
||||
QImage colorizedImage;
|
||||
QColor renderedColor;
|
||||
QColor colorizedColor;
|
||||
};
|
||||
|
||||
class Icon::Inner final : public std::enable_shared_from_this<Inner> {
|
||||
public:
|
||||
Inner(int frameIndex, base::weak_ptr<Icon> weak, bool limitFps);
|
||||
|
||||
void prepareFromAsync(
|
||||
const QString &name,
|
||||
const QString &path,
|
||||
const QByteArray &json,
|
||||
QSize sizeOverride,
|
||||
QColor color);
|
||||
void waitTillPrepared() const;
|
||||
|
||||
[[nodiscard]] bool valid() const;
|
||||
[[nodiscard]] QSize size() const;
|
||||
[[nodiscard]] int framesCount() const;
|
||||
[[nodiscard]] Frame &frame();
|
||||
[[nodiscard]] const Frame &frame() const;
|
||||
|
||||
[[nodiscard]] crl::time animationDuration(
|
||||
int frameFrom,
|
||||
int frameTo) const;
|
||||
void moveToFrame(int frame, QColor color, QSize updatedDesiredSize);
|
||||
|
||||
private:
|
||||
enum class PreloadState {
|
||||
None,
|
||||
Preloading,
|
||||
Ready,
|
||||
};
|
||||
|
||||
// Called from crl::async.
|
||||
void renderPreloadFrame(const QColor &color);
|
||||
|
||||
const bool _limitFps = false;
|
||||
std::unique_ptr<rlottie::Animation> _rlottie;
|
||||
Frame _current;
|
||||
QSize _desiredSize;
|
||||
std::atomic<PreloadState> _preloadState = PreloadState::None;
|
||||
|
||||
Frame _preloaded; // Changed on main or async depending on _preloadState.
|
||||
QSize _preloadImageSize;
|
||||
|
||||
base::weak_ptr<Icon> _weak;
|
||||
int _framesCount = 0;
|
||||
int _frameMultiplier = 1;
|
||||
mutable crl::semaphore _semaphore;
|
||||
mutable bool _ready = false;
|
||||
|
||||
};
|
||||
|
||||
Icon::Inner::Inner(int frameIndex, base::weak_ptr<Icon> weak, bool limitFps)
|
||||
: _limitFps(limitFps)
|
||||
, _current { .index = frameIndex }
|
||||
, _weak(weak) {
|
||||
}
|
||||
|
||||
void Icon::Inner::prepareFromAsync(
|
||||
const QString &name,
|
||||
const QString &path,
|
||||
const QByteArray &json,
|
||||
QSize sizeOverride,
|
||||
QColor color) {
|
||||
const auto guard = gsl::finally([&] { _semaphore.release(); });
|
||||
if (!_weak) {
|
||||
return;
|
||||
}
|
||||
auto rlottie = CreateFromContent(
|
||||
ReadIconContent(name, json, path),
|
||||
color);
|
||||
if (!rlottie || !_weak) {
|
||||
return;
|
||||
}
|
||||
auto width = size_t();
|
||||
auto height = size_t();
|
||||
rlottie->size(width, height);
|
||||
if (_limitFps && rlottie->frameRate() == 60) {
|
||||
_frameMultiplier = 2;
|
||||
}
|
||||
_framesCount = (rlottie->totalFrame() + _frameMultiplier - 1)
|
||||
/ _frameMultiplier;
|
||||
if (!_framesCount || !width || !height) {
|
||||
return;
|
||||
}
|
||||
_rlottie = std::move(rlottie);
|
||||
while (_current.index < 0) {
|
||||
_current.index += _framesCount;
|
||||
}
|
||||
const auto size = sizeOverride.isEmpty()
|
||||
? style::ConvertScale(QSize{ int(width), int(height) })
|
||||
: sizeOverride;
|
||||
auto image = CreateFrameStorage(size * style::DevicePixelRatio());
|
||||
image.fill(Qt::transparent);
|
||||
auto surface = rlottie::Surface(
|
||||
reinterpret_cast<uint32_t*>(image.bits()),
|
||||
image.width(),
|
||||
image.height(),
|
||||
image.bytesPerLine());
|
||||
_rlottie->renderSync(
|
||||
_current.index * _frameMultiplier,
|
||||
std::move(surface));
|
||||
_current.renderedColor = RealRenderedColor(color);
|
||||
_current.renderedImage = std::move(image);
|
||||
_current.colorizedColor = QColor(); // Mark colorizedImage as invalid.
|
||||
_desiredSize = size;
|
||||
}
|
||||
|
||||
void Icon::Inner::waitTillPrepared() const {
|
||||
if (!_ready) {
|
||||
_semaphore.acquire();
|
||||
_ready = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool Icon::Inner::valid() const {
|
||||
waitTillPrepared();
|
||||
return (_rlottie != nullptr);
|
||||
}
|
||||
|
||||
QSize Icon::Inner::size() const {
|
||||
waitTillPrepared();
|
||||
return _desiredSize;
|
||||
}
|
||||
|
||||
int Icon::Inner::framesCount() const {
|
||||
waitTillPrepared();
|
||||
return _framesCount;
|
||||
}
|
||||
|
||||
Icon::Frame &Icon::Inner::frame() {
|
||||
waitTillPrepared();
|
||||
return _current;
|
||||
}
|
||||
|
||||
const Icon::Frame &Icon::Inner::frame() const {
|
||||
waitTillPrepared();
|
||||
return _current;
|
||||
}
|
||||
|
||||
crl::time Icon::Inner::animationDuration(int frameFrom, int frameTo) const {
|
||||
waitTillPrepared();
|
||||
const auto rate = _rlottie
|
||||
? (_rlottie->frameRate() / _frameMultiplier)
|
||||
: 0.;
|
||||
const auto frames = std::abs(frameTo - frameFrom);
|
||||
return (rate >= 1.)
|
||||
? crl::time(base::SafeRound(frames / rate * 1000.))
|
||||
: 0;
|
||||
}
|
||||
|
||||
void Icon::Inner::moveToFrame(
|
||||
int frame,
|
||||
QColor color,
|
||||
QSize updatedDesiredSize) {
|
||||
waitTillPrepared();
|
||||
if (frame < 0) {
|
||||
frame += _framesCount;
|
||||
}
|
||||
const auto state = _preloadState.load();
|
||||
const auto shown = _current.index;
|
||||
if (!updatedDesiredSize.isEmpty()) {
|
||||
_desiredSize = updatedDesiredSize;
|
||||
}
|
||||
const auto desiredImageSize = _desiredSize * style::DevicePixelRatio();
|
||||
if (!_rlottie
|
||||
|| state == PreloadState::Preloading
|
||||
|| (shown == frame
|
||||
&& (_current.renderedImage.size() == desiredImageSize))) {
|
||||
return;
|
||||
} else if (state == PreloadState::Ready) {
|
||||
if (_preloaded.index == frame
|
||||
&& (shown != frame
|
||||
|| _preloaded.renderedImage.size() == desiredImageSize)) {
|
||||
std::swap(_current, _preloaded);
|
||||
if (_current.renderedImage.size() == desiredImageSize) {
|
||||
return;
|
||||
}
|
||||
} else if ((shown < _preloaded.index && _preloaded.index < frame)
|
||||
|| (shown > _preloaded.index && _preloaded.index > frame)) {
|
||||
std::swap(_current, _preloaded);
|
||||
}
|
||||
}
|
||||
_preloadImageSize = desiredImageSize;
|
||||
_preloaded.index = frame;
|
||||
_preloadState = PreloadState::Preloading;
|
||||
crl::async([
|
||||
guard = shared_from_this(),
|
||||
color = RealRenderedColor(color)
|
||||
] {
|
||||
guard->renderPreloadFrame(color);
|
||||
});
|
||||
}
|
||||
|
||||
void Icon::Inner::renderPreloadFrame(const QColor &color) {
|
||||
if (!_weak) {
|
||||
return;
|
||||
}
|
||||
auto &image = _preloaded.renderedImage;
|
||||
const auto &size = _preloadImageSize;
|
||||
if (!GoodStorageForFrame(image, size)) {
|
||||
image = GoodStorageForFrame(_preloaded.resizedImage, size)
|
||||
? base::take(_preloaded.resizedImage)
|
||||
: CreateFrameStorage(size);
|
||||
}
|
||||
image.fill(Qt::black);
|
||||
auto surface = rlottie::Surface(
|
||||
reinterpret_cast<uint32_t*>(image.bits()),
|
||||
image.width(),
|
||||
image.height(),
|
||||
image.bytesPerLine());
|
||||
_rlottie->renderSync(
|
||||
_preloaded.index * _frameMultiplier,
|
||||
std::move(surface));
|
||||
_preloaded.renderedColor = color;
|
||||
_preloaded.resizedImage = QImage();
|
||||
_preloaded.colorizedColor = QColor(); // Mark colorizedImage as invalid.
|
||||
_preloadState = PreloadState::Ready;
|
||||
crl::on_main(_weak, [=] {
|
||||
_weak->frameJumpFinished();
|
||||
});
|
||||
}
|
||||
|
||||
Icon::Icon(IconDescriptor &&descriptor)
|
||||
: _inner(std::make_shared<Inner>(
|
||||
descriptor.frame,
|
||||
base::make_weak(this),
|
||||
descriptor.limitFps))
|
||||
, _color(descriptor.color)
|
||||
, _animationFrameTo(descriptor.frame)
|
||||
, _colorizeUsingAlpha(descriptor.colorizeUsingAlpha) {
|
||||
crl::async([
|
||||
inner = _inner,
|
||||
name = descriptor.name,
|
||||
path = descriptor.path,
|
||||
bytes = descriptor.json,
|
||||
sizeOverride = descriptor.sizeOverride,
|
||||
color = (_color ? (*_color)->c : Qt::white)
|
||||
] {
|
||||
inner->prepareFromAsync(name, path, bytes, sizeOverride, color);
|
||||
});
|
||||
}
|
||||
|
||||
void Icon::wait() const {
|
||||
_inner->waitTillPrepared();
|
||||
}
|
||||
|
||||
bool Icon::valid() const {
|
||||
return _inner->valid();
|
||||
}
|
||||
|
||||
int Icon::frameIndex() const {
|
||||
preloadNextFrame();
|
||||
return _inner->frame().index;
|
||||
}
|
||||
|
||||
int Icon::framesCount() const {
|
||||
return _inner->framesCount();
|
||||
}
|
||||
|
||||
QImage Icon::frame() const {
|
||||
return frame(QSize(), nullptr).image;
|
||||
}
|
||||
|
||||
Icon::ResizedFrame Icon::frame(
|
||||
QSize desiredSize,
|
||||
Fn<void()> updateWithPerfect) const {
|
||||
preloadNextFrame(desiredSize);
|
||||
|
||||
const auto desired = size() * style::DevicePixelRatio();
|
||||
auto &frame = _inner->frame();
|
||||
if (frame.renderedImage.isNull()) {
|
||||
return { frame.renderedImage };
|
||||
} else if (!_color) {
|
||||
if (frame.renderedImage.size() == desired) {
|
||||
return { frame.renderedImage };
|
||||
} else if (frame.resizedImage.size() != desired) {
|
||||
frame.resizedImage = frame.renderedImage.scaled(
|
||||
desired,
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
}
|
||||
if (updateWithPerfect) {
|
||||
_repaint = std::move(updateWithPerfect);
|
||||
}
|
||||
return { frame.resizedImage, true };
|
||||
}
|
||||
Assert(frame.renderedImage.size() == desired);
|
||||
const auto color = (*_color)->c;
|
||||
if (color == frame.renderedColor) {
|
||||
return { frame.renderedImage };
|
||||
} else if (!frame.colorizedImage.isNull()
|
||||
&& color == frame.colorizedColor) {
|
||||
return { frame.colorizedImage };
|
||||
}
|
||||
if (frame.colorizedImage.isNull()) {
|
||||
frame.colorizedImage = CreateFrameStorage(desired);
|
||||
}
|
||||
frame.colorizedColor = color;
|
||||
style::colorizeImage(
|
||||
frame.renderedImage,
|
||||
color,
|
||||
&frame.colorizedImage,
|
||||
QRect(),
|
||||
QPoint(),
|
||||
_colorizeUsingAlpha);
|
||||
return { frame.colorizedImage };
|
||||
}
|
||||
|
||||
int Icon::width() const {
|
||||
return size().width();
|
||||
}
|
||||
|
||||
int Icon::height() const {
|
||||
return size().height();
|
||||
}
|
||||
|
||||
QSize Icon::size() const {
|
||||
return _inner->size();
|
||||
}
|
||||
|
||||
void Icon::paint(
|
||||
QPainter &p,
|
||||
int x,
|
||||
int y,
|
||||
std::optional<QColor> colorOverride) {
|
||||
preloadNextFrame();
|
||||
auto &frame = _inner->frame();
|
||||
const auto color = colorOverride.value_or(
|
||||
_color ? (*_color)->c : Qt::white);
|
||||
if (frame.renderedImage.isNull() || color.alpha() == 0) {
|
||||
return;
|
||||
}
|
||||
const auto rect = QRect{ QPoint(x, y), size() };
|
||||
if (color == frame.renderedColor || !_color) {
|
||||
p.drawImage(rect, frame.renderedImage);
|
||||
} else if (color.alphaF() < 1.
|
||||
&& (QColor(color.red(), color.green(), color.blue())
|
||||
== frame.renderedColor)) {
|
||||
const auto o = p.opacity();
|
||||
p.setOpacity(o * color.alphaF());
|
||||
p.drawImage(rect, frame.renderedImage);
|
||||
p.setOpacity(o);
|
||||
} else if (!frame.colorizedImage.isNull()
|
||||
&& color == frame.colorizedColor) {
|
||||
p.drawImage(rect, frame.colorizedImage);
|
||||
} else if (!frame.colorizedImage.isNull()
|
||||
&& color.alphaF() < 1.
|
||||
&& (QColor(color.red(), color.green(), color.blue())
|
||||
== frame.colorizedColor)) {
|
||||
const auto o = p.opacity();
|
||||
p.setOpacity(o * color.alphaF());
|
||||
p.drawImage(rect, frame.colorizedImage);
|
||||
p.setOpacity(o);
|
||||
} else {
|
||||
if (frame.colorizedImage.isNull()) {
|
||||
frame.colorizedImage = CreateFrameStorage(
|
||||
frame.renderedImage.size());
|
||||
}
|
||||
frame.colorizedColor = color;
|
||||
style::colorizeImage(
|
||||
frame.renderedImage,
|
||||
color,
|
||||
&frame.colorizedImage,
|
||||
QRect(),
|
||||
QPoint(),
|
||||
_colorizeUsingAlpha);
|
||||
p.drawImage(rect, frame.colorizedImage);
|
||||
}
|
||||
}
|
||||
|
||||
void Icon::paintInCenter(
|
||||
QPainter &p,
|
||||
QRect rect,
|
||||
std::optional<QColor> colorOverride) {
|
||||
const auto my = size();
|
||||
paint(
|
||||
p,
|
||||
rect.x() + (rect.width() - my.width()) / 2,
|
||||
rect.y() + (rect.height() - my.height()) / 2,
|
||||
colorOverride);
|
||||
}
|
||||
|
||||
void Icon::animate(
|
||||
Fn<void()> update,
|
||||
int frameFrom,
|
||||
int frameTo,
|
||||
std::optional<crl::time> duration) {
|
||||
jumpTo(frameFrom, std::move(update));
|
||||
if (frameFrom != frameTo) {
|
||||
_animationFrameTo = frameTo;
|
||||
_animation.start(
|
||||
[=] {
|
||||
preloadNextFrame();
|
||||
if (_repaint) {
|
||||
_repaint();
|
||||
}
|
||||
},
|
||||
frameFrom,
|
||||
frameTo,
|
||||
(duration
|
||||
? *duration
|
||||
: _inner->animationDuration(frameFrom, frameTo)));
|
||||
}
|
||||
}
|
||||
|
||||
void Icon::jumpTo(int frame, Fn<void()> update) {
|
||||
_animation.stop();
|
||||
_repaint = std::move(update);
|
||||
_animationFrameTo = frame;
|
||||
preloadNextFrame();
|
||||
}
|
||||
|
||||
void Icon::frameJumpFinished() {
|
||||
if (_repaint && !animating()) {
|
||||
_repaint();
|
||||
_repaint = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int Icon::wantedFrameIndex() const {
|
||||
return int(base::SafeRound(_animation.value(_animationFrameTo)));
|
||||
}
|
||||
|
||||
void Icon::preloadNextFrame(QSize updatedDesiredSize) const {
|
||||
_inner->moveToFrame(
|
||||
wantedFrameIndex(),
|
||||
_color ? (*_color)->c : Qt::white,
|
||||
updatedDesiredSize);
|
||||
if (_animationFrameTo < 0) {
|
||||
_animationFrameTo += framesCount();
|
||||
}
|
||||
}
|
||||
|
||||
bool Icon::animating() const {
|
||||
return _animation.animating();
|
||||
}
|
||||
|
||||
std::unique_ptr<Icon> MakeIcon(IconDescriptor &&descriptor) {
|
||||
return std::make_unique<Icon>(std::move(descriptor));
|
||||
}
|
||||
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> MakeEmoji(
|
||||
IconDescriptor &&descriptor,
|
||||
Fn<void()> repaint) {
|
||||
return std::make_unique<LocalLottieCustomEmoji>(
|
||||
std::move(descriptor),
|
||||
repaint);
|
||||
}
|
||||
|
||||
} // namespace Lottie
|
||||
99
Telegram/lib_lottie/lottie/lottie_icon.h
Normal file
99
Telegram/lib_lottie/lottie/lottie_icon.h
Normal file
@@ -0,0 +1,99 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "ui/style/style_core_types.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "base/weak_ptr.h"
|
||||
|
||||
#include <crl/crl_time.h>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <optional>
|
||||
|
||||
namespace Ui::Text {
|
||||
class CustomEmoji;
|
||||
} // namespace Ui::Text
|
||||
|
||||
namespace Lottie {
|
||||
|
||||
struct IconDescriptor {
|
||||
QString name;
|
||||
QString path;
|
||||
QByteArray json;
|
||||
const style::color *color = nullptr;
|
||||
QSize sizeOverride;
|
||||
int frame = 0;
|
||||
bool limitFps = false;
|
||||
bool colorizeUsingAlpha = false;
|
||||
};
|
||||
|
||||
class Icon final : public base::has_weak_ptr {
|
||||
public:
|
||||
explicit Icon(IconDescriptor &&descriptor);
|
||||
Icon(const Icon &other) = delete;
|
||||
Icon &operator=(const Icon &other) = delete;
|
||||
Icon(Icon &&other) = delete; // _animation captures 'this'.
|
||||
Icon &operator=(Icon &&other) = delete;
|
||||
|
||||
[[nodiscard]] bool valid() const;
|
||||
[[nodiscard]] int frameIndex() const;
|
||||
[[nodiscard]] int framesCount() const;
|
||||
[[nodiscard]] QImage frame() const;
|
||||
[[nodiscard]] int width() const;
|
||||
[[nodiscard]] int height() const;
|
||||
[[nodiscard]] QSize size() const;
|
||||
|
||||
struct ResizedFrame {
|
||||
QImage image;
|
||||
bool scaled = false;
|
||||
};
|
||||
[[nodiscard]] ResizedFrame frame(
|
||||
QSize desiredSize,
|
||||
Fn<void()> updateWithPerfect) const;
|
||||
|
||||
void paint(
|
||||
QPainter &p,
|
||||
int x,
|
||||
int y,
|
||||
std::optional<QColor> colorOverride = std::nullopt);
|
||||
void paintInCenter(
|
||||
QPainter &p,
|
||||
QRect rect,
|
||||
std::optional<QColor> colorOverride = std::nullopt);
|
||||
void animate(
|
||||
Fn<void()> update,
|
||||
int frameFrom,
|
||||
int frameTo,
|
||||
std::optional<crl::time> duration = std::nullopt);
|
||||
void jumpTo(int frame, Fn<void()> update);
|
||||
[[nodiscard]] bool animating() const;
|
||||
|
||||
private:
|
||||
struct Frame;
|
||||
class Inner;
|
||||
friend class Inner;
|
||||
|
||||
void wait() const;
|
||||
[[nodiscard]] int wantedFrameIndex() const;
|
||||
void preloadNextFrame(QSize updatedDesiredSize = QSize()) const;
|
||||
void frameJumpFinished();
|
||||
|
||||
std::shared_ptr<Inner> _inner;
|
||||
const style::color *_color = nullptr;
|
||||
Ui::Animations::Simple _animation;
|
||||
mutable int _animationFrameTo = 0;
|
||||
const bool _colorizeUsingAlpha = false;
|
||||
mutable Fn<void()> _repaint;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Icon> MakeIcon(IconDescriptor &&descriptor);
|
||||
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> MakeEmoji(
|
||||
IconDescriptor &&descriptor,
|
||||
Fn<void()> repaint);
|
||||
|
||||
} // namespace Lottie
|
||||
390
Telegram/lib_lottie/lottie/lottie_multi_player.cpp
Normal file
390
Telegram/lib_lottie/lottie/lottie_multi_player.cpp
Normal file
@@ -0,0 +1,390 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "lottie/lottie_multi_player.h"
|
||||
|
||||
#include "lottie/details/lottie_frame_renderer.h"
|
||||
#include "lottie/lottie_animation.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::on_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) {
|
||||
#ifdef LOTTIE_USE_CACHE
|
||||
_animations.push_back(std::make_unique<Animation>(
|
||||
this,
|
||||
std::move(get),
|
||||
std::move(put),
|
||||
content,
|
||||
request,
|
||||
_quality));
|
||||
return _animations.back().get();
|
||||
#else // LOTTIE_USE_CACHE
|
||||
return append(content, request);
|
||||
#endif // LOTTIE_USE_CACHE
|
||||
}
|
||||
|
||||
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* {
|
||||
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
|
||||
113
Telegram/lib_lottie/lottie/lottie_multi_player.h
Normal file
113
Telegram/lib_lottie/lottie/lottie_multi_player.h
Normal file
@@ -0,0 +1,113 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#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 {
|
||||
//std::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(
|
||||
const QByteArray &content,
|
||||
const FrameRequest &request);
|
||||
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);
|
||||
|
||||
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
|
||||
34
Telegram/lib_lottie/lottie/lottie_player.h
Normal file
34
Telegram/lib_lottie/lottie/lottie_player.h
Normal file
@@ -0,0 +1,34 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#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
|
||||
273
Telegram/lib_lottie/lottie/lottie_single_player.cpp
Normal file
273
Telegram/lib_lottie/lottie/lottie_single_player.cpp
Normal file
@@ -0,0 +1,273 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "lottie/lottie_single_player.h"
|
||||
|
||||
#include "lottie/details/lottie_frame_renderer.h"
|
||||
#include "lottie/details/lottie_frame_provider_shared.h"
|
||||
#include "lottie/details/lottie_frame_provider_direct.h"
|
||||
|
||||
#ifdef LOTTIE_USE_CACHE
|
||||
#include "lottie/details/lottie_frame_provider_cached_multi.h"
|
||||
#endif // LOTTIE_USE_CACHE
|
||||
|
||||
#include <crl/crl_async.h>
|
||||
|
||||
namespace Lottie {
|
||||
|
||||
SinglePlayer::SinglePlayer(
|
||||
const QByteArray &content,
|
||||
const FrameRequest &request,
|
||||
Quality quality,
|
||||
const ColorReplacements *replacements,
|
||||
std::shared_ptr<FrameRenderer> renderer)
|
||||
: _timer([=] { checkNextFrameRender(); })
|
||||
, _renderer(renderer ? renderer : FrameRenderer::Instance())
|
||||
, _animation(this, content, request, quality, replacements) {
|
||||
}
|
||||
|
||||
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)
|
||||
: _timer([=] { checkNextFrameRender(); })
|
||||
, _renderer(renderer ? renderer : FrameRenderer::Instance())
|
||||
, _animation(
|
||||
this,
|
||||
std::move(get),
|
||||
std::move(put),
|
||||
content,
|
||||
request,
|
||||
quality,
|
||||
replacements) {
|
||||
}
|
||||
|
||||
SinglePlayer::SinglePlayer(
|
||||
int keysCount,
|
||||
FnMut<void(int, FnMut<void(QByteArray &&)>)> get,
|
||||
FnMut<void(int, QByteArray &&)> put,
|
||||
const QByteArray &content,
|
||||
const FrameRequest &request,
|
||||
Quality quality,
|
||||
const ColorReplacements *replacements,
|
||||
std::shared_ptr<FrameRenderer> renderer)
|
||||
: _timer([=] { checkNextFrameRender(); })
|
||||
, _renderer(renderer ? renderer : FrameRenderer::Instance())
|
||||
, _animation(
|
||||
this,
|
||||
keysCount,
|
||||
std::move(get),
|
||||
std::move(put),
|
||||
content,
|
||||
request,
|
||||
quality,
|
||||
replacements) {
|
||||
}
|
||||
|
||||
SinglePlayer::~SinglePlayer() {
|
||||
if (_state) {
|
||||
_renderer->remove(_state);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<FrameProvider> SinglePlayer::SharedProvider(
|
||||
int keysCount,
|
||||
FnMut<void(int, FnMut<void(QByteArray &&)>)> get,
|
||||
FnMut<void(int, QByteArray &&)> put,
|
||||
const QByteArray &content,
|
||||
const FrameRequest &request,
|
||||
Quality quality,
|
||||
const ColorReplacements *replacements) {
|
||||
auto factory = [=, get = std::move(get), put = std::move(put)](
|
||||
FnMut<void(std::unique_ptr<FrameProvider>)> done) mutable {
|
||||
#ifdef LOTTIE_USE_CACHE
|
||||
struct State {
|
||||
std::atomic<int> left = 0;
|
||||
std::vector<QByteArray> caches;
|
||||
FnMut<void(int, QByteArray &&cached)> put;
|
||||
FnMut<void(std::unique_ptr<FrameProvider>)> done;
|
||||
};
|
||||
const auto state = std::make_shared<State>();
|
||||
state->left = keysCount;
|
||||
state->put = std::move(put);
|
||||
state->done = std::move(done);
|
||||
state->caches.resize(keysCount);
|
||||
for (auto i = 0; i != keysCount; ++i) {
|
||||
get(i, [=](QByteArray &&cached) {
|
||||
state->caches[i] = std::move(cached);
|
||||
if (--state->left) {
|
||||
return;
|
||||
}
|
||||
crl::async([=, done = std::move(state->done)]() mutable {
|
||||
if (const auto error = ContentError(content)) {
|
||||
done(nullptr);
|
||||
return;
|
||||
}
|
||||
auto provider = std::make_unique<FrameProviderCachedMulti>(
|
||||
content,
|
||||
std::move(state->put),
|
||||
std::move(state->caches),
|
||||
request,
|
||||
quality,
|
||||
replacements);
|
||||
done(provider->valid() ? std::move(provider) : nullptr);
|
||||
});
|
||||
});
|
||||
}
|
||||
#else // LOTTIE_USE_CACHE
|
||||
crl::async([=, done = std::move(done)]() mutable {
|
||||
if (const auto error = ContentError(content)) {
|
||||
done(nullptr);
|
||||
return;
|
||||
}
|
||||
auto provider = std::make_unique<FrameProviderDirect>(quality);
|
||||
done(provider->load(content, replacements)
|
||||
? std::move(provider)
|
||||
: nullptr);
|
||||
});
|
||||
#endif // LOTTIE_USE_CACHE
|
||||
};
|
||||
return std::make_shared<FrameProviderShared>(std::move(factory));
|
||||
}
|
||||
|
||||
SinglePlayer::SinglePlayer(
|
||||
std::shared_ptr<FrameProvider> provider,
|
||||
const FrameRequest &request,
|
||||
std::shared_ptr<FrameRenderer> renderer)
|
||||
: _timer([=] { checkNextFrameRender(); })
|
||||
, _renderer(renderer ? renderer : FrameRenderer::Instance())
|
||||
, _animation(this, std::move(provider), request) {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
crl::on_main_update_requests(
|
||||
) | rpl::on_next([=] {
|
||||
checkStep();
|
||||
}, _lifetime);
|
||||
|
||||
// This may destroy the player.
|
||||
_updates.fire({ std::move(information) });
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
int SinglePlayer::frameIndex() const {
|
||||
return _animation.frameIndex();
|
||||
}
|
||||
|
||||
int SinglePlayer::framesCount() const {
|
||||
return _animation.framesCount();
|
||||
}
|
||||
|
||||
Information SinglePlayer::information() const {
|
||||
return _animation.information();
|
||||
}
|
||||
|
||||
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();
|
||||
renderFrame(now);
|
||||
}
|
||||
}
|
||||
|
||||
void SinglePlayer::renderFrame(crl::time now) {
|
||||
_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
|
||||
111
Telegram/lib_lottie/lottie/lottie_single_player.h
Normal file
111
Telegram/lib_lottie/lottie/lottie_single_player.h
Normal file
@@ -0,0 +1,111 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#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;
|
||||
class FrameProvider;
|
||||
|
||||
struct DisplayFrameRequest {
|
||||
};
|
||||
|
||||
struct Update {
|
||||
std::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( // Multi-cache version.
|
||||
int keysCount,
|
||||
FnMut<void(int, FnMut<void(QByteArray &&)>)> get,
|
||||
FnMut<void(int, QByteArray &&)> put,
|
||||
const QByteArray &content,
|
||||
const FrameRequest &request,
|
||||
Quality quality = Quality::Default,
|
||||
const ColorReplacements *replacements = nullptr,
|
||||
std::shared_ptr<FrameRenderer> renderer = nullptr);
|
||||
~SinglePlayer();
|
||||
|
||||
[[nodiscard]] static std::shared_ptr<FrameProvider> SharedProvider(
|
||||
int keysCount,
|
||||
FnMut<void(int, FnMut<void(QByteArray &&)>)> get,
|
||||
FnMut<void(int, QByteArray &&)> put,
|
||||
const QByteArray &content,
|
||||
const FrameRequest &request,
|
||||
Quality quality = Quality::Default,
|
||||
const ColorReplacements *replacements = nullptr);
|
||||
explicit SinglePlayer(
|
||||
std::shared_ptr<FrameProvider> provider,
|
||||
const FrameRequest &request,
|
||||
std::shared_ptr<FrameRenderer> renderer = nullptr);
|
||||
|
||||
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;
|
||||
|
||||
[[nodiscard]] 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;
|
||||
[[nodiscard]] int frameIndex() const;
|
||||
[[nodiscard]] int framesCount() const;
|
||||
[[nodiscard]] Information information() const;
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
|
||||
private:
|
||||
void checkNextFrameAvailability();
|
||||
void checkNextFrameRender();
|
||||
void renderFrame(crl::time now);
|
||||
|
||||
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;
|
||||
|
||||
Animation _animation;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Lottie
|
||||
48
Telegram/lib_lottie/lottie/lottie_wrap.h
Normal file
48
Telegram/lib_lottie/lottie/lottie_wrap.h
Normal file
@@ -0,0 +1,48 @@
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "base/debug_log.h"
|
||||
|
||||
#include <rlottie.h>
|
||||
|
||||
#if __has_include(<glib.h>)
|
||||
#include <glib.h>
|
||||
#endif
|
||||
|
||||
namespace Lottie {
|
||||
|
||||
inline std::unique_ptr<rlottie::Animation> LoadAnimationFromData(
|
||||
std::string jsonData,
|
||||
const std::string &key,
|
||||
const std::string &resourcePath = "",
|
||||
bool cachePolicy = true,
|
||||
const std::vector<std::pair<std::uint32_t, std::uint32_t>> &colorReplacements = {},
|
||||
rlottie::FitzModifier fitzModifier = rlottie::FitzModifier::None) {
|
||||
#ifdef LOTTIE_DISABLE_RECOLORING
|
||||
[[maybe_unused]] static auto logged = [&] {
|
||||
const auto text = "Lottie recoloring is disabled by the distributor, "
|
||||
"expect animations with color issues.";
|
||||
LOG((text));
|
||||
#if __has_include(<glib.h>)
|
||||
g_warning(text);
|
||||
#endif // __has_include(<glib.h>)
|
||||
return true;
|
||||
}();
|
||||
#endif // LOTTIE_DISABLE_RECOLORING
|
||||
return rlottie::Animation::loadFromData(
|
||||
std::move(jsonData),
|
||||
key,
|
||||
resourcePath,
|
||||
cachePolicy
|
||||
#ifndef LOTTIE_DISABLE_RECOLORING
|
||||
,std::move(colorReplacements),
|
||||
fitzModifier
|
||||
#endif // !LOTTIE_DISABLE_RECOLORING
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace Lottie
|
||||
Reference in New Issue
Block a user