diff --git a/Telegram/SourceFiles/ui/chat/chat_theme.cpp b/Telegram/SourceFiles/ui/chat/chat_theme.cpp index babcb94215..c745a0f180 100644 --- a/Telegram/SourceFiles/ui/chat/chat_theme.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_theme.cpp @@ -7,12 +7,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/chat/chat_theme.h" -#include "ui/image/image_prepare.h" +#include "ui/color_contrast.h" +#include "ui/emoji_config.h" +#include "ui/painter.h" #include "ui/power_saving.h" #include "ui/ui_utility.h" #include "ui/chat/message_bubble.h" #include "ui/chat/chat_style.h" -#include "ui/color_contrast.h" +#include "ui/image/image_prepare.h" +#include "ui/text/text_custom_emoji.h" #include "ui/style/style_core_palette.h" #include "ui/style/style_palette_colorizer.h" @@ -53,6 +56,14 @@ constexpr auto kMinAcceptableContrast = 1.14;// 4.5; return (doubled % 2) ? 0.5 : 1.; } +[[nodiscard]] int RotationForSymbol(const QRectF &symbol) { + const auto a = int(base::SafeRound(symbol.x() * 255)); + const auto b = int(base::SafeRound(symbol.y() * 255)); + const auto value = uint16((uint16(a) << 8) | uint16(b)); + const auto shuffled = uint16(value << 5) | uint16(value >> 3); + return (shuffled % 90) - 45; +} + [[nodiscard]] CacheBackgroundResult CacheBackgroundByRequest( const CacheBackgroundRequest &request) { Expects(!request.area.isEmpty()); @@ -83,14 +94,17 @@ constexpr auto kMinAcceptableContrast = 1.14;// 4.5; QPainter p(&result); if (!gradient.isNull()) { if (request.background.patternOpacity >= 0.) { - p.setCompositionMode(QPainter::CompositionMode_SoftLight); + p.setCompositionMode( + QPainter::CompositionMode_SoftLight); p.setOpacity(request.background.patternOpacity); } else { p.setCompositionMode( QPainter::CompositionMode_DestinationIn); } } - const auto tiled = request.background.isPattern + const auto hasGiftSymbols = request.background.isPattern + && !request.background.giftSymbols.empty(); + auto tiled = request.background.isPattern ? request.background.prepared.scaled( request.area.height() * ratio, request.area.height() * ratio, @@ -99,6 +113,31 @@ constexpr auto kMinAcceptableContrast = 1.14;// 4.5; : request.background.preparedForTiled; const auto w = tiled.width() / float(ratio); const auto h = tiled.height() / float(ratio); + + const auto &giftSymbols = request.background.giftSymbols; + const auto &giftSymbolFrame = request.background.giftSymbolFrame; + const auto giftSymbolSize = giftSymbolFrame.size() / ratio; + if (hasGiftSymbols) { + auto q = QPainter(&tiled); + auto hq = PainterHighQualityEnabler(q); + q.setOpacity(0.8); + for (const auto &symbol : giftSymbols) { + const auto rect = QRectF( + symbol.x() * w, + symbol.y() * h, + symbol.width() * w, + symbol.height() * h); + q.save(); + q.translate(rect.center()); + q.scale( + rect.width() / giftSymbolSize.width(), + rect.height() / giftSymbolSize.height()); + q.rotate(RotationForSymbol(symbol)); + q.translate(-rect.center()); + q.drawImage(rect.topLeft(), giftSymbolFrame); + q.restore(); + } + } const auto cx = int(std::ceil(request.area.width() / w)); const auto cy = int(std::ceil(request.area.height() / h)); const auto rows = cy; @@ -111,7 +150,8 @@ constexpr auto kMinAcceptableContrast = 1.14;// 4.5; const auto useshift = xshift / float(ratio); for (auto y = 0; y != rows; ++y) { for (auto x = 0; x != cols; ++x) { - p.drawImage(QPointF(useshift + x * w, y * h), tiled); + const auto position = QPointF(useshift + x * w, y * h); + p.drawImage(position, tiled); } } if (!gradient.isNull() @@ -1093,7 +1133,7 @@ ChatThemeBackground PrepareBackgroundImage( data.path, data.bytes, data.gzipSvg, - data.findGiftSymbols) + !data.giftSymbolFrame.isNull()) : BackgroundImageFields(); auto prepared = needBackground ? PreprocessBackgroundImage(std::move(read.image)) @@ -1143,10 +1183,32 @@ ChatThemeBackground PrepareBackgroundImage( : std::make_optional(data.colors.front())), .colors = data.colors, .giftSymbols = std::move(read.giftSymbols), + .giftSymbolFrame = data.giftSymbolFrame, .patternOpacity = data.patternOpacity, .gradientRotation = data.generateGradient ? data.gradientRotation : 0, .isPattern = data.isPattern, }; } +[[nodiscard]] QImage PrepareGiftSymbol( + const std::unique_ptr &emoji) { + if (!emoji || !emoji->ready()) { + return QImage(); + } + const auto ratio = style::DevicePixelRatio(); + const auto size = Ui::Emoji::GetSizeNormal() / ratio; + auto result = QImage( + 2 * QSize(size, size) * ratio, + QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(ratio); + result.fill(Qt::transparent); + auto p = QPainter(&result); + const auto shift = (2 * size - (Ui::Emoji::GetSizeLarge() / ratio)) / 2; + emoji->paint(p, { + .textColor = QColor(0, 0, 0), + .position = QPoint(shift, shift), + }); + return result; +} + } // namespace Window::Theme diff --git a/Telegram/SourceFiles/ui/chat/chat_theme.h b/Telegram/SourceFiles/ui/chat/chat_theme.h index 9c1643dfbb..caa7dfb591 100644 --- a/Telegram/SourceFiles/ui/chat/chat_theme.h +++ b/Telegram/SourceFiles/ui/chat/chat_theme.h @@ -16,6 +16,10 @@ class palette; struct colorizer; } // namespace style +namespace Ui::Text { +class CustomEmoji; +} // namespace Ui::Text + namespace Ui { class ChatStyle; @@ -30,6 +34,7 @@ struct ChatThemeBackground { std::optional colorForFill; std::vector colors; std::vector giftSymbols; + QImage giftSymbolFrame; float64 patternOpacity = 1.; int gradientRotation = 0; bool isPattern = false; @@ -47,6 +52,7 @@ struct ChatThemeBackgroundData { QString key; QString path; QByteArray bytes; + QImage giftSymbolFrame; bool gzipSvg = false; std::vector colors; bool isPattern = false; @@ -55,7 +61,6 @@ struct ChatThemeBackgroundData { bool isBlurred = false; bool forDarkMode = false; bool generateGradient = false; - bool findGiftSymbols = false; int gradientRotation = 0; }; @@ -269,5 +274,7 @@ struct BackgroundImageFields { int rotation); [[nodiscard]] ChatThemeBackground PrepareBackgroundImage( const ChatThemeBackgroundData &data); +[[nodiscard]] QImage PrepareGiftSymbol( + const std::unique_ptr &emoji); } // namespace Ui diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index e6df00af52..8546ffe054 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -1505,8 +1505,8 @@ struct SessionController::CachedThemeKey { struct SessionController::CachedTheme { std::weak_ptr theme; std::shared_ptr media; + std::unique_ptr giftSymbol; Data::WallPaper paper; - bool findGiftSymbols = false; bool basedOnDark = false; bool caching = false; rpl::lifetime lifetime; @@ -3296,13 +3296,20 @@ void SessionController::cacheChatTheme( const auto document = use.document(); const auto media = document ? document->createMediaView() : nullptr; const auto findGiftSymbols = (data.unique != nullptr); + auto giftSymbol = findGiftSymbols + ? session().data().customEmojiManager().create( + data.unique->pattern.document, + crl::guard(this, [=] { _giftSymbolLoaded.fire({}); }), + Data::CustomEmojiSizeTag::Large) + : nullptr; + const auto giftSymbolReady = !giftSymbol || giftSymbol->ready(); use.loadDocument(); auto &theme = [&]() -> CachedTheme& { const auto i = _customChatThemes.find(key); if (i != end(_customChatThemes)) { i->second.media = media; + i->second.giftSymbol = std::move(giftSymbol); i->second.paper = use; - i->second.findGiftSymbols = findGiftSymbols; i->second.basedOnDark = dark; i->second.caching = true; return i->second; @@ -3311,8 +3318,8 @@ void SessionController::cacheChatTheme( key, CachedTheme{ .media = media, + .giftSymbol = std::move(giftSymbol), .paper = use, - .findGiftSymbols = findGiftSymbols, .basedOnDark = dark, .caching = true, }).first->second; @@ -3341,6 +3348,9 @@ void SessionController::cacheChatTheme( }); if (media && media->loaded(true)) { theme.media = nullptr; + if (giftSymbolReady) { + theme.giftSymbol = nullptr; + } } } @@ -3358,15 +3368,25 @@ void SessionController::cacheChatThemeDone( } i->second.caching = false; i->second.theme = result; - if (i->second.media) { - if (i->second.media->loaded(true)) { + const auto media = i->second.media.get(); + const auto giftSymbol = i->second.giftSymbol.get(); + if (media || giftSymbol) { + if ((!media || media->loaded(true)) + && (!giftSymbol || giftSymbol->ready())) { updateCustomThemeBackground(i->second); } else { - session().downloaderTaskFinished( + rpl::merge( + session().downloaderTaskFinished(), + ((giftSymbol && !giftSymbol->ready()) + ? (_giftSymbolLoaded.events() | rpl::type_erased()) + : rpl::never()) ) | rpl::filter([=] { const auto i = _customChatThemes.find(key); Assert(i != end(_customChatThemes)); - return !i->second.media || i->second.media->loaded(true); + const auto media = i->second.media.get(); + const auto giftSymbol = i->second.giftSymbol.get(); + return (!media || media->loaded(true)) + && (!giftSymbol || giftSymbol->ready()); }) | rpl::start_with_next([=] { const auto i = _customChatThemes.find(key); Assert(i != end(_customChatThemes)); @@ -3381,9 +3401,15 @@ void SessionController::updateCustomThemeBackground(CachedTheme &theme) { const auto guard = gsl::finally([&] { theme.lifetime.destroy(); theme.media = nullptr; + theme.giftSymbol = nullptr; }); const auto strong = theme.theme.lock(); - if (!theme.media || !strong || !theme.media->loaded(true)) { + const auto media = theme.media.get(); + const auto giftSymbol = theme.giftSymbol.get(); + if (!strong + || (!media && !giftSymbol) + || (media && !media->loaded(true)) + || (giftSymbol && !giftSymbol->ready())) { return; } const auto key = strong->key(); @@ -3424,6 +3450,7 @@ Ui::ChatThemeBackgroundData SessionController::backgroundData( .key = paper.key(), .path = paperPath, .bytes = paperBytes, + .giftSymbolFrame = Ui::PrepareGiftSymbol(theme.giftSymbol), .gzipSvg = gzipSvg, .colors = colors, .isPattern = isPattern, @@ -3432,7 +3459,6 @@ Ui::ChatThemeBackgroundData SessionController::backgroundData( .isBlurred = isBlurred, .forDarkMode = theme.basedOnDark, .generateGradient = generateGradient, - .findGiftSymbols = theme.findGiftSymbols, .gradientRotation = gradientRotation, }; } diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index d8597e0aab..be206dadf0 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -799,6 +799,7 @@ private: const std::shared_ptr _defaultChatTheme; base::flat_map _customChatThemes; rpl::event_stream> _cachedThemesStream; + rpl::event_stream<> _giftSymbolLoaded; const std::unique_ptr _chatStyle; std::weak_ptr _chatStyleTheme; std::deque> _lastUsedCustomChatThemes; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index c9c1f63df8..3710f80536 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit c9c1f63df8c7d6f8043ca66d8e67cc4fdb84c6c5 +Subproject commit 3710f80536352b009c5ab359210911086ead6b13