From 5628c1eee6125f3015166f29a49d53e541590d7f Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 27 Jun 2019 15:27:01 +0200 Subject: [PATCH] Encode cached frames to YUV420P. --- .../SourceFiles/ffmpeg/ffmpeg_utility.cpp | 41 +- Telegram/SourceFiles/ffmpeg/ffmpeg_utility.h | 13 +- Telegram/SourceFiles/lottie/lottie_cache.cpp | 426 +++++++++++++----- Telegram/SourceFiles/lottie/lottie_cache.h | 60 ++- Telegram/SourceFiles/lottie/lottie_common.h | 8 +- Telegram/SourceFiles/ui/text/text.cpp | 4 +- Telegram/SourceFiles/ui/text/text.h | 4 +- Telegram/SourceFiles/ui/text/text_block.h | 2 +- 8 files changed, 384 insertions(+), 174 deletions(-) diff --git a/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp b/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp index 19642e115b..6aea0c7ab9 100644 --- a/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp +++ b/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp @@ -174,8 +174,10 @@ void FrameDeleter::operator()(AVFrame *value) { } SwscalePointer MakeSwscalePointer( - not_null frame, - QSize resize, + QSize srcSize, + int srcFormat, + QSize dstSize, + int dstFormat, SwscalePointer *existing) { // We have to use custom caching for SwsContext, because // sws_getCachedContext checks passed flags with existing context flags, @@ -184,25 +186,26 @@ SwscalePointer MakeSwscalePointer( // to the resulting context, so the caching doesn't work. if (existing && (*existing) != nullptr) { const auto &deleter = existing->get_deleter(); - if (deleter.resize == resize - && deleter.frameSize == QSize(frame->width, frame->height) - && deleter.frameFormat == frame->format) { + if (deleter.srcSize == srcSize + && deleter.srcFormat == srcFormat + && deleter.dstSize == dstSize + && deleter.dstFormat == dstFormat) { return std::move(*existing); } } - if (frame->format <= AV_PIX_FMT_NONE || frame->format >= AV_PIX_FMT_NB) { + if (srcFormat <= AV_PIX_FMT_NONE || srcFormat >= AV_PIX_FMT_NB) { LogError(qstr("frame->format")); return SwscalePointer(); } const auto result = sws_getCachedContext( existing ? existing->release() : nullptr, - frame->width, - frame->height, - AVPixelFormat(frame->format), - resize.width(), - resize.height(), - AV_PIX_FMT_BGRA, + srcSize.width(), + srcSize.height(), + AVPixelFormat(srcFormat), + dstSize.width(), + dstSize.height(), + AVPixelFormat(dstFormat), 0, nullptr, nullptr, @@ -212,7 +215,19 @@ SwscalePointer MakeSwscalePointer( } return SwscalePointer( result, - { resize, QSize{ frame->width, frame->height }, frame->format }); + { srcSize, srcFormat, dstSize, dstFormat }); +} + +SwscalePointer MakeSwscalePointer( + not_null frame, + QSize resize, + SwscalePointer *existing) { + return MakeSwscalePointer( + QSize(frame->width, frame->height), + frame->format, + resize, + AV_PIX_FMT_BGRA, + existing); } void SwscaleDeleter::operator()(SwsContext *value) { diff --git a/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.h b/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.h index ccc390baa3..6d75dde527 100644 --- a/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.h +++ b/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.h @@ -146,13 +146,20 @@ using FramePointer = std::unique_ptr; void ClearFrameMemory(AVFrame *frame); struct SwscaleDeleter { - QSize resize; - QSize frameSize; - int frameFormat = int(AV_PIX_FMT_NONE); + QSize srcSize; + int srcFormat = int(AV_PIX_FMT_NONE); + QSize dstSize; + int dstFormat = int(AV_PIX_FMT_NONE); void operator()(SwsContext *value); }; using SwscalePointer = std::unique_ptr; +[[nodiscard]] SwscalePointer MakeSwscalePointer( + QSize srcSize, + int srcFormat, + QSize dstSize, + int dstFormat, // This field doesn't take part in caching! + SwscalePointer *existing = nullptr); [[nodiscard]] SwscalePointer MakeSwscalePointer( not_null frame, QSize resize, diff --git a/Telegram/SourceFiles/lottie/lottie_cache.cpp b/Telegram/SourceFiles/lottie/lottie_cache.cpp index 46ccb7f0ca..bb4765522b 100644 --- a/Telegram/SourceFiles/lottie/lottie_cache.cpp +++ b/Telegram/SourceFiles/lottie/lottie_cache.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "logs.h" #include +#include #include #include #include @@ -22,62 +23,119 @@ namespace { constexpr auto kAlignStorage = 16; -void Xor(AlignedStorage &to, const AlignedStorage &from) { - Expects(to.rawSize() == from.rawSize()); +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.rawSize(); - const auto fromBytes = reinterpret_cast(from.raw()); - const auto toBytes = reinterpret_cast(to.raw()); - const auto skip = reinterpret_cast(toBytes) % kBlockSize; - const auto blocks = (amount - skip) / kBlockSize; - for (auto i = 0; i != skip; ++i) { - toBytes[i] ^= fromBytes[i]; - } - const auto fromBlocks = reinterpret_cast(fromBytes + skip); - const auto toBlocks = reinterpret_cast(toBytes + skip); + const auto amount = from.size(); + const auto fromBytes = reinterpret_cast(from.data()); + const auto toBytes = reinterpret_cast(to.data()); + const auto blocks = amount / kBlockSize; + const auto fromBlocks = reinterpret_cast(fromBytes); + const auto toBlocks = reinterpret_cast(toBytes); for (auto i = 0; i != blocks; ++i) { toBlocks[i] ^= fromBlocks[i]; } - const auto left = amount - skip - (blocks * kBlockSize); + const auto left = amount - (blocks * kBlockSize); for (auto i = amount - left; i != amount; ++i) { toBytes[i] ^= fromBytes[i]; } } -bool UncompressToRaw(AlignedStorage &to, bytes::const_span from) { - if (from.empty() || from.size() > to.rawSize()) { +void UnPremultiply(QImage &to, const QImage &from) { + // This creates QImage::Format_ARGB32_Premultiplied, but we use it + // as an image in QImage::Format_ARGB32 format. + if (!FFmpeg::GoodStorageForFrame(to, from.size())) { + to = FFmpeg::CreateFrameStorage(from.size()); + } + + const auto layout = &qPixelLayouts[QImage::Format_ARGB32]; + const auto convert = layout->convertFromARGB32PM; + const auto fromPerLine = from.bytesPerLine(); + const auto toPerLine = to.bytesPerLine(); + const auto width = from.width(); + if (fromPerLine != width * 4 || toPerLine != width * 4) { + auto fromBytes = from.bits(); + auto toBytes = to.bits(); + for (auto i = 0; i != to.height(); ++i) { + convert( + reinterpret_cast(toBytes), + reinterpret_cast(fromBytes), + width, + layout, + nullptr); + fromBytes += fromPerLine; + toBytes += toPerLine; + } + } else { + convert( + reinterpret_cast(to.bits()), + reinterpret_cast(from.bits()), + from.width() * from.height(), + layout, + nullptr); + } +} + +void PremultiplyInplace(QImage &image) { + const auto layout = &qPixelLayouts[QImage::Format_ARGB32]; + const auto convert = layout->convertToARGB32PM; + const auto perLine = image.bytesPerLine(); + const auto width = image.width(); + if (perLine != width * 4) { + auto bytes = image.bits(); + for (auto i = 0; i != image.height(); ++i) { + convert( + reinterpret_cast(bytes), + reinterpret_cast(bytes), + width, + layout, + nullptr); + bytes += perLine; + } + } else { + convert( + reinterpret_cast(image.bits()), + reinterpret_cast(image.bits()), + image.width() * image.height(), + layout, + nullptr); + } +} + +bool UncompressToRaw(EncodedStorage &to, bytes::const_span from) { + if (from.empty() || from.size() > to.size()) { return false; - } else if (from.size() == to.rawSize()) { - memcpy(to.raw(), from.data(), from.size()); + } else if (from.size() == to.size()) { + memcpy(to.data(), from.data(), from.size()); return true; } const auto result = LZ4_decompress_safe( reinterpret_cast(from.data()), - static_cast(to.raw()), + to.data(), from.size(), - to.rawSize()); - return (result == to.rawSize()); + to.size()); + return (result == to.size()); } -void CompressFromRaw(QByteArray &to, const AlignedStorage &from) { - const auto size = from.rawSize(); +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( - static_cast(from.raw()), + 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.raw(), size); + memcpy(to.data() + sizeof(qint32), from.data(), size); } else { to.resize(compressed + sizeof(qint32)); } @@ -90,8 +148,8 @@ void CompressFromRaw(QByteArray &to, const AlignedStorage &from) { void CompressAndSwapFrame( QByteArray &to, QByteArray *additional, - AlignedStorage &frame, - AlignedStorage &previous) { + EncodedStorage &frame, + EncodedStorage &previous) { CompressFromRaw(to, frame); std::swap(frame, previous); if (!additional) { @@ -113,116 +171,232 @@ void CompressAndSwapFrame( bytes::object_as_span(&negativeLength)); } -void Decode(QImage &to, AlignedStorage &from, const QSize &fromSize) { - from.copyRawToAligned(); - if (!FFmpeg::GoodStorageForFrame(to, fromSize)) { - to = FFmpeg::CreateFrameStorage(fromSize); - } - auto fromBytes = static_cast(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; +void DecodeYUV2RGB( + QImage &to, + const EncodedStorage &from, + FFmpeg::SwscalePointer &context) { + context = FFmpeg::MakeSwscalePointer( + to.size(), + AV_PIX_FMT_YUV420P, + to.size(), + AV_PIX_FMT_BGRA, + &context); + Assert(context != nullptr); + + // AV_NUM_DATA_POINTERS defined in AVFrame struct + const uint8_t *src[AV_NUM_DATA_POINTERS] = { + from.yData(), + from.uData(), + from.vData(), + nullptr + }; + int srcLineSize[AV_NUM_DATA_POINTERS] = { + from.yBytesPerLine(), + from.uBytesPerLine(), + from.vBytesPerLine(), + 0 + }; + uint8_t *dst[AV_NUM_DATA_POINTERS] = { to.bits(), nullptr }; + int dstLineSize[AV_NUM_DATA_POINTERS] = { to.bytesPerLine(), 0 }; + + const auto lines = sws_scale( + context.get(), + src, + srcLineSize, + 0, + to.height(), + dst, + dstLineSize); + + Ensures(lines == to.height()); +} + +void DecodeAlpha(QImage &to, const EncodedStorage &from) { + auto bytes = to.bits(); + auto alpha = from.aData(); + const auto perLine = to.bytesPerLine(); + const auto width = to.width(); + const auto height = to.height(); + for (auto i = 0; i != height; ++i) { + auto ints = reinterpret_cast(bytes); + const auto till = ints + width; + while (ints != till) { + const auto value = uint32(*alpha++); + *ints = (*ints & 0x00FFFFFFU) | ((value & 0xF0U) << 24); + ++ints; + *ints = (*ints & 0x00FFFFFFU) | (value << 28); + ++ints; + } + bytes += perLine; } } -void Encode(AlignedStorage &to, const QImage &from, const QSize &toSize) { - auto fromBytes = from.bits(); - auto toBytes = static_cast(to.aligned()); - const auto fromPerLine = from.bytesPerLine(); - const auto toPerLine = to.bytesPerLine(); - for (auto i = 0; i != to.lines(); ++i) { - memcpy(toBytes, fromBytes, from.width() * 4); - fromBytes += fromPerLine; - toBytes += toPerLine; +void Decode( + QImage &to, + const EncodedStorage &from, + const QSize &fromSize, + FFmpeg::SwscalePointer &context) { + if (!FFmpeg::GoodStorageForFrame(to, fromSize)) { + to = FFmpeg::CreateFrameStorage(fromSize); } - to.copyAlignedToRaw(); + DecodeYUV2RGB(to, from, context); + DecodeAlpha(to, from); + PremultiplyInplace(to); +} + +void EncodeRGB2YUV( + EncodedStorage &to, + const QImage &from, + FFmpeg::SwscalePointer &context) { + context = FFmpeg::MakeSwscalePointer( + from.size(), + AV_PIX_FMT_BGRA, + from.size(), + AV_PIX_FMT_YUV420P, + &context); + Assert(context != nullptr); + + // AV_NUM_DATA_POINTERS defined in AVFrame struct + const uint8_t *src[AV_NUM_DATA_POINTERS] = { from.bits(), nullptr }; + int srcLineSize[AV_NUM_DATA_POINTERS] = { from.bytesPerLine(), 0 }; + uint8_t *dst[AV_NUM_DATA_POINTERS] = { + to.yData(), + to.uData(), + to.vData(), + nullptr + }; + int dstLineSize[AV_NUM_DATA_POINTERS] = { + to.yBytesPerLine(), + to.uBytesPerLine(), + to.vBytesPerLine(), + 0 + }; + + const auto lines = sws_scale( + context.get(), + src, + srcLineSize, + 0, + from.height(), + dst, + dstLineSize); + + Ensures(lines == from.height()); +} + +void EncodeAlpha(EncodedStorage &to, const QImage &from) { + auto bytes = from.bits(); + auto alpha = to.aData(); + const auto perLine = from.bytesPerLine(); + const auto width = from.width(); + const auto height = from.height(); + for (auto i = 0; i != height; ++i) { + auto ints = reinterpret_cast(bytes); + const auto till = ints + width; + for (; ints != till; ints += 2) { + *alpha++ = (((*ints) >> 24) & 0xF0U) | ((*(ints + 1)) >> 28); + } + bytes += perLine; + } +} + +void Encode( + EncodedStorage &to, + const QImage &from, + QImage &cache, + FFmpeg::SwscalePointer &context) { + UnPremultiply(cache, from); + EncodeRGB2YUV(to, cache, context); + EncodeAlpha(to, cache); } } // namespace -void AlignedStorage::allocate(int packedBytesPerLine, int lines) { - Expects(packedBytesPerLine >= 0); - Expects(lines >= 0); +void EncodedStorage::allocate(int width, int height) { + Expects((width % 2) == 0 && (height % 2) == 0); - _packedBytesPerLine = packedBytesPerLine; - _lines = lines; - reallocate(); + if (_width != width || _height != height) { + _width = width; + _height = height; + 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(); +void EncodedStorage::reallocate() { + const auto total = _width * _height * 2; + _data = QByteArray(total + kAlignStorage - 1, Qt::Uninitialized); } -int AlignedStorage::lines() const { - return _lines; +int EncodedStorage::width() const { + return _width; } -int AlignedStorage::rawSize() const { - return _lines * _packedBytesPerLine; +int EncodedStorage::height() const { + return _height; } -void *AlignedStorage::raw() { - return (bytesPerLine() == _packedBytesPerLine) ? aligned() : _raw.data(); +int EncodedStorage::size() const { + return _width * _height * 2; } -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(_buffer.data()); - return reinterpret_cast(kAlignStorage +char *EncodedStorage::data() { + const auto result = reinterpret_cast(_data.data()); + return reinterpret_cast(kAlignStorage * ((result + kAlignStorage - 1) / kAlignStorage)); } -const void *AlignedStorage::aligned() const { - const auto result = reinterpret_cast(_buffer.data()); - return reinterpret_cast(kAlignStorage +const char *EncodedStorage::data() const { + const auto result = reinterpret_cast(_data.data()); + return reinterpret_cast(kAlignStorage * ((result + kAlignStorage - 1) / kAlignStorage)); } -void AlignedStorage::copyRawToAligned() { - const auto fromPerLine = _packedBytesPerLine; - const auto toPerLine = bytesPerLine(); - if (fromPerLine == toPerLine) { - return; - } - auto from = static_cast(raw()); - auto to = static_cast(aligned()); - for (auto i = 0; i != _lines; ++i) { - memcpy(to, from, fromPerLine); - from += fromPerLine; - to += toPerLine; - } +uint8_t *EncodedStorage::yData() { + return reinterpret_cast(data()); } -void AlignedStorage::copyAlignedToRaw() { - const auto fromPerLine = bytesPerLine(); - const auto toPerLine = _packedBytesPerLine; - if (fromPerLine == toPerLine) { - return; - } - auto from = static_cast(aligned()); - auto to = static_cast(raw()); - for (auto i = 0; i != _lines; ++i) { - memcpy(to, from, toPerLine); - from += fromPerLine; - to += toPerLine; - } +const uint8_t *EncodedStorage::yData() const { + return reinterpret_cast(data()); +} + +int EncodedStorage::yBytesPerLine() const { + return _width; +} + +uint8_t *EncodedStorage::uData() { + return yData() + (_width * _height); +} + +const uint8_t *EncodedStorage::uData() const { + return yData() + (_width * _height); +} + +int EncodedStorage::uBytesPerLine() const { + return _width / 2; +} + +uint8_t *EncodedStorage::vData() { + return uData() + (_width * _height / 4); +} + +const uint8_t *EncodedStorage::vData() const { + return uData() + (_width * _height / 4); +} + +int EncodedStorage::vBytesPerLine() const { + return _width / 2; +} + +uint8_t *EncodedStorage::aData() { + return uData() + (_width * _height) / 2; +} + +const uint8_t *EncodedStorage::aData() const { + return uData() + (_width * _height) / 2; +} + +int EncodedStorage::aBytesPerLine() const { + return _width / 2; } CacheState::CacheState(const QByteArray &data, const FrameRequest &request) @@ -338,7 +512,7 @@ bool CacheState::renderFrame( } else { std::swap(_uncompressed, _previous); } - Decode(to, _previous, _size); + Decode(to, _previous, _size, _decodeContext); return true; } @@ -355,17 +529,19 @@ void CacheState::appendFrame( } if (index == 0) { _size = request.size(_original); - _compressedFrames.reserve(_framesCount); + _encode = EncodeFields(); + _encode.compressedFrames.reserve(_framesCount); prepareBuffers(); } - Encode(_uncompressed, frame, _size); + Assert(frame.size() == _size); + Encode(_uncompressed, frame, _encode.cache, _encode.context); CompressAndSwapFrame( - _compressBuffer, - (index != 0) ? &_xorCompressBuffer : nullptr, + _encode.compressBuffer, + (index != 0) ? &_encode.xorCompressBuffer : nullptr, _uncompressed, _previous); - _compressedFrames.push_back(_compressBuffer); - _compressedFrames.back().detach(); + _encode.compressedFrames.push_back(_encode.compressBuffer); + _encode.compressedFrames.back().detach(); if (++_framesReady == _framesCount) { finalizeEncoding(); } @@ -374,7 +550,7 @@ void CacheState::appendFrame( void CacheState::finalizeEncoding() { const auto size = (_data.isEmpty() ? headerSize() : _data.size()) + ranges::accumulate( - _compressedFrames, + _encode.compressedFrames, 0, std::plus(), &QByteArray::size); @@ -386,7 +562,7 @@ void CacheState::finalizeEncoding() { const auto offset = _data.size(); _data.resize(size); auto to = _data.data() + offset; - for (const auto &block : _compressedFrames) { + for (const auto &block : _encode.compressedFrames) { const auto amount = qint32(block.size()); memcpy(to, block.data(), amount); if (*reinterpret_cast(block.data()) < 0) { @@ -394,11 +570,8 @@ void CacheState::finalizeEncoding() { } to += amount; } - _compressedFrames.clear(); - _compressedFrames.shrink_to_fit(); - _compressBuffer.clear(); - _compressBuffer.squeeze(); - + _encode = EncodeFields(); + constexpr auto test = sizeof(_encode); LOG(("SIZE: %1 (%2x%3, %4 frames, %5 xored)").arg(_data.size()).arg(_size.width()).arg(_size.height()).arg(_framesCount).arg(xored)); } @@ -421,8 +594,11 @@ void CacheState::writeHeader() { } void CacheState::prepareBuffers() { - _uncompressed.allocate(_size.width() * 4, _size.height()); - _previous.allocate(_size.width() * 4, _size.height()); + // 12 bit per pixel in YUV420P. + const auto bytesPerLine = _size.width(); + + _uncompressed.allocate(bytesPerLine, _size.height()); + _previous.allocate(bytesPerLine, _size.height()); } CacheState::ReadResult CacheState::readCompressedFrame() { diff --git a/Telegram/SourceFiles/lottie/lottie_cache.h b/Telegram/SourceFiles/lottie/lottie_cache.h index 4e7a5b40b1..04863a013e 100644 --- a/Telegram/SourceFiles/lottie/lottie_cache.h +++ b/Telegram/SourceFiles/lottie/lottie_cache.h @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "ffmpeg/ffmpeg_utility.h" + #include #include #include @@ -15,34 +17,36 @@ namespace Lottie { struct FrameRequest; -class AlignedStorage { +class EncodedStorage { public: - void allocate(int packedBytesPerLine, int lines); + void allocate(int width, int height); - int lines() const; - int rawSize() const; + int width() const; + int height() const; - // Gives a pointer to packedBytesPerLine * lines bytes of memory. - void *raw(); - const void *raw() const; + char *data(); + const char *data() const; + int size() 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(); + 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 _packedBytesPerLine = 0; - int _lines = 0; - QByteArray _raw; - QByteArray _buffer; + int _width = 0; + int _height = 0; + QByteArray _data; }; @@ -79,6 +83,13 @@ private: bool ok = false; bool xored = false; }; + struct EncodeFields { + std::vector compressedFrames; + QByteArray compressBuffer; + QByteArray xorCompressBuffer; + QImage cache; + FFmpeg::SwscalePointer context; + }; int headerSize() const; void prepareBuffers(); void finalizeEncoding(); @@ -88,13 +99,12 @@ private: [[nodiscard]] ReadResult readCompressedFrame(); QByteArray _data; - std::vector _compressedFrames; - QByteArray _compressBuffer; - QByteArray _xorCompressBuffer; + EncodeFields _encode; QSize _size; QSize _original; - AlignedStorage _uncompressed; - AlignedStorage _previous; + EncodedStorage _uncompressed; + EncodedStorage _previous; + FFmpeg::SwscalePointer _decodeContext; QImage _firstFrame; int _frameRate = 0; int _framesCount = 0; diff --git a/Telegram/SourceFiles/lottie/lottie_common.h b/Telegram/SourceFiles/lottie/lottie_common.h index ee9d268b71..c832fdde3a 100644 --- a/Telegram/SourceFiles/lottie/lottie_common.h +++ b/Telegram/SourceFiles/lottie/lottie_common.h @@ -54,12 +54,14 @@ struct FrameRequest { return box.isEmpty(); } [[nodiscard]] QSize size(const QSize &original) const { - Expects(!box.isEmpty()); + Expects(!empty()); const auto result = original.scaled(box, Qt::KeepAspectRatio); + const auto skipw = result.width() % 2; + const auto skiph = result.height() % 2; return QSize( - std::max(result.width(), 1), - std::max(result.height(), 1)); + std::max(result.width() - skipw, 2), + std::max(result.height() - skiph, 2)); } [[nodiscard]] bool operator==(const FrameRequest &other) const { diff --git a/Telegram/SourceFiles/ui/text/text.cpp b/Telegram/SourceFiles/ui/text/text.cpp index ae0bea6b53..87bece1c89 100644 --- a/Telegram/SourceFiles/ui/text/text.cpp +++ b/Telegram/SourceFiles/ui/text/text.cpp @@ -7,8 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/text/text.h" -#include - #include "core/click_handler_types.h" #include "core/crash_reports.h" #include "ui/text/text_block.h" @@ -18,6 +16,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/confirm_box.h" #include "mainwindow.h" +#include + namespace Ui { namespace Text { namespace { diff --git a/Telegram/SourceFiles/ui/text/text.h b/Telegram/SourceFiles/ui/text/text.h index 83634bc814..dfff476a93 100644 --- a/Telegram/SourceFiles/ui/text/text.h +++ b/Telegram/SourceFiles/ui/text/text.h @@ -7,12 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "private/qfontengine_p.h" - #include "core/click_handler.h" #include "ui/text/text_entity.h" #include "base/flags.h" +#include + static const QChar TextCommand(0x0010); enum TextCommands { TextCommandBold = 0x01, diff --git a/Telegram/SourceFiles/ui/text/text_block.h b/Telegram/SourceFiles/ui/text/text_block.h index ea0aa10909..38e1e04464 100644 --- a/Telegram/SourceFiles/ui/text/text_block.h +++ b/Telegram/SourceFiles/ui/text/text_block.h @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "private/qfontengine_p.h" +#include namespace Ui { namespace Text {