From ed3f24651032102d70b5ba916edeb6b3f3de1974 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 29 Aug 2022 16:34:21 +0400 Subject: [PATCH] Use FrameGenerator-based AnimatedIcon for reactions. --- Telegram/CMakeLists.txt | 1 - .../boxes/peers/edit_peer_reactions.cpp | 6 +- .../boxes/reactions_settings_box.cpp | 17 +- .../boxes/reactions_settings_box.h | 2 +- .../SourceFiles/data/data_document_media.cpp | 45 + .../SourceFiles/data/data_document_media.h | 11 + .../data/data_message_reactions.cpp | 31 +- .../SourceFiles/data/data_message_reactions.h | 16 +- .../data/stickers/data_custom_emoji.cpp | 8 +- ...g_emoji.cpp => ffmpeg_frame_generator.cpp} | 84 +- ...fmpeg_emoji.h => ffmpeg_frame_generator.h} | 12 +- .../history/view/history_view_bottom_info.h | 7 +- .../history_view_reactions_animation.cpp | 13 +- .../history_view_reactions_animation.h | 10 +- .../reactions/history_view_reactions_button.h | 4 - .../history_view_reactions_strip.cpp | 61 +- .../reactions/history_view_reactions_strip.h | 22 +- .../SourceFiles/settings/settings_chat.cpp | 2 +- .../ui/text/custom_emoji_instance.cpp | 773 ------------------ .../ui/text/custom_emoji_instance.h | 271 ------ Telegram/cmake/lib_ffmpeg.cmake | 4 +- Telegram/cmake/td_ui.cmake | 2 - Telegram/lib_lottie | 2 +- Telegram/lib_ui | 2 +- 24 files changed, 225 insertions(+), 1181 deletions(-) rename Telegram/SourceFiles/ffmpeg/{ffmpeg_emoji.cpp => ffmpeg_frame_generator.cpp} (81%) rename Telegram/SourceFiles/ffmpeg/{ffmpeg_emoji.h => ffmpeg_frame_generator.h} (66%) delete mode 100644 Telegram/SourceFiles/ui/text/custom_emoji_instance.cpp delete mode 100644 Telegram/SourceFiles/ui/text/custom_emoji_instance.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 91ccf5416..e0ef738c3 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -82,7 +82,6 @@ PRIVATE desktop-app::lib_webview desktop-app::lib_ffmpeg desktop-app::lib_stripe - desktop-app::external_lz4 desktop-app::external_rlottie desktop-app::external_zlib desktop-app::external_kcoreaddons diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp index 9a091b4c0..a780acea8 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/peers/edit_peer_reactions.h" -#include "boxes/reactions_settings_box.h" // AddReactionLottieIcon +#include "boxes/reactions_settings_box.h" // AddReactionAnimatedIcon #include "data/data_message_reactions.h" #include "data/data_peer.h" #include "data/data_chat.h" @@ -58,7 +58,7 @@ void EditAllowedReactionsBox( tr::lng_manage_peer_reactions_enable(), st::manageGroupButton.button); if (!list.empty()) { - AddReactionLottieIcon( + AddReactionAnimatedIcon( enabled, enabled->sizeValue( ) | rpl::map([=](const QSize &size) { @@ -101,7 +101,7 @@ void EditAllowedReactionsBox( container, rpl::single(entry.title), st::manageGroupButton.button); - AddReactionLottieIcon( + AddReactionAnimatedIcon( button, button->sizeValue( ) | rpl::map([=](const QSize &size) { diff --git a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp index 5a5afbd87..3dbddc6f6 100644 --- a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp +++ b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp @@ -19,7 +19,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_element.h" #include "history/view/reactions/history_view_reactions_strip.h" #include "lang/lang_keys.h" -#include "lottie/lottie_icon.h" #include "boxes/premium_preview_box.h" #include "main/main_session.h" #include "settings/settings_common.h" @@ -33,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/labels.h" #include "ui/widgets/scroll_area.h" #include "ui/wrap/vertical_layout.h" +#include "ui/animated_icon.h" #include "window/section_widget.h" #include "window/window_session_controller.h" #include "styles/style_boxes.h" @@ -230,7 +230,7 @@ void AddMessage( } const auto index = state->icons.flag ? 1 : 0; state->icons.lifetimes[index] = rpl::lifetime(); - AddReactionLottieIcon( + AddReactionAnimatedIcon( container, widget->geometryValue( ) | rpl::map([=](const QRect &r) { @@ -253,7 +253,7 @@ void AddMessage( } // namespace -void AddReactionLottieIcon( +void AddReactionAnimatedIcon( not_null parent, rpl::producer iconPositionValue, int iconSize, @@ -261,11 +261,10 @@ void AddReactionLottieIcon( rpl::producer<> &&selects, rpl::producer<> &&destroys, not_null stateLifetime) { - struct State { struct Entry { std::shared_ptr media; - std::shared_ptr icon; + std::shared_ptr icon; }; Entry appear; Entry select; @@ -332,7 +331,7 @@ void AddReactionLottieIcon( p.scale(progress, progress); } - const auto paintFrame = [&](not_null animation) { + const auto paintFrame = [&](not_null animation) { const auto frame = animation->frame(); p.drawImage( QRect( @@ -346,7 +345,7 @@ void AddReactionLottieIcon( const auto appear = state->appear.icon.get(); if (appear && !state->appearAnimated) { state->appearAnimated = true; - appear->animate(update, 0, appear->framesCount() - 1); + appear->animate(update); } if (appear && appear->animating()) { paintFrame(appear); @@ -360,7 +359,7 @@ void AddReactionLottieIcon( ) | rpl::start_with_next([=] { const auto select = state->select.icon.get(); if (select && !select->animating()) { - select->animate(update, 0, select->framesCount() - 1); + select->animate(update); } }, widget->lifetime()); @@ -437,7 +436,7 @@ void ReactionsSettingsBox( } const auto iconSize = st::settingsReactionSize; - AddReactionLottieIcon( + AddReactionAnimatedIcon( button, button->sizeValue( ) | rpl::map([=, left = button->st().iconLeft](const QSize &s) { diff --git a/Telegram/SourceFiles/boxes/reactions_settings_box.h b/Telegram/SourceFiles/boxes/reactions_settings_box.h index bc2b2d48d..b6f957aef 100644 --- a/Telegram/SourceFiles/boxes/reactions_settings_box.h +++ b/Telegram/SourceFiles/boxes/reactions_settings_box.h @@ -20,7 +20,7 @@ namespace Data { struct Reaction; } // namespace Data -void AddReactionLottieIcon( +void AddReactionAnimatedIcon( not_null parent, rpl::producer iconPositionValue, int iconSize, diff --git a/Telegram/SourceFiles/data/data_document_media.cpp b/Telegram/SourceFiles/data/data_document_media.cpp index 83bf70d31..2afa04ad2 100644 --- a/Telegram/SourceFiles/data/data_document_media.cpp +++ b/Telegram/SourceFiles/data/data_document_media.cpp @@ -17,6 +17,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "main/main_session_settings.h" #include "lottie/lottie_animation.h" +#include "lottie/lottie_frame_generator.h" +#include "ffmpeg/ffmpeg_frame_generator.h" #include "history/history_item.h" #include "history/history.h" #include "window/themes/window_theme_preview.h" @@ -505,4 +507,47 @@ void DocumentMedia::ReadOrGenerateThumbnail( document->owner().cache().get(document->goodThumbnailCacheKey(), got); } +auto DocumentIconFrameGenerator(not_null media) +-> FnMut()> { + if (!media->loaded()) { + return nullptr; + } + using Type = StickerType; + const auto document = media->owner(); + const auto content = media->bytes(); + const auto fromFile = content.isEmpty(); + const auto type = document->sticker() + ? document->sticker()->type + : (document->isVideoFile() || document->isAnimation()) + ? Type::Webm + : Type::Webp; + const auto &location = media->owner()->location(true); + if (fromFile && !location.accessEnable()) { + return nullptr; + } + return [=]() -> std::unique_ptr { + const auto bytes = Lottie::ReadContent(content, location.name()); + if (fromFile) { + location.accessDisable(); + } + if (bytes.isEmpty()) { + return nullptr; + } + switch (type) { + case Type::Tgs: + return std::make_unique(bytes); + case Type::Webm: + return std::make_unique(bytes); + case Type::Webp: + return std::make_unique(bytes); + } + Unexpected("Document type in DocumentIconFrameGenerator."); + }; +} + +auto DocumentIconFrameGenerator(const std::shared_ptr &media) +-> FnMut()> { + return DocumentIconFrameGenerator(media.get()); +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_document_media.h b/Telegram/SourceFiles/data/data_document_media.h index 799687466..9dfb3588a 100644 --- a/Telegram/SourceFiles/data/data_document_media.h +++ b/Telegram/SourceFiles/data/data_document_media.h @@ -12,6 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class Image; class FileLoader; +namespace Ui { +class FrameGenerator; +} // namespace Ui + namespace Media { namespace Clip { enum class Notification; @@ -112,4 +116,11 @@ private: }; +[[nodiscard]] auto DocumentIconFrameGenerator(not_null media) +-> FnMut()>; + +[[nodiscard]] auto DocumentIconFrameGenerator( + const std::shared_ptr &media) +-> FnMut()>; + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index 9bcf6c151..8045d68b1 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -20,9 +20,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document_media.h" #include "data/data_peer_values.h" #include "data/stickers/data_custom_emoji.h" -#include "lottie/lottie_icon.h" #include "storage/localimageloader.h" #include "ui/image/image_location_factory.h" +#include "ui/animated_icon.h" #include "mtproto/mtproto_config.h" #include "base/timer_rpl.h" #include "base/call_delayed.h" @@ -300,7 +300,7 @@ void Reactions::preloadImageFor(const ReactionId &id) { ? nullptr : i->centerIcon ? i->centerIcon - : i->appearAnimation.get(); + : i->selectAnimation.get(); if (document) { loadImage(set, document, !i->centerIcon); } else if (!_waitingForList) { @@ -337,7 +337,7 @@ QImage Reactions::resolveImageFor( auto &set = (i != end(_images)) ? i->second : _images[emoji]; const auto resolve = [&](QImage &image, int size) { const auto factor = style::DevicePixelRatio(); - const auto frameSize = set.fromAppearAnimation + const auto frameSize = set.fromSelectAnimation ? (size / 2) : size; image = set.icon->frame().scaled( @@ -345,7 +345,7 @@ QImage Reactions::resolveImageFor( frameSize * factor, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - if (set.fromAppearAnimation) { + if (set.fromSelectAnimation) { auto result = QImage( size * factor, size * factor, @@ -385,7 +385,7 @@ void Reactions::resolveImages() { ? nullptr : i->centerIcon ? i->centerIcon - : i->appearAnimation.get(); + : i->selectAnimation.get(); if (document) { loadImage(set, document, !i->centerIcon); } else { @@ -398,16 +398,16 @@ void Reactions::resolveImages() { void Reactions::loadImage( ImageSet &set, not_null document, - bool fromAppearAnimation) { + bool fromSelectAnimation) { if (!set.bottomInfo.isNull() || set.icon) { return; } else if (!set.media) { - set.fromAppearAnimation = fromAppearAnimation; + set.fromSelectAnimation = fromSelectAnimation; set.media = document->createMediaView(); set.media->checkStickerLarge(); } if (set.media->loaded()) { - setLottie(set); + setAnimatedIcon(set); } else if (!_imagesLoadLifetime) { document->session().downloaderTaskFinished( ) | rpl::start_with_next([=] { @@ -416,13 +416,11 @@ void Reactions::loadImage( } } -void Reactions::setLottie(ImageSet &set) { +void Reactions::setAnimatedIcon(ImageSet &set) { const auto size = style::ConvertScale(kSizeForDownscale); - set.icon = Lottie::MakeIcon({ - .path = set.media->owner()->filepath(true), - .json = set.media->bytes(), + set.icon = Ui::MakeAnimatedIcon({ + .generator = DocumentIconFrameGenerator(set.media), .sizeOverride = QSize(size, size), - .frame = -1, }); set.media = nullptr; } @@ -433,7 +431,7 @@ void Reactions::downloadTaskFinished() { if (!set.media) { continue; } else if (set.media->loaded()) { - setLottie(set); + setAnimatedIcon(set); } else { hasOne = true; } @@ -644,8 +642,6 @@ std::optional Reactions::parse(const MTPAvailableReaction &entry) { if (!known) { LOG(("API Error: Unknown emoji in reactions: %1").arg(emoji)); } - const auto selectAnimation = _owner->processDocument( - data.vselect_animation()); return known ? std::make_optional(Reaction{ .id = ReactionId{ emoji }, @@ -653,7 +649,8 @@ std::optional Reactions::parse(const MTPAvailableReaction &entry) { //.staticIcon = _owner->processDocument(data.vstatic_icon()), .appearAnimation = _owner->processDocument( data.vappear_animation()), - .selectAnimation = selectAnimation, + .selectAnimation = _owner->processDocument( + data.vselect_animation()), //.activateAnimation = _owner->processDocument( // data.vactivate_animation()), //.activateEffects = _owner->processDocument( diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h index aa4c43ca6..abe178078 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.h +++ b/Telegram/SourceFiles/data/data_message_reactions.h @@ -11,14 +11,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_message_reaction_id.h" #include "data/stickers/data_custom_emoji.h" +namespace Ui { +class AnimatedIcon; +} // namespace Ui + namespace Ui::Text { class CustomEmoji; } // namespace Ui::Text -namespace Lottie { -class Icon; -} // namespace Lottie - namespace Data { class DocumentMedia; @@ -117,8 +117,8 @@ private: QImage bottomInfo; QImage inlineList; std::shared_ptr media; - std::unique_ptr icon; - bool fromAppearAnimation = false; + std::unique_ptr icon; + bool fromSelectAnimation = false; }; [[nodiscard]] not_null resolveListener(); @@ -148,8 +148,8 @@ private: void loadImage( ImageSet &set, not_null document, - bool fromAppearAnimation); - void setLottie(ImageSet &set); + bool fromSelectAnimation); + void setAnimatedIcon(ImageSet &set); void resolveImages(); void downloadTaskFinished(); diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp index 029978ed7..fd0937b35 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -17,8 +17,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_message_reactions.h" #include "data/stickers/data_stickers.h" #include "lottie/lottie_common.h" -#include "lottie/lottie_emoji.h" -#include "ffmpeg/ffmpeg_emoji.h" +#include "lottie/lottie_frame_generator.h" +#include "ffmpeg/ffmpeg_frame_generator.h" #include "chat_helpers/stickers_lottie.h" #include "ui/widgets/input_fields.h" #include "ui/text/text_custom_emoji.h" @@ -321,9 +321,9 @@ void CustomEmojiLoader::check() { -> std::unique_ptr { switch (type) { case StickerType::Tgs: - return std::make_unique(bytes); + return std::make_unique(bytes); case StickerType::Webm: - return std::make_unique(bytes); + return std::make_unique(bytes); case StickerType::Webp: return std::make_unique(bytes); } diff --git a/Telegram/SourceFiles/ffmpeg/ffmpeg_emoji.cpp b/Telegram/SourceFiles/ffmpeg/ffmpeg_frame_generator.cpp similarity index 81% rename from Telegram/SourceFiles/ffmpeg/ffmpeg_emoji.cpp rename to Telegram/SourceFiles/ffmpeg/ffmpeg_frame_generator.cpp index 1844dea51..c6b2fe100 100644 --- a/Telegram/SourceFiles/ffmpeg/ffmpeg_emoji.cpp +++ b/Telegram/SourceFiles/ffmpeg/ffmpeg_frame_generator.cpp @@ -5,7 +5,7 @@ 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 "ffmpeg/ffmpeg_emoji.h" +#include "ffmpeg/ffmpeg_frame_generator.h" #include "ffmpeg/ffmpeg_utility.h" #include "base/debug_log.h" @@ -17,7 +17,7 @@ constexpr auto kMaxArea = 1920 * 1080 * 4; } // namespace -class EmojiGenerator::Impl final { +class FrameGenerator::Impl final { public: explicit Impl(const QByteArray &bytes); @@ -25,6 +25,11 @@ public: QImage storage, QSize size, Qt::AspectRatioMode mode); + [[nodiscard]] Frame renderCurrent( + QImage storage, + QSize size, + Qt::AspectRatioMode mode); + void jumpToStart(); private: struct ReadFrame { @@ -35,10 +40,6 @@ private: void readNextFrame(); void resolveNextFrameTiming(); - [[nodiscard]] Frame renderCurrent( - QImage storage, - QSize size, - Qt::AspectRatioMode mode); [[nodiscard]] QString wrapError(int result) const; @@ -76,13 +77,13 @@ private: }; -EmojiGenerator::Impl::Impl(const QByteArray &bytes) +FrameGenerator::Impl::Impl(const QByteArray &bytes) : _bytes(bytes) { _format = MakeFormatPointer( static_cast(this), - &EmojiGenerator::Impl::Read, + &FrameGenerator::Impl::Read, nullptr, - &EmojiGenerator::Impl::Seek); + &FrameGenerator::Impl::Seek); auto error = 0; if ((error = avformat_find_stream_info(_format.get(), nullptr))) { @@ -105,11 +106,11 @@ EmojiGenerator::Impl::Impl(const QByteArray &bytes) _codec = MakeCodecPointer({ .stream = info }); } -int EmojiGenerator::Impl::Read(void *opaque, uint8_t *buf, int buf_size) { +int FrameGenerator::Impl::Read(void *opaque, uint8_t *buf, int buf_size) { return static_cast(opaque)->read(buf, buf_size); } -int EmojiGenerator::Impl::read(uint8_t *buf, int buf_size) { +int FrameGenerator::Impl::read(uint8_t *buf, int buf_size) { const auto available = _bytes.size() - _deviceOffset; if (available <= 0) { return -1; @@ -120,14 +121,14 @@ int EmojiGenerator::Impl::read(uint8_t *buf, int buf_size) { return fill; } -int64_t EmojiGenerator::Impl::Seek( +int64_t FrameGenerator::Impl::Seek( void *opaque, int64_t offset, int whence) { return static_cast(opaque)->seek(offset, whence); } -int64_t EmojiGenerator::Impl::seek(int64_t offset, int whence) { +int64_t FrameGenerator::Impl::seek(int64_t offset, int whence) { if (whence == AVSEEK_SIZE) { return _bytes.size(); } @@ -146,7 +147,7 @@ int64_t EmojiGenerator::Impl::seek(int64_t offset, int whence) { return now; } -EmojiGenerator::Frame EmojiGenerator::Impl::renderCurrent( +FrameGenerator::Frame FrameGenerator::Impl::renderCurrent( QImage storage, QSize size, Qt::AspectRatioMode mode) { @@ -238,18 +239,18 @@ EmojiGenerator::Frame EmojiGenerator::Impl::renderCurrent( transform.rotate(_rotation); storage = storage.transformed(transform); } - ClearFrameMemory(_current.frame.get()); const auto duration = _next.frame ? (_next.position - _current.position) : _current.duration; return { - .image = std::move(storage), .duration = duration, + .image = std::move(storage), + .last = !_next.frame, }; } -EmojiGenerator::Frame EmojiGenerator::Impl::renderNext( +FrameGenerator::Frame FrameGenerator::Impl::renderNext( QImage storage, QSize size, Qt::AspectRatioMode mode) { @@ -264,7 +265,27 @@ EmojiGenerator::Frame EmojiGenerator::Impl::renderNext( return renderCurrent(std::move(storage), size, mode); } -void EmojiGenerator::Impl::resolveNextFrameTiming() { +void FrameGenerator::Impl::jumpToStart() { + auto result = 0; + if ((result = avformat_seek_file(_format.get(), _streamId, std::numeric_limits::min(), 0, std::numeric_limits::max(), 0)) < 0) { + if ((result = av_seek_frame(_format.get(), _streamId, 0, AVSEEK_FLAG_BYTE)) < 0) { + if ((result = av_seek_frame(_format.get(), _streamId, 0, AVSEEK_FLAG_FRAME)) < 0) { + if ((result = av_seek_frame(_format.get(), _streamId, 0, 0)) < 0) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Webm Error: Unable to av_seek_frame() to the start, ") + wrapError(result)); + return; + } + } + } + } + avcodec_flush_buffers(_codec.get()); + _current = ReadFrame(); + _next = ReadFrame(); + _currentFrameDelay = _nextFrameDelay = 0; + _framePosition = 0; +} + +void FrameGenerator::Impl::resolveNextFrameTiming() { const auto base = _format->streams[_streamId]->time_base; const auto duration = _next.frame->pkt_duration; const auto framePts = _next.frame->pts; @@ -287,7 +308,7 @@ void EmojiGenerator::Impl::resolveNextFrameTiming() { _next.duration = _nextFrameDelay; } -void EmojiGenerator::Impl::readNextFrame() { +void FrameGenerator::Impl::readNextFrame() { auto frame = _next.frame ? base::take(_next.frame) : MakeFramePointer(); while (true) { auto result = avcodec_receive_frame(_codec.get(), frame.get()); @@ -347,28 +368,43 @@ void EmojiGenerator::Impl::readNextFrame() { } } -QString EmojiGenerator::Impl::wrapError(int result) const { +QString FrameGenerator::Impl::wrapError(int result) const { auto error = std::array{}; return u"error %1, %2"_q .arg(result) .arg(av_make_error_string(error.data(), error.size(), result)); } -EmojiGenerator::EmojiGenerator(const QByteArray &bytes) +FrameGenerator::FrameGenerator(const QByteArray &bytes) : _impl(std::make_unique(bytes)) { } -EmojiGenerator::~EmojiGenerator() = default; +FrameGenerator::~FrameGenerator() = default; -int EmojiGenerator::count() { +int FrameGenerator::count() { return 0; } -EmojiGenerator::Frame EmojiGenerator::renderNext( +double FrameGenerator::rate() { + return 0.; +} + +FrameGenerator::Frame FrameGenerator::renderNext( QImage storage, QSize size, Qt::AspectRatioMode mode) { return _impl->renderNext(std::move(storage), size, mode); } +FrameGenerator::Frame FrameGenerator::renderCurrent( + QImage storage, + QSize size, + Qt::AspectRatioMode mode) { + return _impl->renderCurrent(std::move(storage), size, mode); +} + +void FrameGenerator::jumpToStart() { + _impl->jumpToStart(); +} + } // namespace FFmpeg diff --git a/Telegram/SourceFiles/ffmpeg/ffmpeg_emoji.h b/Telegram/SourceFiles/ffmpeg/ffmpeg_frame_generator.h similarity index 66% rename from Telegram/SourceFiles/ffmpeg/ffmpeg_emoji.h rename to Telegram/SourceFiles/ffmpeg/ffmpeg_frame_generator.h index d1e4acd96..b157bf681 100644 --- a/Telegram/SourceFiles/ffmpeg/ffmpeg_emoji.h +++ b/Telegram/SourceFiles/ffmpeg/ffmpeg_frame_generator.h @@ -14,16 +14,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace FFmpeg { -class EmojiGenerator final : public Ui::FrameGenerator { +class FrameGenerator final : public Ui::FrameGenerator { public: - explicit EmojiGenerator(const QByteArray &bytes); - ~EmojiGenerator(); + 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: class Impl; diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.h b/Telegram/SourceFiles/history/view/history_view_bottom_info.h index 615fbccbd..fca2d8d2b 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.h +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.h @@ -14,16 +14,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Ui { struct ChatPaintContext; +class AnimatedIcon; } // namespace Ui namespace Data { class Reactions; } // namespace Data -namespace Lottie { -class Icon; -} // namespace Lottie - namespace HistoryView { namespace Reactions { class Animation; @@ -31,7 +28,7 @@ class Animation; struct ReactionAnimationArgs { ::Data::ReactionId id; - std::shared_ptr flyIcon; + std::shared_ptr flyIcon; QRect flyFrom; [[nodiscard]] ReactionAnimationArgs translated(QPoint point) const; diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_animation.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_animation.cpp index 526a09ca2..511cb7be1 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_animation.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_animation.cpp @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_element.h" #include "history/view/history_view_bottom_info.h" -#include "lottie/lottie_icon.h" +#include "ui/animated_icon.h" #include "data/data_message_reactions.h" #include "data/data_document.h" #include "data/data_document_media.h" @@ -36,7 +36,7 @@ Animation::Animation( return; } const auto resolve = [&]( - std::unique_ptr &icon, + std::unique_ptr &icon, DocumentData *document, int size) { if (!document) { @@ -46,9 +46,8 @@ Animation::Animation( if (!media || !media->loaded()) { return false; } - icon = Lottie::MakeIcon({ - .path = document->filepath(true), - .json = media->bytes(), + icon = Ui::MakeAnimatedIcon({ + .generator = DocumentIconFrameGenerator(media), .sizeOverride = QSize(size, size), }); return true; @@ -142,8 +141,8 @@ int Animation::computeParabolicTop( } void Animation::startAnimations() { - _center->animate([=] { callback(); }, 0, _center->framesCount() - 1); - _effect->animate([=] { callback(); }, 0, _effect->framesCount() - 1); + _center->animate([=] { callback(); }); + _effect->animate([=] { callback(); }); } void Animation::flyCallback() { diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_animation.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_animation.h index ba13aeda8..176670193 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_animation.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_animation.h @@ -9,8 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/animations.h" -namespace Lottie { -class Icon; +namespace Ui { +class AnimatedIcon; } // namespace Lottie namespace Data { @@ -47,9 +47,9 @@ private: const not_null<::Data::Reactions*> _owner; Fn _repaint; - std::shared_ptr _flyIcon; - std::unique_ptr _center; - std::unique_ptr _effect; + std::shared_ptr _flyIcon; + std::unique_ptr _center; + std::unique_ptr _effect; Ui::Animations::Simple _fly; QRect _flyFrom; bool _valid = false; diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.h index 2aff06bad..2621373de 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.h @@ -262,8 +262,4 @@ void SetupManagerList( not_null manager, rpl::producer items); -[[nodiscard]] std::shared_ptr DefaultIconFactory( - not_null media, - int size); - } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_strip.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_strip.cpp index 9ca1e20ab..ecc4aabca 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_strip.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_strip.cpp @@ -11,8 +11,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_document.h" #include "data/data_document_media.h" -#include "lottie/lottie_icon.h" #include "main/main_session.h" +#include "ui/effects/frame_generator.h" +#include "ui/animated_icon.h" #include "styles/style_chat.h" namespace HistoryView::Reactions { @@ -27,21 +28,31 @@ constexpr auto kHoverScale = 1.24; return style::ConvertScale(kSizeForDownscale); } -[[nodiscard]] std::shared_ptr CreateIcon( +[[nodiscard]] std::shared_ptr CreateIcon( not_null media, - int size, - int frame) { + int size) { Expects(media->loaded()); - return std::make_shared(Lottie::IconDescriptor{ - .path = media->owner()->filepath(true), - .json = media->bytes(), + return std::make_shared(Ui::AnimatedIconDescriptor{ + .generator = DocumentIconFrameGenerator(media), .sizeOverride = QSize(size, size), - .frame = frame, - .limitFps = true, }); } +[[nodiscard]] std::shared_ptr CreateIconSnapshot( + not_null document, + not_null existing) { + const auto frame = existing->frame(); + return frame.isNull() + ? CreateIcon( + document->activeMediaView().get(), + existing->width()) + : std::make_shared(Ui::AnimatedIconDescriptor{ + .generator = [=] { return std::make_unique(frame); }, + .sizeOverride = existing->size(), + }); +} + } // namespace Strip::Strip( @@ -157,7 +168,7 @@ void Strip::paintOne( } else if (icon.added == AddedButton::Expand) { paintExpandIcon(p, position, target); } else { - const auto paintFrame = [&](not_null animation) { + const auto paintFrame = [&](not_null animation) { const auto size = int(std::floor(target.width() + 0.01)); const auto frame = animation->frame({ size, size }, _update); p.drawImage(target, frame.image); @@ -166,7 +177,7 @@ void Strip::paintOne( const auto appear = icon.appear.get(); if (appear && !icon.appearAnimated && allowAppearStart) { icon.appearAnimated = true; - appear->animate(_update, 0, appear->framesCount() - 1); + appear->animate(_update); } if (appear && appear->animating()) { paintFrame(appear); @@ -220,15 +231,9 @@ int Strip::fillChosenIconGetIndex(ChosenReaction &chosen) const { } const auto &icon = *i; if (const auto &appear = icon.appear; appear && appear->animating()) { - chosen.icon = CreateIcon( - icon.appearAnimation->activeMediaView().get(), - appear->width(), - appear->frameIndex()); + chosen.icon = CreateIconSnapshot(icon.appearAnimation, appear.get()); } else if (const auto &select = icon.select) { - chosen.icon = CreateIcon( - icon.selectAnimation->activeMediaView().get(), - select->width(), - select->frameIndex()); + chosen.icon = CreateIconSnapshot(icon.selectAnimation, select.get()); } return (i - begin(_icons)); } @@ -307,7 +312,7 @@ void Strip::setSelected(int index) const { const auto select = skipAnimation ? nullptr : icon.select.get(); if (select && !icon.selectAnimated) { icon.selectAnimated = true; - select->animate(_update, 0, select->framesCount() - 1); + select->animate(_update); } } }; @@ -342,13 +347,13 @@ void Strip::clearAppearAnimations(bool mainAppeared) { } icon.selectedScale.stop(); if (const auto select = icon.select.get()) { - select->jumpTo(0, nullptr); + select->jumpToStart(nullptr); } icon.selectAnimated = false; } if (icon.appearAnimated != main) { if (const auto appear = icon.appear.get()) { - appear->jumpTo(0, nullptr); + appear->jumpToStart(nullptr); } icon.appearAnimated = main; } @@ -358,7 +363,7 @@ void Strip::clearAppearAnimations(bool mainAppeared) { void Strip::clearStateForHidden(ReactionIcons &icon) { if (const auto appear = icon.appear.get()) { - appear->jumpTo(0, nullptr); + appear->jumpToStart(nullptr); } if (icon.selected) { setSelected(-1); @@ -366,7 +371,7 @@ void Strip::clearStateForHidden(ReactionIcons &icon) { icon.appearAnimated = false; icon.selectAnimated = false; if (const auto select = icon.select.get()) { - select->jumpTo(0, nullptr); + select->jumpToStart(nullptr); } icon.selectedScale.stop(); } @@ -424,8 +429,8 @@ void Strip::loadIcons() { } } } - if (all && !_icons.empty() && _icons.front().appearAnimation) { - auto &data = _icons.front().appearAnimation->owner().reactions(); + if (all && !_icons.empty() && _icons.front().selectAnimation) { + auto &data = _icons.front().selectAnimation->owner().reactions(); for (const auto &icon : _icons) { data.preloadAnimationsFor(icon.id); } @@ -562,10 +567,10 @@ IconFactory CachedIconFactory::createMethod() { }; } -std::shared_ptr DefaultIconFactory( +std::shared_ptr DefaultIconFactory( not_null media, int size) { - return CreateIcon(media, size, 0); + return CreateIcon(media, size); } } // namespace HistoryView::Reactions diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_strip.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_strip.h index 9821de5bc..405261c83 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_strip.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_strip.h @@ -18,16 +18,16 @@ struct Reaction; class DocumentMedia; } // namespace Data -namespace Lottie { -class Icon; -} // namespace Lottie +namespace Ui { +class AnimatedIcon; +} // namespace Ui namespace HistoryView::Reactions { struct ChosenReaction { FullMsgId context; Data::ReactionId id; - std::shared_ptr icon; + std::shared_ptr icon; QRect geometry; explicit operator bool() const { @@ -35,7 +35,7 @@ struct ChosenReaction { } }; -using IconFactory = Fn( +using IconFactory = Fn( not_null, int)>; @@ -84,14 +84,14 @@ private: struct ReactionDocument { std::shared_ptr media; - std::shared_ptr icon; + std::shared_ptr icon; }; struct ReactionIcons { ReactionId id; DocumentData *appearAnimation = nullptr; DocumentData *selectAnimation = nullptr; - std::shared_ptr appear; - std::shared_ptr select; + std::shared_ptr appear; + std::shared_ptr select; mutable Ui::Animations::Simple selectedScale; AddedButton added = AddedButton::None; bool appearAnimated = false; @@ -133,7 +133,7 @@ private: mutable int _selectedIcon = -1; std::shared_ptr _mainReactionMedia; - std::shared_ptr _mainReactionIcon; + std::shared_ptr _mainReactionIcon; QImage _mainReactionImage; rpl::lifetime _mainReactionLifetime; @@ -153,11 +153,11 @@ public: private: base::flat_map< std::shared_ptr, - std::shared_ptr> _cache; + std::shared_ptr> _cache; }; -[[nodiscard]] std::shared_ptr DefaultIconFactory( +[[nodiscard]] std::shared_ptr DefaultIconFactory( not_null media, int size); diff --git a/Telegram/SourceFiles/settings/settings_chat.cpp b/Telegram/SourceFiles/settings/settings_chat.cpp index 041971517..30838894b 100644 --- a/Telegram/SourceFiles/settings/settings_chat.cpp +++ b/Telegram/SourceFiles/settings/settings_chat.cpp @@ -927,7 +927,7 @@ void SetupMessages( const auto index = state->icons.flag ? 1 : 0; state->icons.lifetimes[index] = rpl::lifetime(); const auto iconSize = st::settingsReactionRightIcon; - AddReactionLottieIcon( + AddReactionAnimatedIcon( inner, buttonRight->geometryValue( ) | rpl::map([=](const QRect &r) { diff --git a/Telegram/SourceFiles/ui/text/custom_emoji_instance.cpp b/Telegram/SourceFiles/ui/text/custom_emoji_instance.cpp deleted file mode 100644 index 8170cb16c..000000000 --- a/Telegram/SourceFiles/ui/text/custom_emoji_instance.cpp +++ /dev/null @@ -1,773 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop application for the Telegram messaging service. - -For license and copyright information please follow this link: -https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL -*/ -#include "ui/text/custom_emoji_instance.h" - -#include "ui/effects/frame_generator.h" -#include "ui/ui_utility.h" - -#include -#include - -class QPainter; - -namespace Ui::CustomEmoji { -namespace { - -constexpr auto kMaxSize = 128; -constexpr auto kMaxFrames = 180; -constexpr auto kCacheVersion = 1; -constexpr auto kPreloadFrames = 3; - -struct CacheHeader { - int version = 0; - int size = 0; - int frames = 0; - int length = 0; -}; - -void PaintScaledImage( - QPainter &p, - const QRect &target, - const Cache::Frame &frame, - const Context &context) { - if (context.scaled) { - const auto sx = anim::interpolate( - target.width() / 2, - 0, - context.scale); - const auto sy = (target.height() == target.width()) - ? sx - : anim::interpolate(target.height() / 2, 0, context.scale); - const auto scaled = target.marginsRemoved({ sx, sy, sx, sy }); - if (frame.source.isNull()) { - p.drawImage(scaled, *frame.image); - } else { - p.drawImage(scaled, *frame.image, frame.source); - } - } else if (frame.source.isNull()) { - p.drawImage(target, *frame.image); - } else { - p.drawImage(target, *frame.image, frame.source); - } -} - -} // namespace - -Preview::Preview(QPainterPath path, float64 scale) -: _data(ScaledPath{ std::move(path), scale }) { -} - -Preview::Preview(QImage image, bool exact) -: _data(Image{ .data = std::move(image), .exact = exact }) { -} - -void Preview::paint(QPainter &p, const Context &context) { - if (const auto path = std::get_if(&_data)) { - paintPath(p, context, *path); - } else if (const auto image = std::get_if(&_data)) { - const auto &data = image->data; - const auto factor = style::DevicePixelRatio(); - const auto rect = QRect(context.position, data.size() / factor); - PaintScaledImage(p, rect, { .image = &data }, context); - } -} - -bool Preview::isImage() const { - return v::is(_data); -} - -bool Preview::isExactImage() const { - if (const auto image = std::get_if(&_data)) { - return image->exact; - } - return false; -} - -QImage Preview::image() const { - if (const auto image = std::get_if(&_data)) { - return image->data; - } - return QImage(); -} - -void Preview::paintPath( - QPainter &p, - const Context &context, - const ScaledPath &path) { - auto hq = PainterHighQualityEnabler(p); - p.setBrush(context.preview); - p.setPen(Qt::NoPen); - const auto scale = path.scale; - const auto required = (scale != 1.) || context.scaled; - if (required) { - p.save(); - } - p.translate(context.position); - if (required) { - p.scale(scale, scale); - const auto center = QPoint( - context.size.width() / 2, - context.size.height() / 2); - if (context.scaled) { - p.translate(center); - p.scale(context.scale, context.scale); - p.translate(-center); - } - } - p.drawPath(path.path); - if (required) { - p.restore(); - } else { - p.translate(-context.position); - } -} - -Cache::Cache(int size) : _size(size) { -} - -std::optional Cache::FromSerialized( - const QByteArray &serialized, - int requestedSize) { - Expects(requestedSize > 0 && requestedSize <= kMaxSize); - - if (serialized.size() <= sizeof(CacheHeader)) { - return {}; - } - auto header = CacheHeader(); - memcpy(&header, serialized.data(), sizeof(header)); - const auto size = header.size; - if (size != requestedSize - || header.frames <= 0 - || header.frames >= kMaxFrames - || header.length <= 0 - || header.length > (size * size * header.frames * sizeof(int32)) - || (serialized.size() != sizeof(CacheHeader) - + header.length - + (header.frames * sizeof(Cache(0)._durations[0])))) { - return {}; - } - const auto rows = (header.frames + kPerRow - 1) / kPerRow; - const auto columns = std::min(header.frames, kPerRow); - auto durations = std::vector(header.frames, 0); - auto full = QImage( - columns * size, - rows * size, - QImage::Format_ARGB32_Premultiplied); - Assert(full.bytesPerLine() == full.width() * sizeof(int32)); - - const auto decompressed = LZ4_decompress_safe( - serialized.data() + sizeof(CacheHeader), - reinterpret_cast(full.bits()), - header.length, - full.bytesPerLine() * full.height()); - if (decompressed <= 0) { - return {}; - } - memcpy( - durations.data(), - serialized.data() + sizeof(CacheHeader) + header.length, - header.frames * sizeof(durations[0])); - - auto result = Cache(size); - result._finished = true; - result._full = std::move(full); - result._frames = header.frames; - result._durations = std::move(durations); - return result; -} - -QByteArray Cache::serialize() { - Expects(_finished); - Expects(_durations.size() == _frames); - Expects(_full.bytesPerLine() == sizeof(int32) * _full.width()); - - auto header = CacheHeader{ - .version = kCacheVersion, - .size = _size, - .frames = _frames, - }; - const auto input = _full.width() * _full.height() * sizeof(int32); - const auto max = sizeof(CacheHeader) - + LZ4_compressBound(input) - + (_frames * sizeof(_durations[0])); - auto result = QByteArray(max, Qt::Uninitialized); - header.length = LZ4_compress_default( - reinterpret_cast(_full.constBits()), - result.data() + sizeof(CacheHeader), - input, - result.size() - sizeof(CacheHeader)); - Assert(header.length > 0); - memcpy(result.data(), &header, sizeof(CacheHeader)); - memcpy( - result.data() + sizeof(CacheHeader) + header.length, - _durations.data(), - _frames * sizeof(_durations[0])); - result.resize(sizeof(CacheHeader) - + header.length - + _frames * sizeof(_durations[0])); - - return result; -} - -int Cache::frames() const { - return _frames; -} - -Cache::Frame Cache::frame(int index) const { - Expects(index < _frames); - - const auto row = index / kPerRow; - const auto inrow = index % kPerRow; - if (_finished) { - return { &_full, { inrow * _size, row * _size, _size, _size } }; - } - return { &_images[row], { 0, inrow * _size, _size, _size } }; -} - -int Cache::size() const { - return _size; -} - -Preview Cache::makePreview() const { - Expects(_frames > 0); - - const auto first = frame(0); - return { first.image->copy(first.source), true }; -} - -void Cache::reserve(int frames) { - Expects(!_finished); - - const auto rows = (frames + kPerRow - 1) / kPerRow; - if (const auto add = rows - int(_images.size()); add > 0) { - _images.resize(rows); - for (auto e = end(_images), i = e - add; i != e; ++i) { - (*i) = QImage( - _size, - _size * kPerRow, - QImage::Format_ARGB32_Premultiplied); - } - } - _durations.reserve(frames); -} - -int Cache::frameRowByteSize() const { - return _size * 4; -} - -int Cache::frameByteSize() const { - return _size * frameRowByteSize(); -} - -void Cache::add(crl::time duration, const QImage &frame) { - Expects(!_finished); - Expects(frame.size() == QSize(_size, _size)); - Expects(frame.format() == QImage::Format_ARGB32_Premultiplied); - - const auto row = (_frames / kPerRow); - const auto inrow = (_frames % kPerRow); - const auto rows = row + 1; - while (_images.size() < rows) { - _images.emplace_back(); - _images.back() = QImage( - _size, - _size * kPerRow, - QImage::Format_ARGB32_Premultiplied); - } - const auto srcPerLine = frame.bytesPerLine(); - const auto dstPerLine = _images[row].bytesPerLine(); - const auto perLine = std::min(srcPerLine, dstPerLine); - auto dst = _images[row].bits() + inrow * _size * dstPerLine; - auto src = frame.constBits(); - for (auto y = 0; y != _size; ++y) { - memcpy(dst, src, perLine); - dst += dstPerLine; - src += srcPerLine; - } - ++_frames; - _durations.push_back(std::clamp( - duration, - crl::time(0), - crl::time(std::numeric_limits::max()))); -} - -void Cache::finish() { - _finished = true; - if (_frame == _frames) { - _frame = 0; - } - const auto rows = (_frames + kPerRow - 1) / kPerRow; - const auto columns = std::min(_frames, kPerRow); - const auto zero = (rows * columns) - _frames; - _full = QImage( - columns * _size, - rows * _size, - QImage::Format_ARGB32_Premultiplied); - auto dstData = _full.bits(); - const auto perLine = _size * 4; - const auto dstPerLine = _full.bytesPerLine(); - for (auto y = 0; y != rows; ++y) { - auto &row = _images[y]; - auto src = row.bits(); - const auto srcPerLine = row.bytesPerLine(); - const auto till = columns - ((y + 1 == rows) ? zero : 0); - for (auto x = 0; x != till; ++x) { - auto dst = dstData + y * dstPerLine * _size + x * perLine; - for (auto line = 0; line != _size; ++line) { - memcpy(dst, src, perLine); - src += srcPerLine; - dst += dstPerLine; - } - } - } - if (const auto perLine = zero * _size) { - auto dst = dstData - + (rows - 1) * dstPerLine * _size - + (columns - zero) * _size * 4; - for (auto left = 0; left != _size; ++left) { - memset(dst, 0, perLine); - dst += dstPerLine; - } - } -} - -PaintFrameResult Cache::paintCurrentFrame( - QPainter &p, - const Context &context) { - if (!_frames) { - return {}; - } - const auto first = context.firstFrameOnly; - if (!first) { - const auto now = context.paused ? 0 : context.now; - const auto finishes = now ? currentFrameFinishes() : 0; - if (finishes && now >= finishes) { - ++_frame; - if (_finished && _frame == _frames) { - _frame = 0; - } - _shown = now; - } else if (!_shown) { - _shown = now; - } - } - const auto index = first ? 0 : std::min(_frame, _frames - 1); - const auto info = frame(index); - const auto size = _size / style::DevicePixelRatio(); - const auto rect = QRect(context.position, QSize(size, size)); - PaintScaledImage(p, rect, info, context); - const auto next = first ? 0 : currentFrameFinishes(); - return { - .painted = true, - .next = next, - .duration = next ? (next - _shown) : 0, - }; -} - -int Cache::currentFrame() const { - return _frame; -} - -crl::time Cache::currentFrameFinishes() const { - if (!_shown || _frame >= _durations.size()) { - return 0; - } else if (const auto duration = _durations[_frame]) { - return _shown + duration; - } - return 0; -} - -Cached::Cached( - const QString &entityData, - Fn()> unloader, - Cache cache) -: _unloader(std::move(unloader)) -, _cache(std::move(cache)) -, _entityData(entityData) { -} - -QString Cached::entityData() const { - return _entityData; -} - -PaintFrameResult Cached::paint(QPainter &p, const Context &context) { - return _cache.paintCurrentFrame(p, context); -} - -Preview Cached::makePreview() const { - return _cache.makePreview(); -} - -Loading Cached::unload() { - return Loading(_unloader(), makePreview()); -} - -Renderer::Renderer(RendererDescriptor &&descriptor) -: _cache(descriptor.size) -, _put(std::move(descriptor.put)) -, _loader(std::move(descriptor.loader)) { - Expects(_loader != nullptr); - - const auto size = _cache.size(); - const auto guard = base::make_weak(this); - crl::async([=, factory = std::move(descriptor.generator)]() mutable { - auto generator = factory(); - auto rendered = generator->renderNext( - QImage(), - QSize(size, size), - Qt::KeepAspectRatio); - if (rendered.image.isNull()) { - return; - } - crl::on_main(guard, [ - =, - frame = std::move(rendered), - generator = std::move(generator) - ]() mutable { - frameReady( - std::move(generator), - frame.duration, - std::move(frame.image)); - }); - }); -} - -Renderer::~Renderer() = default; - -void Renderer::frameReady( - std::unique_ptr generator, - crl::time duration, - QImage frame) { - if (frame.isNull()) { - finish(); - return; - } - if (const auto count = generator->count()) { - if (!_cache.frames()) { - _cache.reserve(std::max(count, kMaxFrames)); - } - } - const auto current = _cache.currentFrame(); - const auto total = _cache.frames(); - const auto explicitRepaint = (current == total); - _cache.add(duration, frame); - if (explicitRepaint && _repaint) { - _repaint(); - } - if (!duration || total + 1 >= kMaxFrames) { - finish(); - } else if (current + kPreloadFrames > total) { - renderNext(std::move(generator), std::move(frame)); - } else { - _generator = std::move(generator); - _storage = std::move(frame); - } -} - -void Renderer::renderNext( - std::unique_ptr generator, - QImage storage) { - const auto size = _cache.size(); - const auto guard = base::make_weak(this); - crl::async([ - =, - storage = std::move(storage), - generator = std::move(generator) - ]() mutable { - auto rendered = generator->renderNext( - std::move(storage), - QSize(size, size), - Qt::KeepAspectRatio); - crl::on_main(guard, [ - =, - frame = std::move(rendered), - generator = std::move(generator) - ]() mutable { - frameReady( - std::move(generator), - frame.duration, - std::move(frame.image)); - }); - }); -} - -void Renderer::finish() { - _finished = true; - _cache.finish(); - if (_put) { - _put(_cache.serialize()); - } -} - -PaintFrameResult Renderer::paint(QPainter &p, const Context &context) { - const auto result = _cache.paintCurrentFrame(p, context); - if (_generator - && (!result.painted - || _cache.currentFrame() + kPreloadFrames >= _cache.frames())) { - renderNext(std::move(_generator), std::move(_storage)); - } - return result; -} - -std::optional Renderer::ready(const QString &entityData) { - return _finished - ? Cached{ entityData, std::move(_loader), std::move(_cache) } - : std::optional(); -} - -std::unique_ptr Renderer::cancel() { - return _loader(); -} - -bool Renderer::canMakePreview() const { - return _cache.frames() > 0; -} - -Preview Renderer::makePreview() const { - return _cache.makePreview(); -} - -void Renderer::setRepaintCallback(Fn repaint) { - _repaint = std::move(repaint); -} - -Cache Renderer::takeCache() { - return std::move(_cache); -} - -Loading::Loading(std::unique_ptr loader, Preview preview) -: _loader(std::move(loader)) -, _preview(std::move(preview)) { -} - -QString Loading::entityData() const { - return _loader->entityData(); -} - -void Loading::load(Fn done) { - _loader->load(crl::guard(this, [this, done = std::move(done)]( - Loader::LoadResult result) mutable { - if (const auto caching = std::get_if(&result)) { - caching->preview = _preview - ? std::move(_preview) - : _loader->preview(); - } - done(std::move(result)); - })); -} - -bool Loading::loading() const { - return _loader->loading(); -} - -void Loading::paint(QPainter &p, const Context &context) { - if (!_preview) { - if (auto preview = _loader->preview()) { - _preview = std::move(preview); - } - } - _preview.paint(p, context); -} - -bool Loading::hasImagePreview() const { - return _preview.isImage(); -} - -Preview Loading::imagePreview() const { - return _preview.isImage() ? _preview : Preview(); -} - -void Loading::updatePreview(Preview preview) { - if (!_preview.isImage() && preview.isImage()) { - _preview = std::move(preview); - } else if (!_preview) { - if (auto loaderPreview = _loader->preview()) { - _preview = std::move(loaderPreview); - } else if (preview) { - _preview = std::move(preview); - } - } -} - -void Loading::cancel() { - _loader->cancel(); - invalidate_weak_ptrs(this); -} - -Instance::Instance( - Loading loading, - Fn, RepaintRequest)> repaintLater) -: _state(std::move(loading)) -, _repaintLater(std::move(repaintLater)) { -} - -QString Instance::entityData() const { - return v::match(_state, [](const Loading &state) { - return state.entityData(); - }, [](const Caching &state) { - return state.entityData; - }, [](const Cached &state) { - return state.entityData(); - }); -} - -void Instance::paint(QPainter &p, const Context &context) { - v::match(_state, [&](Loading &state) { - state.paint(p, context); - load(state); - }, [&](Caching &state) { - auto result = state.renderer->paint(p, context); - if (!result.painted) { - state.preview.paint(p, context); - } else { - if (!state.preview.isExactImage()) { - state.preview = state.renderer->makePreview(); - } - if (result.next > context.now) { - _repaintLater(this, { result.next, result.duration }); - } - } - if (auto cached = state.renderer->ready(state.entityData)) { - _state = std::move(*cached); - } - }, [&](Cached &state) { - const auto result = state.paint(p, context); - if (result.next > context.now) { - _repaintLater(this, { result.next, result.duration }); - } - }); -} - -bool Instance::ready() { - return v::match(_state, [&](Loading &state) { - if (state.hasImagePreview()) { - return true; - } - load(state); - return false; - }, [](Caching &state) { - return state.renderer->canMakePreview(); - }, [](Cached &state) { - return true; - }); -} - -void Instance::load(Loading &state) { - state.load([=](Loader::LoadResult result) { - if (auto caching = std::get_if(&result)) { - caching->renderer->setRepaintCallback([=] { repaint(); }); - _state = std::move(*caching); - } else if (auto cached = std::get_if(&result)) { - _state = std::move(*cached); - repaint(); - } else { - Unexpected("Value in Loader::LoadResult."); - } - }); -} - -bool Instance::hasImagePreview() const { - return v::match(_state, [](const Loading &state) { - return state.hasImagePreview(); - }, [](const Caching &state) { - return state.preview.isImage(); - }, [](const Cached &state) { - return true; - }); -} - -Preview Instance::imagePreview() const { - return v::match(_state, [](const Loading &state) { - return state.imagePreview(); - }, [](const Caching &state) { - return state.preview.isImage() ? state.preview : Preview(); - }, [](const Cached &state) { - return state.makePreview(); - }); -} - -void Instance::updatePreview(Preview preview) { - v::match(_state, [&](Loading &state) { - state.updatePreview(std::move(preview)); - }, [&](Caching &state) { - if ((!state.preview.isImage() && preview.isImage()) - || (!state.preview && preview)) { - state.preview = std::move(preview); - } - }, [](const Cached &) {}); -} - -void Instance::repaint() { - for (const auto &object : _usage) { - object->repaint(); - } -} - -void Instance::incrementUsage(not_null object) { - _usage.emplace(object); -} - -void Instance::decrementUsage(not_null object) { - _usage.remove(object); - if (!_usage.empty()) { - return; - } - v::match(_state, [](Loading &state) { - state.cancel(); - }, [&](Caching &state) { - _state = Loading{ - state.renderer->cancel(), - std::move(state.preview), - }; - }, [&](Cached &state) { - _state = state.unload(); - }); - _repaintLater(this, RepaintRequest()); -} - -Object::Object(not_null instance, Fn repaint) -: _instance(instance) -, _repaint(std::move(repaint)) { -} - -Object::~Object() { - unload(); -} - -QString Object::entityData() { - return _instance->entityData(); -} - -void Object::paint(QPainter &p, const Context &context) { - if (!_using) { - _using = true; - _instance->incrementUsage(this); - } - _instance->paint(p, context); -} - -void Object::unload() { - if (_using) { - _using = false; - _instance->decrementUsage(this); - } -} - -bool Object::ready() { - if (!_using) { - _using = true; - _instance->incrementUsage(this); - } - return _instance->ready(); -} - -void Object::repaint() { - _repaint(); -} - -} // namespace Ui::CustomEmoji diff --git a/Telegram/SourceFiles/ui/text/custom_emoji_instance.h b/Telegram/SourceFiles/ui/text/custom_emoji_instance.h deleted file mode 100644 index 39296a50e..000000000 --- a/Telegram/SourceFiles/ui/text/custom_emoji_instance.h +++ /dev/null @@ -1,271 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop application for the Telegram messaging service. - -For license and copyright information please follow this link: -https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL -*/ -#pragma once - -#include "ui/text/text_custom_emoji.h" -#include "base/weak_ptr.h" -#include "base/bytes.h" -#include "base/timer.h" - -class QColor; -class QPainter; - -namespace Ui { -class FrameGenerator; -} // namespace Ui - -namespace Ui::CustomEmoji { - -using Context = Ui::Text::CustomEmoji::Context; - -class Preview final { -public: - Preview() = default; - Preview(QImage image, bool exact); - Preview(QPainterPath path, float64 scale); - - void paint(QPainter &p, const Context &context); - [[nodiscard]] bool isImage() const; - [[nodiscard]] bool isExactImage() const; - [[nodiscard]] QImage image() const; - - [[nodiscard]] explicit operator bool() const { - return !v::is_null(_data); - } - -private: - struct ScaledPath { - QPainterPath path; - float64 scale = 1.; - }; - struct Image { - QImage data; - bool exact = false; - }; - - void paintPath( - QPainter &p, - const Context &context, - const ScaledPath &path); - - std::variant _data; - -}; - -struct PaintFrameResult { - bool painted = false; - crl::time next = 0; - crl::time duration = 0; -}; - -class Cache final { -public: - Cache(int size); - - struct Frame { - not_null image; - QRect source; - }; - - [[nodiscard]] static std::optional FromSerialized( - const QByteArray &serialized, - int requestedSize); - [[nodiscard]] QByteArray serialize(); - - [[nodiscard]] int size() const; - [[nodiscard]] int frames() const; - [[nodiscard]] Frame frame(int index) const; - void reserve(int frames); - void add(crl::time duration, const QImage &frame); - void finish(); - - [[nodiscard]] Preview makePreview() const; - - PaintFrameResult paintCurrentFrame(QPainter &p, const Context &context); - [[nodiscard]] int currentFrame() const; - -private: - static constexpr auto kPerRow = 16; - - [[nodiscard]] int frameRowByteSize() const; - [[nodiscard]] int frameByteSize() const; - [[nodiscard]] crl::time currentFrameFinishes() const; - - std::vector _images; - std::vector _durations; - QImage _full; - crl::time _shown = 0; - int _frame = 0; - int _size = 0; - int _frames = 0; - bool _finished = false; - -}; - -class Loader; -class Loading; - -class Cached final { -public: - Cached( - const QString &entityData, - Fn()> unloader, - Cache cache); - - [[nodiscard]] QString entityData() const; - [[nodiscard]] Preview makePreview() const; - PaintFrameResult paint(QPainter &p, const Context &context); - [[nodiscard]] Loading unload(); - -private: - Fn()> _unloader; - Cache _cache; - QString _entityData; - -}; - -struct RendererDescriptor { - Fn()> generator; - Fn put; - Fn()> loader; - int size = 0; -}; - -class Renderer final : public base::has_weak_ptr { -public: - explicit Renderer(RendererDescriptor &&descriptor); - virtual ~Renderer(); - - PaintFrameResult paint(QPainter &p, const Context &context); - [[nodiscard]] std::optional ready(const QString &entityData); - [[nodiscard]] std::unique_ptr cancel(); - - [[nodiscard]] bool canMakePreview() const; - [[nodiscard]] Preview makePreview() const; - - void setRepaintCallback(Fn repaint); - [[nodiscard]] Cache takeCache(); - -private: - void frameReady( - std::unique_ptr generator, - crl::time duration, - QImage frame); - void renderNext( - std::unique_ptr generator, - QImage storage); - void finish(); - - Cache _cache; - std::unique_ptr _generator; - QImage _storage; - Fn _put; - Fn _repaint; - Fn()> _loader; - bool _finished = false; - -}; - -struct Caching { - std::unique_ptr renderer; - QString entityData; - Preview preview; -}; - -class Loader { -public: - using LoadResult = std::variant; - [[nodiscard]] virtual QString entityData() = 0; - virtual void load(Fn loaded) = 0; - [[nodiscard]] virtual bool loading() = 0; - virtual void cancel() = 0; - [[nodiscard]] virtual Preview preview() = 0; - virtual ~Loader() = default; -}; - -class Loading final : public base::has_weak_ptr { -public: - Loading(std::unique_ptr loader, Preview preview); - - [[nodiscard]] QString entityData() const; - - void load(Fn done); - [[nodiscard]] bool loading() const; - void paint(QPainter &p, const Context &context); - [[nodiscard]] bool hasImagePreview() const; - [[nodiscard]] Preview imagePreview() const; - void updatePreview(Preview preview); - void cancel(); - -private: - std::unique_ptr _loader; - Preview _preview; - -}; - -struct RepaintRequest { - crl::time when = 0; - crl::time duration = 0; -}; - -class Object; -class Instance final : public base::has_weak_ptr { -public: - Instance( - Loading loading, - Fn, RepaintRequest)> repaintLater); - Instance(const Instance&) = delete; - Instance &operator=(const Instance&) = delete; - - [[nodiscard]] QString entityData() const; - void paint(QPainter &p, const Context &context); - [[nodiscard]] bool ready(); - [[nodiscard]] bool hasImagePreview() const; - [[nodiscard]] Preview imagePreview() const; - void updatePreview(Preview preview); - - void incrementUsage(not_null object); - void decrementUsage(not_null object); - - void repaint(); - -private: - void load(Loading &state); - - std::variant _state; - base::flat_set> _usage; - Fn that, RepaintRequest)> _repaintLater; - -}; - -class Delegate { -public: - [[nodiscard]] virtual bool paused() = 0; - virtual ~Delegate() = default; -}; - -class Object final : public Ui::Text::CustomEmoji { -public: - Object(not_null instance, Fn repaint); - ~Object(); - - QString entityData() override; - void paint(QPainter &p, const Context &context) override; - void unload() override; - bool ready() override; - - void repaint(); - -private: - const not_null _instance; - Fn _repaint; - bool _using = false; - -}; - -} // namespace Ui::CustomEmoji diff --git a/Telegram/cmake/lib_ffmpeg.cmake b/Telegram/cmake/lib_ffmpeg.cmake index a1eac0c2f..7416fa873 100644 --- a/Telegram/cmake/lib_ffmpeg.cmake +++ b/Telegram/cmake/lib_ffmpeg.cmake @@ -10,8 +10,8 @@ init_target(lib_ffmpeg) nice_target_sources(lib_ffmpeg ${src_loc} PRIVATE - ffmpeg/ffmpeg_emoji.cpp - ffmpeg/ffmpeg_emoji.h + ffmpeg/ffmpeg_frame_generator.cpp + ffmpeg/ffmpeg_frame_generator.h ffmpeg/ffmpeg_utility.cpp ffmpeg/ffmpeg_utility.h ) diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index ae84e0090..d51cc3c45 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -242,8 +242,6 @@ PRIVATE ui/effects/round_checkbox.h ui/effects/scroll_content_shadow.cpp ui/effects/scroll_content_shadow.h - ui/text/custom_emoji_instance.cpp - ui/text/custom_emoji_instance.h ui/text/format_song_name.cpp ui/text/format_song_name.h ui/text/format_values.cpp diff --git a/Telegram/lib_lottie b/Telegram/lib_lottie index d4764ed8b..960473ef4 160000 --- a/Telegram/lib_lottie +++ b/Telegram/lib_lottie @@ -1 +1 @@ -Subproject commit d4764ed8bf0ba2309847bca99f7f8b3d58db25b4 +Subproject commit 960473ef42f92a4e0f0f2ac37a2c7b225f1527fc diff --git a/Telegram/lib_ui b/Telegram/lib_ui index f876d15ee..3287bf45c 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit f876d15eedbce39a445b020b92f45d137952ed2a +Subproject commit 3287bf45c607e980268090ae27dfe4fb69d037e4