mirror of https://github.com/procxx/kepka.git
Started Lottie::CacheState class.
This commit is contained in:
parent
35bc2cc2a5
commit
10772f4ac5
|
@ -1100,11 +1100,8 @@ std::unique_ptr<Lottie::Animation> LottieFromDocument(
|
||||||
data,
|
data,
|
||||||
filepath,
|
filepath,
|
||||||
box);
|
box);
|
||||||
} else if (!data.isEmpty()) {
|
|
||||||
return Lottie::FromData(data);
|
|
||||||
} else {
|
|
||||||
return Lottie::FromFile(filepath);
|
|
||||||
}
|
}
|
||||||
|
return Lottie::FromContent(data, filepath);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Stickers
|
} // namespace Stickers
|
||||||
|
|
|
@ -1368,9 +1368,9 @@ void StickersListWidget::setupLottie(Set &set, int section, int index) {
|
||||||
auto &sticker = set.stickers[index];
|
auto &sticker = set.stickers[index];
|
||||||
const auto document = sticker.document;
|
const auto document = sticker.document;
|
||||||
|
|
||||||
sticker.animated = document->data().isEmpty()
|
sticker.animated = Lottie::FromContent(
|
||||||
? Lottie::FromFile(document->filepath())
|
document->data(),
|
||||||
: Lottie::FromData(document->data());
|
document->filepath());
|
||||||
const auto animation = sticker.animated.get();
|
const auto animation = sticker.animated.get();
|
||||||
|
|
||||||
animation->updates(
|
animation->updates(
|
||||||
|
|
|
@ -97,9 +97,7 @@ QSize HistorySticker::countCurrentSize(int newWidth) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistorySticker::setupLottie() {
|
void HistorySticker::setupLottie() {
|
||||||
_lottie = _data->data().isEmpty()
|
_lottie = Lottie::FromContent(_data->data(), _data->filepath());
|
||||||
? Lottie::FromFile(_data->filepath())
|
|
||||||
: Lottie::FromData(_data->data());
|
|
||||||
_parent->data()->history()->owner().registerHeavyViewPart(_parent);
|
_parent->data()->history()->owner().registerHeavyViewPart(_parent);
|
||||||
|
|
||||||
_lottie->updates(
|
_lottie->updates(
|
||||||
|
|
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "lottie/lottie_animation.h"
|
#include "lottie/lottie_animation.h"
|
||||||
|
|
||||||
#include "lottie/lottie_frame_renderer.h"
|
#include "lottie/lottie_frame_renderer.h"
|
||||||
|
#include "lottie/lottie_cache.h"
|
||||||
#include "storage/cache/storage_cache_database.h"
|
#include "storage/cache/storage_cache_database.h"
|
||||||
#include "base/algorithm.h"
|
#include "base/algorithm.h"
|
||||||
#include "zlib.h"
|
#include "zlib.h"
|
||||||
|
@ -56,19 +57,85 @@ std::string UnpackGzip(const QByteArray &bytes) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
QByteArray ReadFile(const QString &filepath) {
|
||||||
|
auto f = QFile(filepath);
|
||||||
std::unique_ptr<Animation> FromFile(const QString &path) {
|
return (f.size() <= kMaxFileSize && f.open(QIODevice::ReadOnly))
|
||||||
return FromData([&] {
|
? f.readAll()
|
||||||
auto f = QFile(path);
|
: QByteArray();
|
||||||
return (f.size() <= kMaxFileSize && f.open(QIODevice::ReadOnly))
|
|
||||||
? f.readAll()
|
|
||||||
: QByteArray();
|
|
||||||
}());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Animation> FromData(const QByteArray &data) {
|
QByteArray ReadContent(const QByteArray &data, const QString &filepath) {
|
||||||
return std::make_unique<Animation>(base::duplicate(data));
|
return data.isEmpty() ? ReadFile(filepath) : base::duplicate(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<Error> ContentError(const QByteArray &content) {
|
||||||
|
if (content.size() > kMaxFileSize) {
|
||||||
|
qWarning() << "Lottie Error: Too large file: " << content.size();
|
||||||
|
return Error::ParseFailed;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<rlottie::Animation> CreateImplementation(
|
||||||
|
const QByteArray &content) {
|
||||||
|
const auto string = UnpackGzip(content);
|
||||||
|
Assert(string.size() <= kMaxFileSize);
|
||||||
|
|
||||||
|
auto result = rlottie::Animation::loadFromData(string, std::string());
|
||||||
|
if (!result) {
|
||||||
|
qWarning() << "Lottie Error: Parse failed.";
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if (const auto error = ContentError(content)) {
|
||||||
|
return *error;
|
||||||
|
}
|
||||||
|
auto animation = CreateImplementation(content);
|
||||||
|
return animation
|
||||||
|
? CheckSharedState(std::make_unique<SharedState>(
|
||||||
|
std::move(animation)))
|
||||||
|
: Error::ParseFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
details::InitData Init(
|
||||||
|
const QByteArray &content,
|
||||||
|
not_null<Storage::Cache::Database*> cache,
|
||||||
|
Storage::Cache::Key key,
|
||||||
|
const QByteArray &cached,
|
||||||
|
QSize box) {
|
||||||
|
if (const auto error = ContentError(content)) {
|
||||||
|
return *error;
|
||||||
|
}
|
||||||
|
auto state = CacheState(cached, box);
|
||||||
|
const auto prepare = !state.framesCount()
|
||||||
|
|| (state.framesReady() < state.framesCount());
|
||||||
|
auto animation = prepare ? CreateImplementation(content) : nullptr;
|
||||||
|
return (!prepare || animation)
|
||||||
|
? CheckSharedState(std::make_unique<SharedState>(
|
||||||
|
std::move(animation)))
|
||||||
|
: Error::ParseFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::unique_ptr<Animation> FromContent(
|
||||||
|
const QByteArray &data,
|
||||||
|
const QString &filepath) {
|
||||||
|
return std::make_unique<Animation>(ReadContent(data, filepath));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Animation> FromCached(
|
std::unique_ptr<Animation> FromCached(
|
||||||
|
@ -77,40 +144,14 @@ std::unique_ptr<Animation> FromCached(
|
||||||
const QByteArray &data,
|
const QByteArray &data,
|
||||||
const QString &filepath,
|
const QString &filepath,
|
||||||
QSize box) {
|
QSize box) {
|
||||||
return data.isEmpty()
|
return std::make_unique<Animation>(
|
||||||
? Lottie::FromFile(filepath)
|
cache,
|
||||||
: Lottie::FromData(data);
|
key,
|
||||||
|
ReadContent(data, filepath),
|
||||||
|
box);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Init(QByteArray &&content)
|
QImage ReadThumbnail(const QByteArray &content) {
|
||||||
-> base::variant<std::unique_ptr<SharedState>, Error> {
|
|
||||||
if (content.size() > kMaxFileSize) {
|
|
||||||
qWarning()
|
|
||||||
<< "Lottie Error: Too large file: "
|
|
||||||
<< content.size();
|
|
||||||
return Error::ParseFailed;
|
|
||||||
}
|
|
||||||
const auto string = UnpackGzip(content);
|
|
||||||
Assert(string.size() <= kMaxFileSize);
|
|
||||||
|
|
||||||
auto animation = rlottie::Animation::loadFromData(string, std::string());
|
|
||||||
if (!animation) {
|
|
||||||
qWarning()
|
|
||||||
<< "Lottie Error: Parse failed.";
|
|
||||||
return Error::ParseFailed;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto result = std::make_unique<SharedState>(std::move(animation));
|
|
||||||
auto information = result->information();
|
|
||||||
if (!information.frameRate
|
|
||||||
|| information.framesCount <= 0
|
|
||||||
|| information.size.isEmpty()) {
|
|
||||||
return Error::NotSupported;
|
|
||||||
}
|
|
||||||
return std::move(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage ReadThumbnail(QByteArray &&content) {
|
|
||||||
return Init(std::move(content)).match([](
|
return Init(std::move(content)).match([](
|
||||||
const std::unique_ptr<SharedState> &state) {
|
const std::unique_ptr<SharedState> &state) {
|
||||||
return state->frameForPaint()->original;
|
return state->frameForPaint()->original;
|
||||||
|
@ -119,15 +160,28 @@ QImage ReadThumbnail(QByteArray &&content) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Animation::Animation(QByteArray &&content)
|
Animation::Animation(const QByteArray &content)
|
||||||
: _timer([=] { checkNextFrameRender(); }) {
|
: _timer([=] { checkNextFrameRender(); }) {
|
||||||
const auto weak = base::make_weak(this);
|
const auto weak = base::make_weak(this);
|
||||||
crl::async([=, content = base::take(content)]() mutable {
|
crl::async([=] {
|
||||||
crl::on_main(weak, [this, result = Init(std::move(content))]() mutable {
|
crl::on_main(weak, [=, data = Init(content)]() mutable {
|
||||||
result.match([&](std::unique_ptr<SharedState> &state) {
|
initDone(std::move(data));
|
||||||
parseDone(std::move(state));
|
});
|
||||||
}, [&](Error error) {
|
});
|
||||||
parseFailed(error);
|
}
|
||||||
|
|
||||||
|
Animation::Animation(
|
||||||
|
not_null<Storage::Cache::Database*> cache,
|
||||||
|
Storage::Cache::Key key,
|
||||||
|
const QByteArray &content,
|
||||||
|
QSize box)
|
||||||
|
: _timer([=] { checkNextFrameRender(); }) {
|
||||||
|
const auto weak = base::make_weak(this);
|
||||||
|
cache->get(key, [=](QByteArray &&cached) mutable {
|
||||||
|
crl::async([=] {
|
||||||
|
auto result = Init(content, cache, key, cached, box);
|
||||||
|
crl::on_main(weak, [=, data = std::move(result)]() mutable {
|
||||||
|
initDone(std::move(data));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -140,6 +194,14 @@ Animation::~Animation() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Animation::initDone(details::InitData &&data) {
|
||||||
|
data.match([&](std::unique_ptr<SharedState> &state) {
|
||||||
|
parseDone(std::move(state));
|
||||||
|
}, [&](Error error) {
|
||||||
|
parseFailed(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void Animation::parseDone(std::unique_ptr<SharedState> state) {
|
void Animation::parseDone(std::unique_ptr<SharedState> state) {
|
||||||
Expects(state != nullptr);
|
Expects(state != nullptr);
|
||||||
|
|
||||||
|
|
|
@ -32,14 +32,15 @@ struct Key;
|
||||||
|
|
||||||
namespace Lottie {
|
namespace Lottie {
|
||||||
|
|
||||||
constexpr auto kMaxFileSize = 1024 * 1024;
|
inline constexpr auto kMaxFileSize = 1024 * 1024;
|
||||||
|
|
||||||
class Animation;
|
|
||||||
class SharedState;
|
class SharedState;
|
||||||
|
class Animation;
|
||||||
class FrameRenderer;
|
class FrameRenderer;
|
||||||
|
|
||||||
std::unique_ptr<Animation> FromFile(const QString &path);
|
std::unique_ptr<Animation> FromContent(
|
||||||
std::unique_ptr<Animation> FromData(const QByteArray &data);
|
const QByteArray &data,
|
||||||
|
const QString &filepath);
|
||||||
std::unique_ptr<Animation> FromCached(
|
std::unique_ptr<Animation> FromCached(
|
||||||
not_null<Storage::Cache::Database*> cache,
|
not_null<Storage::Cache::Database*> cache,
|
||||||
Storage::Cache::Key key,
|
Storage::Cache::Key key,
|
||||||
|
@ -47,11 +48,22 @@ std::unique_ptr<Animation> FromCached(
|
||||||
const QString &filepath,
|
const QString &filepath,
|
||||||
QSize box);
|
QSize box);
|
||||||
|
|
||||||
QImage ReadThumbnail(QByteArray &&content);
|
QImage ReadThumbnail(const QByteArray &content);
|
||||||
|
|
||||||
|
namespace details {
|
||||||
|
|
||||||
|
using InitData = base::variant<std::unique_ptr<SharedState>, Error>;
|
||||||
|
|
||||||
|
} // namespace details
|
||||||
|
|
||||||
class Animation final : public base::has_weak_ptr {
|
class Animation final : public base::has_weak_ptr {
|
||||||
public:
|
public:
|
||||||
explicit Animation(QByteArray &&content);
|
explicit Animation(const QByteArray &content);
|
||||||
|
Animation(
|
||||||
|
not_null<Storage::Cache::Database*> cache,
|
||||||
|
Storage::Cache::Key key,
|
||||||
|
const QByteArray &content,
|
||||||
|
QSize box);
|
||||||
~Animation();
|
~Animation();
|
||||||
|
|
||||||
//void play(const PlaybackOptions &options);
|
//void play(const PlaybackOptions &options);
|
||||||
|
@ -69,6 +81,7 @@ public:
|
||||||
void checkStep();
|
void checkStep();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void initDone(details::InitData &&data);
|
||||||
void parseDone(std::unique_ptr<SharedState> state);
|
void parseDone(std::unique_ptr<SharedState> state);
|
||||||
void parseFailed(Error error);
|
void parseFailed(Error error);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,225 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "lottie/lottie_cache.h"
|
||||||
|
|
||||||
|
#include "lottie/lottie_frame_renderer.h"
|
||||||
|
#include "base/bytes.h"
|
||||||
|
|
||||||
|
#include <QDataStream>
|
||||||
|
|
||||||
|
namespace Lottie {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kAlignStorage = 16;
|
||||||
|
|
||||||
|
bool UncompressToRaw(AlignedStorage &to, bytes::const_span from) {
|
||||||
|
if (from.empty() || from.size() > to.rawSize()) {
|
||||||
|
return false;
|
||||||
|
} else if (from.size() == to.rawSize()) {
|
||||||
|
memcpy(to.raw(), from.data(), from.size());
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// #TODO stickers
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Decode(QImage &to, const AlignedStorage &from, const QSize &fromSize) {
|
||||||
|
auto fromBytes = static_cast<const char*>(from.aligned());
|
||||||
|
auto toBytes = to.bits();
|
||||||
|
const auto fromPerLine = from.bytesPerLine();
|
||||||
|
const auto toPerLine = to.bytesPerLine();
|
||||||
|
for (auto i = 0; i != to.height(); ++i) {
|
||||||
|
memcpy(toBytes, fromBytes, to.width() * 4);
|
||||||
|
fromBytes += fromPerLine;
|
||||||
|
toBytes += toPerLine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void AlignedStorage::allocate(int packedBytesPerLine, int lines) {
|
||||||
|
Expects(packedBytesPerLine >= 0);
|
||||||
|
Expects(lines >= 0);
|
||||||
|
|
||||||
|
_packedBytesPerLine = packedBytesPerLine;
|
||||||
|
_lines = lines;
|
||||||
|
reallocate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlignedStorage::reallocate() {
|
||||||
|
const auto perLine = bytesPerLine();
|
||||||
|
const auto total = perLine * _lines;
|
||||||
|
_buffer = QByteArray(total + kAlignStorage - 1, Qt::Uninitialized);
|
||||||
|
_raw = (perLine != _packedBytesPerLine)
|
||||||
|
? QByteArray(_packedBytesPerLine * _lines, Qt::Uninitialized)
|
||||||
|
: QByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
int AlignedStorage::lines() const {
|
||||||
|
return _lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
int AlignedStorage::rawSize() const {
|
||||||
|
return _lines * _packedBytesPerLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *AlignedStorage::raw() {
|
||||||
|
return (bytesPerLine() == _packedBytesPerLine) ? aligned() : _raw.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
const void *AlignedStorage::raw() const {
|
||||||
|
return (bytesPerLine() == _packedBytesPerLine) ? aligned() : _raw.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
int AlignedStorage::bytesPerLine() const {
|
||||||
|
return kAlignStorage
|
||||||
|
* ((_packedBytesPerLine + kAlignStorage - 1) / kAlignStorage);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *AlignedStorage::aligned() {
|
||||||
|
const auto result = reinterpret_cast<quintptr>(_buffer.data());
|
||||||
|
return reinterpret_cast<void*>(kAlignStorage
|
||||||
|
* ((result + kAlignStorage - 1) / kAlignStorage));
|
||||||
|
}
|
||||||
|
|
||||||
|
const void *AlignedStorage::aligned() const {
|
||||||
|
const auto result = reinterpret_cast<quintptr>(_buffer.data());
|
||||||
|
return reinterpret_cast<void*>(kAlignStorage
|
||||||
|
* ((result + kAlignStorage - 1) / kAlignStorage));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlignedStorage::copyRawToAligned() {
|
||||||
|
const auto fromPerLine = _packedBytesPerLine;
|
||||||
|
const auto toPerLine = bytesPerLine();
|
||||||
|
if (fromPerLine == toPerLine) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto from = static_cast<char*>(raw());
|
||||||
|
auto to = static_cast<char*>(aligned());
|
||||||
|
for (auto i = 0; i != _lines; ++i) {
|
||||||
|
memcpy(from, to, fromPerLine);
|
||||||
|
from += fromPerLine;
|
||||||
|
to += toPerLine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlignedStorage::copyAlignedToRaw() {
|
||||||
|
const auto fromPerLine = bytesPerLine();
|
||||||
|
const auto toPerLine = _packedBytesPerLine;
|
||||||
|
if (fromPerLine == toPerLine) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto from = static_cast<char*>(aligned());
|
||||||
|
auto to = static_cast<char*>(raw());
|
||||||
|
for (auto i = 0; i != _lines; ++i) {
|
||||||
|
memcpy(from, to, toPerLine);
|
||||||
|
from += fromPerLine;
|
||||||
|
to += toPerLine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CacheState::CacheState(const QByteArray &data, QSize box)
|
||||||
|
: _data(data) {
|
||||||
|
if (!readHeader(box)) {
|
||||||
|
_framesReady = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int CacheState::frameRate() const {
|
||||||
|
return _frameRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CacheState::framesReady() const {
|
||||||
|
return _framesReady;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CacheState::framesCount() const {
|
||||||
|
return _framesCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CacheState::readHeader(QSize box) {
|
||||||
|
if (_data.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
QDataStream stream(&_data, QIODevice::ReadOnly);
|
||||||
|
|
||||||
|
auto encoder = uchar(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 > kMaxFrameRate)
|
||||||
|
|| (framesCount <= 0)
|
||||||
|
|| (framesCount > kMaxFramesCount)
|
||||||
|
|| (framesReady <= 0)
|
||||||
|
|| (framesReady > framesCount)
|
||||||
|
|| FrameRequest{ box }.size(original) != size) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_size = size;
|
||||||
|
_original = original;
|
||||||
|
_frameRate = frameRate;
|
||||||
|
_framesCount = framesCount;
|
||||||
|
_framesReady = framesReady;
|
||||||
|
prepareBuffers();
|
||||||
|
if (!readCompressedDelta(stream.device()->pos())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_uncompressed.copyRawToAligned();
|
||||||
|
std::swap(_uncompressed, _previous);
|
||||||
|
Decode(_firstFrame, _previous, _size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage CacheState::takeFirstFrame() {
|
||||||
|
return std::move(_firstFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CacheState::prepareBuffers() {
|
||||||
|
_uncompressed.allocate(_size.width() * 4, _size.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
int CacheState::uncompressedDeltaSize() const {
|
||||||
|
return _size.width() * _size.height() * 4; // #TODO stickers
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CacheState::readCompressedDelta(int offset) {
|
||||||
|
auto length = qint32(0);
|
||||||
|
const auto part = bytes::make_span(_data).subspan(offset);
|
||||||
|
if (part.size() < sizeof(length)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bytes::copy(bytes::object_as_span(&length), part);
|
||||||
|
const auto bytes = part.subspan(sizeof(length));
|
||||||
|
const auto uncompressedSize = uncompressedDeltaSize();
|
||||||
|
|
||||||
|
_offset = offset + length;
|
||||||
|
return (length <= bytes.size())
|
||||||
|
? UncompressToRaw(_uncompressed, bytes.subspan(0, length))
|
||||||
|
: false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Lottie
|
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QImage>
|
||||||
|
#include <QSize>
|
||||||
|
#include <QByteArray>
|
||||||
|
|
||||||
|
namespace Lottie {
|
||||||
|
|
||||||
|
class AlignedStorage {
|
||||||
|
public:
|
||||||
|
void allocate(int packedBytesPerLine, int lines);
|
||||||
|
|
||||||
|
int lines() const;
|
||||||
|
int rawSize() const;
|
||||||
|
|
||||||
|
// Gives a pointer to packedBytesPerLine * lines bytes of memory.
|
||||||
|
void *raw();
|
||||||
|
const void *raw() const;
|
||||||
|
|
||||||
|
// Gives a stride value in the aligned storage (% 16 == 0).
|
||||||
|
int bytesPerLine() const;
|
||||||
|
|
||||||
|
// Gives a pointer to the aligned memory (% 16 == 0).
|
||||||
|
void *aligned();
|
||||||
|
const void *aligned() const;
|
||||||
|
|
||||||
|
void copyRawToAligned();
|
||||||
|
void copyAlignedToRaw();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void reallocate();
|
||||||
|
|
||||||
|
int _packedBytesPerLine = 0;
|
||||||
|
int _lines = 0;
|
||||||
|
QByteArray _raw;
|
||||||
|
QByteArray _buffer;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class CacheState {
|
||||||
|
public:
|
||||||
|
enum class Encoder : uchar {
|
||||||
|
YUV420A4_LZ4,
|
||||||
|
};
|
||||||
|
|
||||||
|
CacheState(const QByteArray &data, QSize box);
|
||||||
|
|
||||||
|
[[nodiscard]] int frameRate() const;
|
||||||
|
[[nodiscard]] int framesReady() const;
|
||||||
|
[[nodiscard]] int framesCount() const;
|
||||||
|
[[nodiscard]] QImage takeFirstFrame();
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[nodiscard]] bool readHeader(QSize box);
|
||||||
|
void prepareBuffers();
|
||||||
|
[[nodiscard]] bool readCompressedDelta(int offset);
|
||||||
|
[[nodiscard]] int uncompressedDeltaSize() const;
|
||||||
|
|
||||||
|
QByteArray _data;
|
||||||
|
QSize _size;
|
||||||
|
QSize _original;
|
||||||
|
AlignedStorage _uncompressed;
|
||||||
|
AlignedStorage _previous;
|
||||||
|
QImage _firstFrame;
|
||||||
|
int _frameRate = 0;
|
||||||
|
int _framesCount = 0;
|
||||||
|
int _framesReady = 0;
|
||||||
|
int _offset = 0;
|
||||||
|
Encoder _encoder = Encoder::YUV420A4_LZ4;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Lottie
|
|
@ -23,8 +23,6 @@ namespace Lottie {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kDisplaySkipped = crl::time(-1);
|
constexpr auto kDisplaySkipped = crl::time(-1);
|
||||||
constexpr auto kMaxFrameRate = 120;
|
|
||||||
constexpr auto kMaxSize = 3096;
|
|
||||||
|
|
||||||
std::weak_ptr<FrameRenderer> GlobalInstance;
|
std::weak_ptr<FrameRenderer> GlobalInstance;
|
||||||
|
|
||||||
|
@ -199,7 +197,7 @@ void SharedState::calculateProperties() {
|
||||||
(width > 0 && width < kMaxSize) ? int(width) : 0,
|
(width > 0 && width < kMaxSize) ? int(width) : 0,
|
||||||
(height > 0 && height < kMaxSize) ? int(height) : 0);
|
(height > 0 && height < kMaxSize) ? int(height) : 0);
|
||||||
_frameRate = (rate >= 1. && rate <= kMaxFrameRate) ? int(rate) : 0;
|
_frameRate = (rate >= 1. && rate <= kMaxFrameRate) ? int(rate) : 0;
|
||||||
_framesCount = (count > 0) ? int(count) : 0;
|
_framesCount = (count > 0 && count <= kMaxFramesCount) ? int(count) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SharedState::isValid() const {
|
bool SharedState::isValid() const {
|
||||||
|
|
|
@ -23,6 +23,10 @@ class Animation;
|
||||||
|
|
||||||
namespace Lottie {
|
namespace Lottie {
|
||||||
|
|
||||||
|
inline constexpr auto kMaxFrameRate = 120;
|
||||||
|
inline constexpr auto kMaxSize = 3096;
|
||||||
|
inline constexpr auto kMaxFramesCount = 600;
|
||||||
|
|
||||||
class Animation;
|
class Animation;
|
||||||
class JsonObject;
|
class JsonObject;
|
||||||
|
|
||||||
|
|
|
@ -1049,9 +1049,7 @@ QSize MediaPreviewWidget::currentDimensions() const {
|
||||||
void MediaPreviewWidget::setupLottie() {
|
void MediaPreviewWidget::setupLottie() {
|
||||||
Expects(_document != nullptr);
|
Expects(_document != nullptr);
|
||||||
|
|
||||||
_lottie = _document->data().isEmpty()
|
_lottie = Lottie::FromContent(_document->data(), _document->filepath());
|
||||||
? Lottie::FromFile(_document->filepath())
|
|
||||||
: Lottie::FromData(_document->data());
|
|
||||||
|
|
||||||
_lottie->updates(
|
_lottie->updates(
|
||||||
) | rpl::start_with_next_error([=](Lottie::Update update) {
|
) | rpl::start_with_next_error([=](Lottie::Update update) {
|
||||||
|
|
|
@ -53,6 +53,8 @@
|
||||||
'sources': [
|
'sources': [
|
||||||
'<(src_loc)/lottie/lottie_animation.cpp',
|
'<(src_loc)/lottie/lottie_animation.cpp',
|
||||||
'<(src_loc)/lottie/lottie_animation.h',
|
'<(src_loc)/lottie/lottie_animation.h',
|
||||||
|
'<(src_loc)/lottie/lottie_cache.cpp',
|
||||||
|
'<(src_loc)/lottie/lottie_cache.h',
|
||||||
'<(src_loc)/lottie/lottie_common.h',
|
'<(src_loc)/lottie/lottie_common.h',
|
||||||
'<(src_loc)/lottie/lottie_frame_renderer.cpp',
|
'<(src_loc)/lottie/lottie_frame_renderer.cpp',
|
||||||
'<(src_loc)/lottie/lottie_frame_renderer.h',
|
'<(src_loc)/lottie/lottie_frame_renderer.h',
|
||||||
|
|
Loading…
Reference in New Issue