From f5a323e40ad0c3394c299c78e00edfbdb83036b4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 27 Aug 2021 14:32:18 +0300 Subject: [PATCH] Move all background helper methods to chat_theme module. --- Telegram/SourceFiles/boxes/background_box.cpp | 5 +- .../boxes/background_preview_box.cpp | 16 +- .../data/data_document_resolver.cpp | 19 +- .../SourceFiles/data/data_document_resolver.h | 2 +- Telegram/SourceFiles/data/data_wall_paper.cpp | 88 +----- Telegram/SourceFiles/data/data_wall_paper.h | 15 - .../media/history_view_theme_document.cpp | 4 +- Telegram/SourceFiles/mainwidget.cpp | 5 +- Telegram/SourceFiles/ui/chat/chat_theme.cpp | 262 +++++++++++++++++- Telegram/SourceFiles/ui/chat/chat_theme.h | 37 +++ .../window/themes/window_theme.cpp | 125 +-------- .../SourceFiles/window/themes/window_theme.h | 11 +- .../themes/window_themes_cloud_list.cpp | 3 +- .../SourceFiles/window/window_main_menu.cpp | 2 +- .../window/window_session_controller.cpp | 186 ++++++++++--- .../window/window_session_controller.h | 10 +- 16 files changed, 491 insertions(+), 299 deletions(-) diff --git a/Telegram/SourceFiles/boxes/background_box.cpp b/Telegram/SourceFiles/boxes/background_box.cpp index 360e79d60..ffe69d903 100644 --- a/Telegram/SourceFiles/boxes/background_box.cpp +++ b/Telegram/SourceFiles/boxes/background_box.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "ui/effects/round_checkbox.h" #include "ui/image/image.h" +#include "ui/chat/chat_theme.h" #include "ui/ui_utility.h" #include "main/main_session.h" #include "apiwrap.h" @@ -331,7 +332,7 @@ void BackgroundBox::Inner::validatePaperThumbnail( } } else if (!paper.data.backgroundColors().empty()) { paper.thumbnail = Ui::PixmapFromImage( - Data::GenerateWallPaper( + Ui::GenerateBackgroundImage( st::backgroundSize * cIntRetinaFactor(), paper.data.backgroundColors(), paper.data.gradientRotation())); @@ -346,7 +347,7 @@ void BackgroundBox::Inner::validatePaperThumbnail( : paper.dataMedia->thumbnail(); auto original = thumbnail->original(); if (paper.data.isPattern()) { - original = Data::PreparePatternImage( + original = Ui::PreparePatternImage( std::move(original), paper.data.backgroundColors(), paper.data.gradientRotation(), diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp index e56ff46ab..c4013f39e 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp @@ -341,7 +341,7 @@ bool ServiceCheck::checkRippleStartPosition(QPoint position) const { Images::Option blur = Images::Option(0)) { auto result = PrepareScaledNonPattern(image, blur); if (isPattern) { - result = Data::PreparePatternImage( + result = Ui::PreparePatternImage( std::move(result), background, gradientRotation, @@ -395,7 +395,7 @@ void BackgroundPreviewBox::generateBackground() { const auto size = QSize(st::boxWideWidth, st::boxWideWidth) * cIntRetinaFactor(); _generated = Ui::PixmapFromImage((_paper.patternOpacity() >= 0.) - ? Data::GenerateWallPaper( + ? Ui::GenerateBackgroundImage( size, _paper.backgroundColors(), _paper.gradientRotation()) @@ -668,7 +668,7 @@ void BackgroundPreviewBox::setScaledFromThumb() { auto blurred = (_paper.document() || _paper.isPattern()) ? QImage() : PrepareScaledNonPattern( - Data::PrepareBlurredBackground(thumbnail->original()), + Ui::PrepareBlurredBackground(thumbnail->original()), Images::Option(0)); setScaledFromImage(std::move(scaled), std::move(blurred)); } @@ -676,7 +676,7 @@ void BackgroundPreviewBox::setScaledFromThumb() { void BackgroundPreviewBox::setScaledFromImage( QImage &&image, QImage &&blurred) { - updateServiceBg({ Window::Theme::CountAverageColor(image) }); + updateServiceBg({ Ui::CountAverageColor(image) }); if (!_full.isNull()) { startFadeInFrom(std::move(_scaled)); } @@ -714,7 +714,7 @@ void BackgroundPreviewBox::updateServiceBg(const std::vector &bg) { green += color.green(); blue += color.blue(); } - _serviceBg = Window::Theme::AdjustedColor( + _serviceBg = Ui::ThemeAdjustedColor( st::msgServiceBg->c, QColor(red / count, green / count, blue / count)); } @@ -748,7 +748,7 @@ void BackgroundPreviewBox::checkLoadedDocument() { patternOpacity); auto blurred = !isPattern ? PrepareScaledNonPattern( - Data::PrepareBlurredBackground(image), + Ui::PrepareBlurredBackground(image), Images::Option(0)) : QImage(); crl::on_main(std::move(guard), [ @@ -763,9 +763,9 @@ void BackgroundPreviewBox::checkLoadedDocument() { }); }); }; - _generating = Data::ReadImageAsync( + _generating = Data::ReadBackgroundImageAsync( _media.get(), - Window::Theme::PreprocessBackgroundImage, + Ui::PreprocessBackgroundImage, generateCallback); } diff --git a/Telegram/SourceFiles/data/data_document_resolver.cpp b/Telegram/SourceFiles/data/data_document_resolver.cpp index 53049a70a..1c0352a6d 100644 --- a/Telegram/SourceFiles/data/data_document_resolver.cpp +++ b/Telegram/SourceFiles/data/data_document_resolver.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item.h" #include "media/player/media_player_instance.h" #include "platform/platform_file_utilities.h" +#include "ui/chat/chat_theme.h" #include "ui/text/text_utilities.h" #include "window/window_session_controller.h" @@ -33,8 +34,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Data { namespace { -constexpr auto kMaxWallpaperSize = 3072; - void LaunchWithWarning( // not_null controller, const QString &name, @@ -175,19 +174,7 @@ bool IsIpRevealingName(const QString &filepath) { ); } -[[nodiscard]] QImage ReadImage( - const QString &path, - const QByteArray &content, - bool gzipSvg) { - return Images::Read({ - .path = path, - .content = content, - .maxSize = QSize(kMaxWallpaperSize, kMaxWallpaperSize), - .gzipSvg = gzipSvg, - }).image; -} - -base::binary_guard ReadImageAsync( +base::binary_guard ReadBackgroundImageAsync( not_null media, FnMut postprocess, FnMut done) { @@ -201,7 +188,7 @@ base::binary_guard ReadImageAsync( guard = result.make_guard(), callback = std::move(done) ]() mutable { - auto image = ReadImage(path, bytes, gzipSvg); + auto image = Ui::ReadBackgroundImage(path, bytes, gzipSvg); if (postprocess) { image = postprocess(std::move(image)); } diff --git a/Telegram/SourceFiles/data/data_document_resolver.h b/Telegram/SourceFiles/data/data_document_resolver.h index b1cf745a4..5dd29c0ba 100644 --- a/Telegram/SourceFiles/data/data_document_resolver.h +++ b/Telegram/SourceFiles/data/data_document_resolver.h @@ -24,7 +24,7 @@ class DocumentMedia; // [[nodiscard]] bool IsValidMediaFile(const QString &filepath); [[nodiscard]] bool IsExecutableName(const QString &filepath); [[nodiscard]] bool IsIpRevealingName(const QString &filepath); -base::binary_guard ReadImageAsync( +base::binary_guard ReadBackgroundImageAsync( not_null media, FnMut postprocess, FnMut done); diff --git a/Telegram/SourceFiles/data/data_wall_paper.cpp b/Telegram/SourceFiles/data/data_wall_paper.cpp index 55082b0d2..0d8371d0e 100644 --- a/Telegram/SourceFiles/data/data_wall_paper.cpp +++ b/Telegram/SourceFiles/data/data_wall_paper.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_file_origin.h" #include "data/data_session.h" #include "storage/serialize_common.h" +#include "ui/chat/chat_theme.h" #include "core/application.h" #include "main/main_session.h" @@ -45,10 +46,6 @@ constexpr auto kVersion = 1; return color ? SerializeColor(*color) : quint32(-1); } -[[nodiscard]] QColor DefaultBackgroundColor() { - return QColor(213, 223, 233); -} - [[nodiscard]] std::vector ColorsFromMTP( const MTPDwallPaperSettings &data) { auto result = std::vector(); @@ -683,89 +680,8 @@ bool IsCloudWallPaper(const WallPaper &paper) { && !details::IsTestingEditorWallPaper(paper); } -QImage GenerateWallPaper( - QSize size, - const std::vector &bg, - int gradientRotation, - float64 patternOpacity, - Fn drawPattern) { - auto result = bg.empty() - ? Images::GenerateGradient(size, { DefaultBackgroundColor() }) - : Images::GenerateGradient(size, bg, gradientRotation); - if (bg.size() > 1 && (!drawPattern || patternOpacity >= 0.)) { - result = Images::DitherImage(std::move(result)); - } - if (drawPattern) { - auto p = QPainter(&result); - if (patternOpacity >= 0.) { - p.setCompositionMode(QPainter::CompositionMode_SoftLight); - p.setOpacity(patternOpacity); - } else { - p.setCompositionMode(QPainter::CompositionMode_DestinationIn); - } - drawPattern(p); - if (patternOpacity < 0. && patternOpacity > -1.) { - p.setCompositionMode(QPainter::CompositionMode_SourceOver); - p.setOpacity(1. + patternOpacity); - p.fillRect(QRect{ QPoint(), size }, Qt::black); - } - } - - return std::move(result).convertToFormat( - QImage::Format_ARGB32_Premultiplied); -} - -QImage PreparePatternImage( - QImage pattern, - const std::vector &bg, - int gradientRotation, - float64 patternOpacity) { - auto result = GenerateWallPaper( - pattern.size(), - bg, - gradientRotation, - patternOpacity, - [&](QPainter &p) { - p.drawImage(QRect(QPoint(), pattern.size()), pattern); - }); - - pattern = QImage(); - return result; -} - -QImage PrepareBlurredBackground(QImage image) { - constexpr auto kSize = 900; - constexpr auto kRadius = 24; - if (image.width() > kSize || image.height() > kSize) { - image = image.scaled( - kSize, - kSize, - Qt::KeepAspectRatio, - Qt::SmoothTransformation); - } - return Images::BlurLargeImage(image, kRadius); -} - -QImage GenerateDitheredGradient( - const std::vector &colors, - int rotation) { - constexpr auto kSize = 512; - const auto size = QSize(kSize, kSize); - if (colors.empty()) { - return Images::GenerateGradient(size, { DefaultBackgroundColor() }); - } - auto result = Images::GenerateGradient(size, colors, rotation); - if (colors.size() > 1) { - result = Images::DitherImage(std::move(result)); - } - return result; -} - QImage GenerateDitheredGradient(const Data::WallPaper &paper) { - if (paper.backgroundColors().empty()) { - return GenerateDitheredGradient({ DefaultBackgroundColor() }, 0); - } - return GenerateDitheredGradient( + return Ui::GenerateDitheredGradient( paper.backgroundColors(), paper.gradientRotation()); } diff --git a/Telegram/SourceFiles/data/data_wall_paper.h b/Telegram/SourceFiles/data/data_wall_paper.h index 795b88fdc..40893bf70 100644 --- a/Telegram/SourceFiles/data/data_wall_paper.h +++ b/Telegram/SourceFiles/data/data_wall_paper.h @@ -121,21 +121,6 @@ private: [[nodiscard]] bool IsDefaultWallPaper(const WallPaper &paper); [[nodiscard]] bool IsCloudWallPaper(const WallPaper &paper); -[[nodiscard]] QImage GenerateWallPaper( - QSize size, - const std::vector &bg, - int gradientRotation, - float64 patternOpacity = 1., - Fn drawPattern = nullptr); -[[nodiscard]] QImage PreparePatternImage( - QImage pattern, - const std::vector &bg, - int gradientRotation, - float64 patternOpacity); -[[nodiscard]] QImage PrepareBlurredBackground(QImage image); -[[nodiscard]] QImage GenerateDitheredGradient( - const std::vector &colors, - int rotation); [[nodiscard]] QImage GenerateDitheredGradient(const WallPaper &paper); [[nodiscard]] QColor ColorFromSerialized(quint32 serialized); diff --git a/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp index 5cb50e5a9..d41b67cf6 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp @@ -279,7 +279,7 @@ void ThemeDocument::validateThumbnail() const { } void ThemeDocument::generateThumbnail() const { - _thumbnail = Ui::PixmapFromImage(Data::GenerateWallPaper( + _thumbnail = Ui::PixmapFromImage(Ui::GenerateBackgroundImage( QSize(_pixw, _pixh) * cIntRetinaFactor(), _background, _gradientRotation, @@ -316,7 +316,7 @@ void ThemeDocument::prepareThumbnailFrom( _pixw, _pixh); if (isPattern) { - original = Data::PreparePatternImage( + original = Ui::PreparePatternImage( std::move(original), _background, _gradientRotation, diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 942a30fcb..99c42e76f 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_histories.h" #include "data/stickers/data_stickers.h" #include "api/api_text_entities.h" +#include "ui/chat/chat_theme.h" #include "ui/special_buttons.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" @@ -1183,9 +1184,9 @@ void MainWidget::checkChatBackground() { : background->data; setReadyChatBackground(ready, std::move(image)); }; - _background->generating = Data::ReadImageAsync( + _background->generating = Data::ReadBackgroundImageAsync( media.get(), - Window::Theme::PreprocessBackgroundImage, + Ui::PreprocessBackgroundImage, generateCallback); } diff --git a/Telegram/SourceFiles/ui/chat/chat_theme.cpp b/Telegram/SourceFiles/ui/chat/chat_theme.cpp index 170fd817f..1fa03f2e0 100644 --- a/Telegram/SourceFiles/ui/chat/chat_theme.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_theme.cpp @@ -21,6 +21,12 @@ constexpr auto kMaxChatEntryHistorySize = 50; constexpr auto kCacheBackgroundTimeout = 3 * crl::time(1000); constexpr auto kCacheBackgroundFastTimeout = crl::time(200); constexpr auto kBackgroundFadeDuration = crl::time(200); +constexpr auto kMinimumTiledSize = 512; +constexpr auto kMaxSize = 2960; + +[[nodiscard]] QColor DefaultBackgroundColor() { + return QColor(213, 223, 233); +} [[nodiscard]] CacheBackgroundResult CacheBackground( const CacheBackgroundRequest &request) { @@ -141,7 +147,7 @@ bool operator!=( } CachedBackground::CachedBackground(CacheBackgroundResult &&result) -: pixmap(Ui::PixmapFromImage(std::move(result.image))) +: pixmap(PixmapFromImage(std::move(result.image))) , area(result.area) , x(result.x) , y(result.y) { @@ -169,6 +175,18 @@ void ChatTheme::setBackground(ChatThemeBackground &&background) { _repaintBackgroundRequests.fire({}); } +void ChatTheme::updateBackgroundImageFrom(ChatThemeBackground &&background) { + _mutableBackground.prepared = std::move(background.prepared); + _mutableBackground.preparedForTiled = std::move( + background.preparedForTiled); + if (!_backgroundState.now.pixmap.isNull()) { + if (_cacheBackgroundTimer) { + _cacheBackgroundTimer->cancel(); + } + cacheBackgroundNow(); + } +} + uint64 ChatTheme::key() const { return _id; } @@ -184,7 +202,7 @@ void ChatTheme::setBubblesBackground(QImage image) { }); } if (!_bubblesBackgroundPattern) { - _bubblesBackgroundPattern = Ui::PrepareBubblePattern(); + _bubblesBackgroundPattern = PrepareBubblePattern(); } _bubblesBackgroundPattern->pixmap = _bubblesBackground.pixmap; _repaintBackgroundRequests.fire({}); @@ -427,4 +445,244 @@ ChatBackgroundRects ComputeChatBackgroundRects( } } +QColor CountAverageColor(const QImage &image) { + Expects(image.format() == QImage::Format_ARGB32_Premultiplied + || image.format() == QImage::Format_RGB32); + + uint64 components[3] = { 0 }; + const auto w = image.width(); + const auto h = image.height(); + const auto size = w * h; + if (const auto pix = image.constBits()) { + for (auto i = 0, l = size * 4; i != l; i += 4) { + components[2] += pix[i + 0]; + components[1] += pix[i + 1]; + components[0] += pix[i + 2]; + } + } + if (size) { + for (auto i = 0; i != 3; ++i) { + components[i] /= size; + } + } + return QColor(components[0], components[1], components[2]); +} + +QColor ThemeAdjustedColor(QColor original, QColor background) { + return QColor::fromHslF( + background.hslHueF(), + background.hslSaturationF(), + original.lightnessF(), + original.alphaF() + ).toRgb(); +} + +QImage PreprocessBackgroundImage(QImage image) { + if (image.isNull()) { + return image; + } + if (image.format() != QImage::Format_ARGB32_Premultiplied) { + image = std::move(image).convertToFormat( + QImage::Format_ARGB32_Premultiplied); + } + if (image.width() > 40 * image.height()) { + const auto width = 40 * image.height(); + const auto height = image.height(); + image = image.copy((image.width() - width) / 2, 0, width, height); + } else if (image.height() > 40 * image.width()) { + const auto width = image.width(); + const auto height = 40 * image.width(); + image = image.copy(0, (image.height() - height) / 2, width, height); + } + if (image.width() > kMaxSize || image.height() > kMaxSize) { + image = image.scaled( + kMaxSize, + kMaxSize, + Qt::KeepAspectRatio, + Qt::SmoothTransformation); + } + return image; +} + +std::optional CalculateImageMonoColor(const QImage &image) { + Expects(image.bytesPerLine() == 4 * image.width()); + + if (image.isNull()) { + return std::nullopt; + } + const auto bits = reinterpret_cast(image.constBits()); + const auto first = bits[0]; + for (auto i = 0; i < image.width() * image.height(); i++) { + if (first != bits[i]) { + return std::nullopt; + } + } + return image.pixelColor(QPoint()); +} + +QImage PrepareImageForTiled(const QImage &prepared) { + const auto width = prepared.width(); + const auto height = prepared.height(); + const auto isSmallForTiled = (width > 0 && height > 0) + && (width < kMinimumTiledSize || height < kMinimumTiledSize); + if (!isSmallForTiled) { + return prepared; + } + const auto repeatTimesX = (kMinimumTiledSize + width - 1) / width; + const auto repeatTimesY = (kMinimumTiledSize + height - 1) / height; + auto result = QImage( + width * repeatTimesX, + height * repeatTimesY, + QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(prepared.devicePixelRatio()); + auto imageForTiledBytes = result.bits(); + auto bytesInLine = width * sizeof(uint32); + for (auto timesY = 0; timesY != repeatTimesY; ++timesY) { + auto imageBytes = prepared.constBits(); + for (auto y = 0; y != height; ++y) { + for (auto timesX = 0; timesX != repeatTimesX; ++timesX) { + memcpy(imageForTiledBytes, imageBytes, bytesInLine); + imageForTiledBytes += bytesInLine; + } + imageBytes += prepared.bytesPerLine(); + imageForTiledBytes += result.bytesPerLine() - (repeatTimesX * bytesInLine); + } + } + return result; +} + +[[nodiscard]] QImage ReadBackgroundImage( + const QString &path, + const QByteArray &content, + bool gzipSvg) { + return Images::Read({ + .path = path, + .content = content, + .maxSize = QSize(kMaxSize, kMaxSize), + .gzipSvg = gzipSvg, + }).image; +} + +QImage GenerateBackgroundImage( + QSize size, + const std::vector &bg, + int gradientRotation, + float64 patternOpacity, + Fn drawPattern) { + auto result = bg.empty() + ? Images::GenerateGradient(size, { DefaultBackgroundColor() }) + : Images::GenerateGradient(size, bg, gradientRotation); + if (bg.size() > 1 && (!drawPattern || patternOpacity >= 0.)) { + result = Images::DitherImage(std::move(result)); + } + if (drawPattern) { + auto p = QPainter(&result); + if (patternOpacity >= 0.) { + p.setCompositionMode(QPainter::CompositionMode_SoftLight); + p.setOpacity(patternOpacity); + } else { + p.setCompositionMode(QPainter::CompositionMode_DestinationIn); + } + drawPattern(p); + if (patternOpacity < 0. && patternOpacity > -1.) { + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + p.setOpacity(1. + patternOpacity); + p.fillRect(QRect{ QPoint(), size }, Qt::black); + } + } + + return std::move(result).convertToFormat( + QImage::Format_ARGB32_Premultiplied); +} + +QImage PreparePatternImage( + QImage pattern, + const std::vector &bg, + int gradientRotation, + float64 patternOpacity) { + auto result = GenerateBackgroundImage( + pattern.size(), + bg, + gradientRotation, + patternOpacity, + [&](QPainter &p) { + p.drawImage(QRect(QPoint(), pattern.size()), pattern); + }); + + pattern = QImage(); + return result; +} + +QImage PrepareBlurredBackground(QImage image) { + constexpr auto kSize = 900; + constexpr auto kRadius = 24; + if (image.width() > kSize || image.height() > kSize) { + image = image.scaled( + kSize, + kSize, + Qt::KeepAspectRatio, + Qt::SmoothTransformation); + } + return Images::BlurLargeImage(image, kRadius); +} + +QImage GenerateDitheredGradient( + const std::vector &colors, + int rotation) { + constexpr auto kSize = 512; + const auto size = QSize(kSize, kSize); + if (colors.empty()) { + return Images::GenerateGradient(size, { DefaultBackgroundColor() }); + } + auto result = Images::GenerateGradient(size, colors, rotation); + if (colors.size() > 1) { + result = Images::DitherImage(std::move(result)); + } + return result; +} + +ChatThemeBackground PrepareBackgroundImage( + const QString &path, + const QByteArray &bytes, + bool gzipSvg, + const std::vector &colors, + bool isPattern, + float64 patternOpacity, + bool isBlurred) { + auto prepared = (isPattern || colors.empty()) + ? PreprocessBackgroundImage(ReadBackgroundImage(path, bytes, gzipSvg)) + : QImage(); + if (isPattern && !prepared.isNull()) { + if (colors.size() < 2) { + const auto gradientRotation = 0; // No gradient here. + prepared = PreparePatternImage( + std::move(prepared), + colors, + gradientRotation, + patternOpacity); + } + prepared.setDevicePixelRatio(style::DevicePixelRatio()); + } else if (colors.empty()) { + prepared.setDevicePixelRatio(style::DevicePixelRatio()); + } + const auto imageMonoColor = (colors.size() < 2) + ? CalculateImageMonoColor(prepared) + : std::nullopt; + if (!prepared.isNull() && !isPattern && isBlurred) { + prepared = PrepareBlurredBackground(std::move(prepared)); + } + return ChatThemeBackground{ + .prepared = prepared, + .preparedForTiled = PrepareImageForTiled(prepared), + .colorForFill = (!prepared.isNull() + ? imageMonoColor + : (colors.size() > 1 || colors.empty()) + ? std::nullopt + : std::make_optional(colors.front())), + .colors = colors, + .patternOpacity = patternOpacity, + .isPattern = isPattern, + }; +} + } // namespace Window::Theme diff --git a/Telegram/SourceFiles/ui/chat/chat_theme.h b/Telegram/SourceFiles/ui/chat/chat_theme.h index 22c0e88ff..3f91bfefb 100644 --- a/Telegram/SourceFiles/ui/chat/chat_theme.h +++ b/Telegram/SourceFiles/ui/chat/chat_theme.h @@ -117,6 +117,7 @@ public: } void setBackground(ChatThemeBackground &&background); + void updateBackgroundImageFrom(ChatThemeBackground &&background); const ChatThemeBackground &background() const { return _mutableBackground; } @@ -175,4 +176,40 @@ struct ChatBackgroundRects { QSize fillSize, QSize imageSize); +[[nodiscard]] QColor CountAverageColor(const QImage &image); +[[nodiscard]] QColor ThemeAdjustedColor(QColor original, QColor background); +[[nodiscard]] QImage PreprocessBackgroundImage(QImage image); +[[nodiscard]] std::optional CalculateImageMonoColor( + const QImage &image); +[[nodiscard]] QImage PrepareImageForTiled(const QImage &prepared); + +[[nodiscard]] QImage ReadBackgroundImage( + const QString &path, + const QByteArray &content, + bool gzipSvg); +[[nodiscard]] QImage GenerateBackgroundImage( + QSize size, + const std::vector &bg, + int gradientRotation, + float64 patternOpacity = 1., + Fn drawPattern = nullptr); +[[nodiscard]] QImage PreparePatternImage( + QImage pattern, + const std::vector &bg, + int gradientRotation, + float64 patternOpacity); +[[nodiscard]] QImage PrepareBlurredBackground(QImage image); +[[nodiscard]] QImage GenerateDitheredGradient( + const std::vector &colors, + int rotation); + +[[nodiscard]] ChatThemeBackground PrepareBackgroundImage( + const QString &path, + const QByteArray &bytes, + bool gzipSvg, + const std::vector &colors, + bool isPattern, + float64 patternOpacity, + bool isBlurred); + } // namespace Ui diff --git a/Telegram/SourceFiles/window/themes/window_theme.cpp b/Telegram/SourceFiles/window/themes/window_theme.cpp index 32dc2c081..94fa6e561 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme.cpp @@ -24,8 +24,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "base/crc32hash.h" #include "data/data_session.h" +#include "data/data_document_resolver.h" #include "main/main_account.h" // Account::local. #include "main/main_domain.h" // Domain::activeSessionValue. +#include "ui/chat/chat_theme.h" #include "ui/image/image.h" #include "ui/ui_utility.h" #include "boxes/confirm_box.h" @@ -43,7 +45,6 @@ namespace { constexpr auto kThemeFileSizeLimit = 5 * 1024 * 1024; constexpr auto kBackgroundSizeLimit = 25 * 1024 * 1024; constexpr auto kNightThemeFile = ":/gui/night.tdesktop-theme"_cs; -constexpr auto kMinimumTiledSize = 512; struct Applying { Saved data; @@ -58,20 +59,6 @@ inline bool AreTestingTheme() { return !GlobalApplying.paletteForRevert.isEmpty(); } -std::optional CalculateImageMonoColor(const QImage &image) { - if (image.isNull()) { - return std::nullopt; - } - const auto bits = reinterpret_cast(image.constBits()); - const auto first = bits[0]; - for (auto i = 0; i < image.width() * image.height(); i++) { - if (first != bits[i]) { - return std::nullopt; - } - } - return image.pixelColor(QPoint()); -} - [[nodiscard]] bool GoodImageFormatAndSize(const QImage &image) { return !image.size().isEmpty() && (image.format() == QImage::Format_ARGB32_Premultiplied @@ -646,7 +633,7 @@ QImage ChatBackground::postprocessBackgroundImage(QImage image) { } void ChatBackground::set(const Data::WallPaper &paper, QImage image) { - image = PreprocessBackgroundImage(std::move(image)); + image = Ui::PreprocessBackgroundImage(std::move(image)); const auto needResetAdjustable = Data::IsDefaultWallPaper(paper) && !Data::IsDefaultWallPaper(_paper) @@ -701,7 +688,7 @@ void ChatBackground::set(const Data::WallPaper &paper, QImage image) { if (_paper.isPattern() && !image.isNull()) { if (bgColors.size() < 2) { auto prepared = postprocessBackgroundImage( - Data::PreparePatternImage( + Ui::PreparePatternImage( image, bgColors, _paper.gradientRotation(), @@ -752,7 +739,7 @@ void ChatBackground::setPrepared( Expects(gradient.isNull() || GoodImageFormatAndSize(gradient)); if (!prepared.isNull() && !_paper.isPattern() && _paper.isBlurred()) { - prepared = Data::PrepareBlurredBackground(std::move(prepared)); + prepared = Ui::PrepareBlurredBackground(std::move(prepared)); } if (adjustPaletteRequired()) { if (!gradient.isNull()) { @@ -768,40 +755,9 @@ void ChatBackground::setPrepared( _prepared = std::move(prepared); _gradient = std::move(gradient); _imageMonoColor = _gradient.isNull() - ? CalculateImageMonoColor(_prepared) + ? Ui::CalculateImageMonoColor(_prepared) : std::nullopt; - prepareImageForTiled(); -} - -void ChatBackground::prepareImageForTiled() { - const auto width = _prepared.width(); - const auto height = _prepared.height(); - const auto isSmallForTiled = (width > 0 && height > 0) - && (width < kMinimumTiledSize || height < kMinimumTiledSize); - if (!isSmallForTiled) { - _preparedForTiled = _prepared; - return; - } - const auto repeatTimesX = qCeil(kMinimumTiledSize / (1. * width)); - const auto repeatTimesY = qCeil(kMinimumTiledSize / (1. * height)); - _preparedForTiled = QImage( - width * repeatTimesX, - height * repeatTimesY, - QImage::Format_ARGB32_Premultiplied); - _preparedForTiled.setDevicePixelRatio(_prepared.devicePixelRatio()); - auto imageForTiledBytes = _preparedForTiled.bits(); - auto bytesInLine = width * sizeof(uint32); - for (auto timesY = 0; timesY != repeatTimesY; ++timesY) { - auto imageBytes = _prepared.constBits(); - for (auto y = 0; y != height; ++y) { - for (auto timesX = 0; timesX != repeatTimesX; ++timesX) { - memcpy(imageForTiledBytes, imageBytes, bytesInLine); - imageForTiledBytes += bytesInLine; - } - imageBytes += _prepared.bytesPerLine(); - imageForTiledBytes += _preparedForTiled.bytesPerLine() - (repeatTimesX * bytesInLine); - } - } + _preparedForTiled = Ui::PrepareImageForTiled(_prepared); } void ChatBackground::setPaper(const Data::WallPaper &paper) { @@ -850,13 +806,13 @@ void ChatBackground::clearEditingTheme(ClearEditing clear) { } void ChatBackground::adjustPaletteUsingBackground(const QImage &image) { - adjustPaletteUsingColor(CountAverageColor(image)); + adjustPaletteUsingColor(Ui::CountAverageColor(image)); } void ChatBackground::adjustPaletteUsingColor(QColor color) { const auto prepared = color.toHsl(); for (const auto &adjustable : _adjustableColors) { - const auto adjusted = AdjustedColor(adjustable.item->c, prepared); + const auto adjusted = Ui::ThemeAdjustedColor(adjustable.item->c, prepared); adjustable.item.set( adjusted.red(), adjusted.green(), @@ -885,10 +841,7 @@ void ChatBackground::recacheGradientForFill(QImage gradient) { QImage ChatBackground::createCurrentImage() const { if (const auto fill = colorForFill()) { - auto result = QImage( - kMinimumTiledSize, - kMinimumTiledSize, - QImage::Format_ARGB32_Premultiplied); + auto result = QImage(512, 512, QImage::Format_ARGB32_Premultiplied); result.fill(*fill); return result; } else if (_gradient.isNull()) { @@ -1477,64 +1430,6 @@ QString EditingPalettePath() { return cWorkingDir() + "tdata/editing-theme.tdesktop-palette"; } -QColor CountAverageColor(const QImage &image) { - Expects(image.format() == QImage::Format_ARGB32_Premultiplied - || image.format() == QImage::Format_RGB32); - - uint64 components[3] = { 0 }; - const auto w = image.width(); - const auto h = image.height(); - const auto size = w * h; - if (const auto pix = image.constBits()) { - for (auto i = 0, l = size * 4; i != l; i += 4) { - components[2] += pix[i + 0]; - components[1] += pix[i + 1]; - components[0] += pix[i + 2]; - } - } - if (size) { - for (auto i = 0; i != 3; ++i) { - components[i] /= size; - } - } - return QColor(components[0], components[1], components[2]); -} - -QColor AdjustedColor(QColor original, QColor background) { - return QColor::fromHslF( - background.hslHueF(), - background.hslSaturationF(), - original.lightnessF(), - original.alphaF() - ).toRgb(); -} - -QImage PreprocessBackgroundImage(QImage image) { - constexpr auto kMaxSize = 2960; - - if (image.format() != QImage::Format_ARGB32_Premultiplied) { - image = std::move(image).convertToFormat( - QImage::Format_ARGB32_Premultiplied); - } - if (image.width() > 40 * image.height()) { - const auto width = 40 * image.height(); - const auto height = image.height(); - image = image.copy((image.width() - width) / 2, 0, width, height); - } else if (image.height() > 40 * image.width()) { - const auto width = image.width(); - const auto height = 40 * image.width(); - image = image.copy(0, (image.height() - height) / 2, width, height); - } - if (image.width() > kMaxSize || image.height() > kMaxSize) { - image = image.scaled( - kMaxSize, - kMaxSize, - Qt::KeepAspectRatio, - Qt::SmoothTransformation); - } - return image; -} - bool ReadPaletteValues(const QByteArray &content, Fn callback) { if (content.size() > kThemeSchemeSizeLimit) { LOG(("Theme Error: color scheme file too large (should be less than 1 MB, got %2)").arg(content.size())); diff --git a/Telegram/SourceFiles/window/themes/window_theme.h b/Telegram/SourceFiles/window/themes/window_theme.h index 40dfaa650..0edb918b0 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.h +++ b/Telegram/SourceFiles/window/themes/window_theme.h @@ -18,6 +18,10 @@ namespace Window { class Controller; } // namespace Window +namespace Ui { +struct ChatThemeBackground; +} // namespace Ui + namespace Window { namespace Theme { @@ -107,9 +111,6 @@ bool LoadFromContent( const QByteArray &content, not_null out, Cached *outCache); -[[nodiscard]] QColor CountAverageColor(const QImage &image); -[[nodiscard]] QColor AdjustedColor(QColor original, QColor background); -[[nodiscard]] QImage PreprocessBackgroundImage(QImage image); struct BackgroundUpdate { enum class Type { @@ -280,7 +281,9 @@ private: [[nodiscard]] ChatBackground *Background(); -bool ReadPaletteValues(const QByteArray &content, Fn callback); +bool ReadPaletteValues( + const QByteArray &content, + Fn callback); } // namespace Theme } // namespace Window diff --git a/Telegram/SourceFiles/window/themes/window_themes_cloud_list.cpp b/Telegram/SourceFiles/window/themes/window_themes_cloud_list.cpp index 896e82e9e..90251c299 100644 --- a/Telegram/SourceFiles/window/themes/window_themes_cloud_list.cpp +++ b/Telegram/SourceFiles/window/themes/window_themes_cloud_list.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document.h" #include "data/data_document_media.h" #include "data/data_session.h" +#include "ui/chat/chat_theme.h" #include "ui/image/image_prepare.h" #include "ui/widgets/popup_menu.h" #include "ui/toast/toast.h" @@ -185,7 +186,7 @@ void CloudListCheck::ensureContrast() { QImage::Format_ARGB32_Premultiplied); const auto active = style::internal::EnsureContrast( _colors->radiobuttonActive, - CountAverageColor(image)); + Ui::CountAverageColor(image)); _colors->radiobuttonInactive = _colors->radiobuttonActive = QColor( active.red(), active.green(), diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index ee5daedce..49bf80140 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -996,7 +996,7 @@ void MainMenu::refreshBackground() { prepared.size()); auto backgroundImage = paper.isPattern() - ? Data::GenerateWallPaper( + ? Ui::GenerateBackgroundImage( fill * cIntRetinaFactor(), paper.backgroundColors(), paper.gradientRotation(), diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index d3ab9406d..b18212eb3 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -28,6 +28,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_user.h" +#include "data/data_document.h" +#include "data/data_document_media.h" +#include "data/data_document_resolver.h" #include "data/data_changes.h" #include "data/data_group_call.h" #include "data/data_chat_filters.h" @@ -71,6 +74,34 @@ namespace { constexpr auto kMaxChatEntryHistorySize = 50; +[[nodiscard]] Fn PreparePaletteCallback( + bool dark, + std::optional accent) { + return [=](style::palette &palette) { + using namespace Theme; + if (dark) { + const auto &embedded = EmbeddedThemes(); + const auto i = ranges::find( + embedded, + EmbeddedType::Night, + &EmbeddedScheme::type); + Assert(i != end(embedded)); + + auto instance = Instance(); + const auto loaded = LoadFromFile( + i->path, + &instance, + nullptr, + nullptr, + accent ? ColorizerFrom(*i, *accent) : Colorizer()); + Assert(loaded); + palette = instance.palette; + } else { + // #TODO themes apply accent color to classic theme + } + }; +} + } // namespace void ActivateWindow(not_null controller) { @@ -458,6 +489,13 @@ void SessionNavigation::showPollResults( showSection(std::make_shared(poll, contextId), params); } +struct SessionController::CachedTheme { + std::shared_ptr theme; + std::shared_ptr media; + Data::WallPaper paper; + rpl::lifetime lifetime; +}; + SessionController::SessionController( not_null session, not_null window) @@ -1325,8 +1363,8 @@ auto SessionController::cachedChatThemeValue( return rpl::single(_defaultChatTheme); } const auto i = _customChatThemes.find(key); - if (i != end(_customChatThemes) && i->second) { - return rpl::single(i->second); + if (i != end(_customChatThemes) && i->second.theme) { + return rpl::single(i->second.theme); } if (i == end(_customChatThemes)) { cacheChatTheme(data); @@ -1358,57 +1396,119 @@ void SessionController::pushDefaultChatBackground() { void SessionController::cacheChatTheme(const Data::CloudTheme &data) { Expects(data.id != 0); - - using namespace Theme; + Expects(data.paper.has_value()); + Expects(!data.paper->backgroundColors().empty()); const auto key = data.id; - const auto dark = data.basedOnDark; - const auto accent = data.accentColor; - if (data.paper) { - const auto document = data.paper->document(); - - } - const auto preparePalette = [=](style::palette &palette) { - if (dark) { - const auto &embedded = Theme::EmbeddedThemes(); - const auto i = ranges::find( - embedded, - EmbeddedType::Night, - &EmbeddedScheme::type); - Assert(i != end(embedded)); - - auto instance = Instance(); - const auto loaded = LoadFromFile( - i->path, - &instance, - nullptr, - nullptr, - accent ? ColorizerFrom(*i, *accent) : Colorizer()); - Assert(loaded); - palette = instance.palette; - } else { - // #TODO themes apply accent color to classic theme - } - }; - const auto prepareBackground = [] { - return Ui::ChatThemeBackground{ - - }; - }; + const auto document = data.paper->document(); + const auto media = document ? document->createMediaView() : nullptr; + data.paper->loadDocument(); + auto &theme = _customChatThemes.emplace( + key, + CachedTheme{ .media = media, .paper = *data.paper }).first->second; auto descriptor = Ui::ChatThemeDescriptor{ .id = key, - .preparePalette = preparePalette, - .prepareBackground = prepareBackground, + .preparePalette = PreparePaletteCallback( + data.basedOnDark, + data.accentColor), + .prepareBackground = backgroundGenerator(theme), }; - crl::async([this, descriptor = std::move(descriptor), weak = base::make_weak(this)] { - crl::on_main(weak, [ + crl::async([ + this, + descriptor = std::move(descriptor), + weak = base::make_weak(this) + ]{ + crl::on_main(weak,[ this, result = std::make_shared(std::move(descriptor)) ]() mutable { - _customChatThemes.emplace(result->key(), result); - _cachedThemesStream.fire(std::move(result)); + cacheChatThemeDone(std::move(result)); }); }); + if (media && media->loaded(true)) { + theme.media = nullptr; + } +} + +void SessionController::cacheChatThemeDone( + std::shared_ptr result) { + Expects(result != nullptr); + + const auto key = result->key(); + const auto i = _customChatThemes.find(key); + if (i == end(_customChatThemes)) { + return; + } + i->second.theme = result; + if (i->second.media) { + if (i->second.media->loaded(true)) { + updateCustomThemeBackground(i->second); + } else { + session().downloaderTaskFinished( + ) | rpl::filter([=] { + const auto i = _customChatThemes.find(key); + Assert(i != end(_customChatThemes)); + return !i->second.media || i->second.media->loaded(true); + }) | rpl::start_with_next([=] { + const auto i = _customChatThemes.find(key); + Assert(i != end(_customChatThemes)); + updateCustomThemeBackground(i->second); + }, i->second.lifetime); + } + } + _cachedThemesStream.fire(std::move(result)); +} + +void SessionController::updateCustomThemeBackground(CachedTheme &theme) { + const auto guard = gsl::finally([&] { + theme.lifetime.destroy(); + theme.media = nullptr; + }); + if (!theme.media || !theme.theme || !theme.media->loaded(true)) { + return; + } + const auto key = theme.theme->key(); + const auto weak = base::make_weak(this); + crl::async([=, generator = backgroundGenerator(theme, false)] { + crl::on_main(weak, [=, result = generator()]() mutable { + const auto i = _customChatThemes.find(key); + if (i != end(_customChatThemes)) { + i->second.theme->updateBackgroundImageFrom(std::move(result)); + } + }); + }); +} + +Fn SessionController::backgroundGenerator( + CachedTheme &theme, + bool generateGradient) { + const auto &paper = theme.paper; + const auto &media = theme.media; + const auto paperPath = media ? media->owner()->filepath() : QString(); + const auto paperBytes = media ? media->bytes() : QByteArray(); + const auto gzipSvg = media && media->owner()->isPatternWallPaperSVG(); + const auto &colors = paper.backgroundColors(); + const auto isPattern = paper.isPattern(); + const auto patternOpacity = paper.patternOpacity(); + const auto isBlurred = paper.isBlurred(); + const auto gradientRotation = paper.gradientRotation(); + return [=] { + auto result = Ui::PrepareBackgroundImage( + paperPath, + paperBytes, + gzipSvg, + colors, + isPattern, + patternOpacity, + isBlurred); + if (generateGradient) { + result.gradientForFill = (colors.size() > 1) + ? Ui::GenerateDitheredGradient(colors, gradientRotation) + : QImage(); + result.gradientRotation = gradientRotation; + } + return result; + }; } HistoryView::PaintContext SessionController::preparePaintContext( diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index abb598e60..082e880b7 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -47,6 +47,7 @@ class LayerWidget; enum class ReportReason; class ChatTheme; struct ChatPaintContext; +struct ChatThemeBackground; } // namespace Ui namespace Data { @@ -419,6 +420,8 @@ public: } private: + struct CachedTheme; + void init(); void initSupportMode(); void refreshFiltersMenu(); @@ -445,6 +448,11 @@ private: void pushDefaultChatBackground(); void cacheChatTheme(const Data::CloudTheme &data); + void cacheChatThemeDone(std::shared_ptr result); + void updateCustomThemeBackground(CachedTheme &theme); + [[nodiscard]] Fn backgroundGenerator( + CachedTheme &theme, + bool generateGradient = true); const not_null _window; @@ -474,7 +482,7 @@ private: rpl::event_stream<> _filtersMenuChanged; std::shared_ptr _defaultChatTheme; - base::flat_map> _customChatThemes; + base::flat_map _customChatThemes; rpl::event_stream> _cachedThemesStream; rpl::lifetime _lifetime;