From c2b1187948ffda9e9edfcc3f3ecbde1f4c8a400a Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 12 Aug 2021 17:51:44 +0300 Subject: [PATCH] Start support of linear gradient wallpapers. --- Telegram/SourceFiles/boxes/background_box.cpp | 8 +- .../boxes/background_preview_box.cpp | 62 ++++--- .../boxes/background_preview_box.h | 4 +- .../SourceFiles/core/local_url_handlers.cpp | 13 +- Telegram/SourceFiles/data/data_wall_paper.cpp | 161 ++++++++++-------- Telegram/SourceFiles/data/data_wall_paper.h | 17 +- .../media/history_view_theme_document.cpp | 10 +- .../view/media/history_view_theme_document.h | 5 +- Telegram/SourceFiles/mainwidget.cpp | 61 +++++-- .../SourceFiles/settings/settings_chat.cpp | 56 +++--- .../SourceFiles/window/section_widget.cpp | 57 ++++--- .../window/themes/window_theme.cpp | 139 ++++++++++----- .../SourceFiles/window/themes/window_theme.h | 10 +- .../SourceFiles/window/window_main_menu.cpp | 3 +- 14 files changed, 379 insertions(+), 227 deletions(-) diff --git a/Telegram/SourceFiles/boxes/background_box.cpp b/Telegram/SourceFiles/boxes/background_box.cpp index 29c624ece..a0cc65620 100644 --- a/Telegram/SourceFiles/boxes/background_box.cpp +++ b/Telegram/SourceFiles/boxes/background_box.cpp @@ -336,13 +336,11 @@ void BackgroundBox::Inner::validatePaperThumbnail( : paper.dataMedia->thumbnail(); auto original = thumbnail->original(); if (paper.data.isPattern()) { - // #TODO themes gradients - const auto color = *paper.data.backgroundColor(); original = Data::PreparePatternImage( std::move(original), - color, - Data::PatternColor(color), - paper.data.patternIntensity()); + paper.data.backgroundColors(), + paper.data.gradientRotation(), + paper.data.patternOpacity()); } paper.thumbnail = Ui::PixmapFromImage(TakeMiddleSample( original, diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp index 37e28da12..391272951 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp @@ -379,13 +379,17 @@ QImage ColorizePattern(QImage image, QColor color) { QImage PrepareScaledFromFull( const QImage &image, - std::optional patternBackground, + const std::vector &patternBackground, + int gradientRotation, + float64 patternOpacity, Images::Option blur = Images::Option(0)) { auto result = PrepareScaledNonPattern(image, blur); - if (patternBackground) { - result = ColorizePattern( + if (!patternBackground.empty()) { + result = Data::PreparePatternImage( std::move(result), - Data::PatternColor(*patternBackground)); + patternBackground, + gradientRotation, + patternOpacity); } return std::move(result).convertToFormat( QImage::Format_ARGB32_Premultiplied); @@ -433,7 +437,7 @@ void BackgroundPreviewBox::prepare() { if (_paper.hasShareUrl()) { addLeftButton(tr::lng_background_share(), [=] { share(); }); } - updateServiceBg(_paper.backgroundColor()); + updateServiceBg(_paper.backgroundColors()); _paper.loadDocument(); const auto document = _paper.document(); @@ -669,7 +673,9 @@ bool BackgroundPreviewBox::setScaledFromThumb() { } auto scaled = PrepareScaledFromFull( thumbnail->original(), - patternBackgroundColor(), + patternBackgroundColors(), + _paper.gradientRotation(), + _paper.patternOpacity(), _paper.document() ? Images::Option::Blurred : Images::Option(0)); auto blurred = (_paper.document() || _paper.isPattern()) ? QImage() @@ -683,7 +689,7 @@ bool BackgroundPreviewBox::setScaledFromThumb() { void BackgroundPreviewBox::setScaledFromImage( QImage &&image, QImage &&blurred) { - updateServiceBg(Window::Theme::CountAverageColor(image)); + updateServiceBg({ Window::Theme::CountAverageColor(image) }); if (!_full.isNull()) { startFadeInFrom(std::move(_scaled)); } @@ -710,16 +716,26 @@ void BackgroundPreviewBox::checkBlurAnimationStart() { startFadeInFrom(_paper.isBlurred() ? _scaled : _blurred); } -void BackgroundPreviewBox::updateServiceBg(std::optional background) { - if (background) { - _serviceBg = Window::Theme::AdjustedColor( - st::msgServiceBg->c, - *background); +void BackgroundPreviewBox::updateServiceBg(const std::vector &bg) { + const auto count = int(bg.size()); + if (!count) { + return; } + auto red = 0, green = 0, blue = 0; + for (const auto &color : bg) { + red += color.red(); + green += color.green(); + blue += color.blue(); + } + _serviceBg = Window::Theme::AdjustedColor( + st::msgServiceBg->c, + QColor(red / count, green / count, blue / count)); } -std::optional BackgroundPreviewBox::patternBackgroundColor() const { - return _paper.isPattern() ? _paper.backgroundColor() : std::nullopt; +std::vector BackgroundPreviewBox::patternBackgroundColors() const { + return _paper.isPattern() + ? _paper.backgroundColors() + : std::vector(); } void BackgroundPreviewBox::checkLoadedDocument() { @@ -737,15 +753,21 @@ void BackgroundPreviewBox::checkLoadedDocument() { crl::async([ this, image = std::move(image), - patternBackground = patternBackgroundColor(), + patternBackground = patternBackgroundColors(), + gradientRotation = _paper.gradientRotation(), + patternOpacity = _paper.patternOpacity(), guard = _generating.make_guard() ]() mutable { - auto scaled = PrepareScaledFromFull(image, patternBackground); - auto blurred = patternBackground - ? QImage() - : PrepareScaledNonPattern( + auto scaled = PrepareScaledFromFull( + image, + patternBackground, + gradientRotation, + patternOpacity); + auto blurred = patternBackground.empty() + ? PrepareScaledNonPattern( Data::PrepareBlurredBackground(image), - Images::Option(0)); + Images::Option(0)) + : QImage(); crl::on_main(std::move(guard), [ this, image = std::move(image), diff --git a/Telegram/SourceFiles/boxes/background_preview_box.h b/Telegram/SourceFiles/boxes/background_preview_box.h index e530f99bb..e0345afae 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.h +++ b/Telegram/SourceFiles/boxes/background_preview_box.h @@ -59,8 +59,8 @@ private: void checkLoadedDocument(); bool setScaledFromThumb(); void setScaledFromImage(QImage &&image, QImage &&blurred); - void updateServiceBg(std::optional background); - std::optional patternBackgroundColor() const; + void updateServiceBg(const std::vector &bg); + std::vector patternBackgroundColors() const; void paintImage(Painter &p); void paintRadial(Painter &p); void paintTexts(Painter &p, crl::time ms); diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 22da219e9..c5f2f177f 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -232,17 +232,20 @@ bool ShowWallPaper( match->captured(1), qthelp::UrlParamNameTransform::ToLower); const auto bg = params.value("bg_color"); - if (!params.value("gradient").isEmpty() - || bg.contains('~') - || bg.contains('-')) { + const auto color = params.value("color"); + const auto gradient = params.value("gradient"); + if (gradient.contains('~') || bg.contains('~')) { Ui::show(Box( tr::lng_background_gradient_unsupported(tr::now))); return false; } - const auto color = params.value("color"); return BackgroundPreviewBox::Start( controller, - (color.isEmpty() ? params.value(qsl("slug")) : color), + (!color.isEmpty() + ? color + : !gradient.isEmpty() + ? gradient + : params.value(qsl("slug"))), params); } diff --git a/Telegram/SourceFiles/data/data_wall_paper.cpp b/Telegram/SourceFiles/data/data_wall_paper.cpp index 9889ad069..af2baad93 100644 --- a/Telegram/SourceFiles/data/data_wall_paper.cpp +++ b/Telegram/SourceFiles/data/data_wall_paper.cpp @@ -117,7 +117,7 @@ constexpr auto kVersion = 1; } [[nodiscard]] std::vector ColorsFromString(const QString &string) { - constexpr auto kMaxColors = 1; // #TODO themes gradients replace to 4 + constexpr auto kMaxColors = 2; // #TODO themes gradients replace to 4 const auto view = QStringView(string); const auto count = int(view.size() / 6); if (!count || count > kMaxColors || view.size() != count * 7 - 1) { @@ -237,6 +237,14 @@ int WallPaper::patternIntensity() const { return _intensity; } +float64 WallPaper::patternOpacity() const { + return _intensity / 100.; +} + +int WallPaper::gradientRotation() const { + return _rotation; +} + bool WallPaper::hasShareUrl() const { return !_slug.isEmpty(); } @@ -350,6 +358,8 @@ WallPaper WallPaper::withUrlParams( result._intensity = intensity; } } + result._rotation = params.value("rotation").toInt(); + result._rotation = (std::clamp(result._rotation, 0, 315) / 45) * 45; return result; } @@ -420,8 +430,7 @@ std::optional WallPaper::Create( } const auto unsupported = data.vsettings() && data.vsettings()->match([&](const MTPDwallPaperSettings &data) { - return data.vsecond_background_color() - || data.vthird_background_color() + return data.vthird_background_color() || data.vfourth_background_color(); // #TODO themes gradients }); if (unsupported) { @@ -450,14 +459,16 @@ std::optional WallPaper::Create( } }); } + if (result.isPattern() && result.backgroundColors().empty()) { + return std::nullopt; + } return result; } std::optional WallPaper::Create(const MTPDwallPaperNoFile &data) { const auto unsupported = data.vsettings() && data.vsettings()->match([&](const MTPDwallPaperSettings &data) { - return data.vsecond_background_color() - || data.vthird_background_color() + return data.vthird_background_color() || data.vfourth_background_color(); // #TODO themes gradients }); if (unsupported) { @@ -477,6 +488,9 @@ std::optional WallPaper::Create(const MTPDwallPaperNoFile &data) { } }); } + if (result.backgroundColors().empty()) { + return std::nullopt; + } return result; } @@ -554,7 +568,7 @@ std::optional WallPaper::FromSerialized( >> blurred >> backgroundColorsCount; // #TODO themes gradients replace with 4 - if (backgroundColorsCount < 0 || backgroundColorsCount > 1) { + if (backgroundColorsCount < 0 || backgroundColorsCount > 2) { return std::nullopt; } backgroundColors.reserve(backgroundColorsCount); @@ -610,6 +624,9 @@ std::optional WallPaper::FromSerialized( result._backgroundColors = std::move(backgroundColors); result._intensity = intensity; result._rotation = rotation; + if (result.isPattern() && result.backgroundColors().empty()) { + return std::nullopt; + } return result; } @@ -625,6 +642,9 @@ std::optional WallPaper::FromLegacySerialized( if (const auto color = ColorFromString(slug)) { result._backgroundColors.push_back(*color); } + if (result.isPattern() && result.backgroundColors().empty()) { + return std::nullopt; + } return result; } @@ -695,78 +715,69 @@ bool IsCloudWallPaper(const WallPaper &paper) { && !details::IsTestingEditorWallPaper(paper); } -QColor PatternColor(QColor background) { - const auto hue = background.hueF(); - const auto saturation = background.saturationF(); - const auto value = background.valueF(); - return QColor::fromHsvF( - hue, - std::min(1.0, saturation + 0.05 + 0.1 * (1. - saturation)), - (value > 0.5 - ? std::max(0., value * 0.65) - : std::max(0., std::min(1., 1. - value * 0.65))), - 0.4 - ).toRgb(); +[[nodiscard]] QImage FillDitheredGradient( + QImage image, + const std::vector &colors, + int rotation) { + Expects(colors.size() > 1); + + image.setDevicePixelRatio(1.); + const auto width = image.width(); + const auto height = image.height(); + if (!width || !height) { + return image; + } + + auto p = QPainter(&image); + const auto [start, finalStop] = [&]() -> std::pair { + const auto type = std::clamp(rotation, 0, 315) / 45; + switch (type) { + case 0: return { { 0, 0 }, { 0, height } }; + case 1: return { { width, 0 }, { 0, height } }; + case 2: return { { width, 0 }, { 0, 0 } }; + case 3: return { { width, height }, { 0, 0 } }; + case 4: return { { 0, height }, { 0, 0 } }; + case 5: return { { 0, height }, { width, 0 } }; + case 6: return { { 0, 0 }, { width, 0 } }; + case 7: return { { 0, 0 }, { width, height } }; + } + Unexpected("Rotation value in GenerateDitheredGradient."); + }(); + auto gradient = QLinearGradient(start, finalStop); + gradient.setStops(QGradientStops{ + { 0.0, colors[0] }, + { 1.0, colors[1] } + }); + p.fillRect(0, 0, width, height, QBrush(std::move(gradient))); + p.end(); + + return image; } QImage PreparePatternImage( QImage image, - QColor bg, - QColor fg, - int intensity) { + const std::vector &bg, + int rotation, + float64 opacity) { if (image.format() != QImage::Format_ARGB32_Premultiplied) { image = std::move(image).convertToFormat( QImage::Format_ARGB32_Premultiplied); } - // Similar to ColorizePattern. - // But here we set bg to all 'alpha=0' pixels and fg to opaque ones. - const auto width = image.width(); - const auto height = image.height(); - const auto alpha = anim::interpolate( - 0, - 255, - fg.alphaF() * std::clamp(intensity / 100., 0., 1.)); - if (!alpha) { - image.fill(bg); - return image; + auto result = QImage(image.size(), QImage::Format_ARGB32_Premultiplied); + if (bg.size() < 2) { + result.fill(bg.empty() ? QColor(213, 223, 233) : bg.front()); + } else { + result = FillDitheredGradient(std::move(result), bg, rotation); } - fg.setAlpha(255); - const auto patternBg = anim::shifted(bg); - const auto patternFg = anim::shifted(fg); + auto p = QPainter(&result); + p.setCompositionMode(QPainter::CompositionMode_SoftLight); + p.setOpacity(opacity); + p.drawImage(QRect(QPoint(), image.size()), image); + p.end(); - constexpr auto resultIntsPerPixel = 1; - const auto resultIntsPerLine = (image.bytesPerLine() >> 2); - const auto resultIntsAdded = resultIntsPerLine - width * resultIntsPerPixel; - auto resultInts = reinterpret_cast(image.bits()); - Assert(resultIntsAdded >= 0); - Assert(image.depth() == static_cast((resultIntsPerPixel * sizeof(uint32)) << 3)); - Assert(image.bytesPerLine() == (resultIntsPerLine << 2)); - - const auto maskBytesPerPixel = (image.depth() >> 3); - const auto maskBytesPerLine = image.bytesPerLine(); - const auto maskBytesAdded = maskBytesPerLine - width * maskBytesPerPixel; - - // We want to read the last byte of four available. - // This is the difference with style::colorizeImage. - auto maskBytes = image.constBits() + (maskBytesPerPixel - 1); - Assert(maskBytesAdded >= 0); - Assert(image.depth() == (maskBytesPerPixel << 3)); - for (auto y = 0; y != height; ++y) { - for (auto x = 0; x != width; ++x) { - const auto maskOpacity = static_cast( - *maskBytes) + 1; - const auto fgOpacity = (maskOpacity * alpha) >> 8; - const auto bgOpacity = 256 - fgOpacity; - *resultInts = anim::unshifted( - patternBg * bgOpacity + patternFg * fgOpacity); - maskBytes += maskBytesPerPixel; - resultInts += resultIntsPerPixel; - } - maskBytes += maskBytesAdded; - resultInts += resultIntsAdded; - } - return image; + image = QImage(); + return result; } QImage PrepareBlurredBackground(QImage image) { @@ -782,6 +793,22 @@ QImage PrepareBlurredBackground(QImage image) { return Images::BlurLargeImage(image, kRadius); } +QImage GenerateDitheredGradient( + const std::vector &colors, + int rotation) { + constexpr auto kSize = 512; + return FillDitheredGradient( + QImage(kSize, kSize, QImage::Format_ARGB32_Premultiplied), + colors, + rotation); +} + +QImage GenerateDitheredGradient(const Data::WallPaper &paper) { + return GenerateDitheredGradient( + paper.backgroundColors(), + paper.gradientRotation()); +} + namespace details { WallPaper UninitializedWallPaper() { diff --git a/Telegram/SourceFiles/data/data_wall_paper.h b/Telegram/SourceFiles/data/data_wall_paper.h index 7e75ab184..8c6d93ddc 100644 --- a/Telegram/SourceFiles/data/data_wall_paper.h +++ b/Telegram/SourceFiles/data/data_wall_paper.h @@ -46,6 +46,8 @@ public: [[nodiscard]] bool isLocal() const; [[nodiscard]] bool isBlurred() const; [[nodiscard]] int patternIntensity() const; + [[nodiscard]] float64 patternOpacity() const; + [[nodiscard]] int gradientRotation() const; [[nodiscard]] bool hasShareUrl() const; [[nodiscard]] QString shareUrl(not_null session) const; @@ -120,13 +122,16 @@ private: [[nodiscard]] bool IsDefaultWallPaper(const WallPaper &paper); [[nodiscard]] bool IsCloudWallPaper(const WallPaper &paper); -QColor PatternColor(QColor background); -QImage PreparePatternImage( +[[nodiscard]] QImage PreparePatternImage( QImage image, - QColor bg, - QColor fg, - int intensity); -QImage PrepareBlurredBackground(QImage image); + const std::vector &bg, + int rotation, + float64 opacity); +[[nodiscard]] QImage PrepareBlurredBackground(QImage image); +[[nodiscard]] QImage GenerateDitheredGradient( + const std::vector &colors, + int rotation); +[[nodiscard]] QImage GenerateDitheredGradient(const WallPaper &paper); namespace details { 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 a46cf9871..17511acbd 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp @@ -59,10 +59,10 @@ void ThemeDocument::fillPatternFieldsFrom(const QString &url) { const auto params = qthelp::url_parse_params( paramsString, qthelp::UrlParamNameTransform::ToLower); - const auto kDefaultBackground = QColor(213, 223, 233); const auto paper = Data::DefaultWallPaper().withUrlParams(params); - _intensity = paper.patternIntensity(); - _background = paper.backgroundColor().value_or(kDefaultBackground); + _background = paper.backgroundColors(); + _patternOpacity = paper.patternOpacity(); + _gradientRotation = paper.gradientRotation(); } QSize ThemeDocument::countOptimalSize() { @@ -262,8 +262,8 @@ void ThemeDocument::prepareThumbnailFrom( original = Data::PreparePatternImage( std::move(original), _background, - Data::PatternColor(_background), - _intensity); + _gradientRotation, + _patternOpacity); } _thumbnail = Ui::PixmapFromImage(std::move(original)); _thumbnailGood = good; diff --git a/Telegram/SourceFiles/history/view/media/history_view_theme_document.h b/Telegram/SourceFiles/history/view/media/history_view_theme_document.h index 66d36e161..3eb9b62a1 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_theme_document.h +++ b/Telegram/SourceFiles/history/view/media/history_view_theme_document.h @@ -74,8 +74,9 @@ private: mutable std::shared_ptr _dataMedia; // For wallpaper documents. - QColor _background; - int _intensity = 0; + std::vector _background; + float64 _patternOpacity = 0.; + int _gradientRotation = 0; }; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 97eea1529..d581900b5 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -781,24 +781,39 @@ bool MainWidget::selectingPeer() const { } void MainWidget::cacheBackground() { - if (Window::Theme::Background()->colorForFill()) { + const auto background = Window::Theme::Background(); + if (background->colorForFill()) { return; - } else if (Window::Theme::Background()->tile()) { - auto &bg = Window::Theme::Background()->pixmapForTiled(); - - auto result = QImage(_willCacheFor.width() * cIntRetinaFactor(), _willCacheFor.height() * cIntRetinaFactor(), QImage::Format_RGB32); + } + const auto gradient = background->gradientForFill(); + const auto patternOpacity = background->paper().patternOpacity(); + const auto &bg = background->pixmap(); + if (background->tile() || bg.isNull()) { + auto result = gradient.isNull() + ? QImage( + _willCacheFor.size() * cIntRetinaFactor(), + QImage::Format_ARGB32_Premultiplied) + : gradient.scaled( + _willCacheFor.size() * cIntRetinaFactor(), + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); result.setDevicePixelRatio(cRetinaFactor()); - { + if (!bg.isNull()) { QPainter p(&result); - auto w = bg.width() / cRetinaFactor(); - auto h = bg.height() / cRetinaFactor(); + if (!gradient.isNull()) { + p.setCompositionMode(QPainter::CompositionMode_SoftLight); + p.setOpacity(patternOpacity); + } + const auto &tiled = background->pixmapForTiled(); + auto w = tiled.width() / cRetinaFactor(); + auto h = tiled.height() / cRetinaFactor(); auto sx = 0; auto sy = 0; auto cx = qCeil(_willCacheFor.width() / w); auto cy = qCeil(_willCacheFor.height() / h); for (int i = sx; i < cx; ++i) { for (int j = sy; j < cy; ++j) { - p.drawPixmap(QPointF(i * w, j * h), bg); + p.drawPixmap(QPointF(i * w, j * h), tiled); } } } @@ -806,18 +821,30 @@ void MainWidget::cacheBackground() { _cachedY = 0; _cachedBackground = Ui::PixmapFromImage(std::move(result)); } else { - auto &bg = Window::Theme::Background()->pixmap(); - QRect to, from; Window::Theme::ComputeBackgroundRects(_willCacheFor, bg.size(), to, from); + auto image = bg.toImage().copy(from).scaled( + to.width() * cIntRetinaFactor(), + to.height() * cIntRetinaFactor(), + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + auto result = gradient.isNull() + ? std::move(image) + : gradient.scaled( + image.size(), + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + result.setDevicePixelRatio(cRetinaFactor()); + if (!gradient.isNull()) { + QPainter p(&result); + p.setCompositionMode(QPainter::CompositionMode_SoftLight); + p.setOpacity(patternOpacity); + p.drawImage(QRect(QPoint(), to.size()), image); + } + image = QImage(); _cachedX = to.x(); _cachedY = to.y(); - _cachedBackground = Ui::PixmapFromImage( - bg.toImage().copy(from).scaled( - to.width() * cIntRetinaFactor(), - to.height() * cIntRetinaFactor(), - Qt::IgnoreAspectRatio, - Qt::SmoothTransformation)); + _cachedBackground = Ui::PixmapFromImage(std::move(result)); _cachedBackground.setDevicePixelRatio(cRetinaFactor()); } _cachedFor = _willCacheFor; diff --git a/Telegram/SourceFiles/settings/settings_chat.cpp b/Telegram/SourceFiles/settings/settings_chat.cpp index 29690be5f..3d99f0702 100644 --- a/Telegram/SourceFiles/settings/settings_chat.cpp +++ b/Telegram/SourceFiles/settings/settings_chat.cpp @@ -551,41 +551,39 @@ void BackgroundRow::radialAnimationCallback(crl::time now) { } void BackgroundRow::updateImage() { - int32 size = st::settingsBackgroundThumb * cIntRetinaFactor(); - QImage back(size, size, QImage::Format_ARGB32_Premultiplied); + const auto size = st::settingsBackgroundThumb; + const auto fullsize = size * cIntRetinaFactor(); + QImage back(fullsize, fullsize, QImage::Format_ARGB32_Premultiplied); back.setDevicePixelRatio(cRetinaFactor()); { Painter p(&back); PainterHighQualityEnabler hq(p); - if (const auto color = Window::Theme::Background()->colorForFill()) { - p.fillRect( - 0, - 0, - st::settingsBackgroundThumb, - st::settingsBackgroundThumb, - *color); + const auto background = Window::Theme::Background(); + if (const auto color = background->colorForFill()) { + p.fillRect(0, 0, size, size, *color); } else { - const auto &pix = Window::Theme::Background()->pixmap(); - const auto sx = (pix.width() > pix.height()) - ? ((pix.width() - pix.height()) / 2) - : 0; - const auto sy = (pix.height() > pix.width()) - ? ((pix.height() - pix.width()) / 2) - : 0; - const auto s = (pix.width() > pix.height()) - ? pix.height() - : pix.width(); - p.drawPixmap( - 0, - 0, - st::settingsBackgroundThumb, - st::settingsBackgroundThumb, - pix, - sx, - sy, - s, - s); + const auto gradient = background->gradientForFill(); + const auto patternOpacity = background->paper().patternOpacity(); + if (!gradient.isNull()) { + auto hq = PainterHighQualityEnabler(p); + p.drawImage(QRect(0, 0, size, size), gradient); + p.setCompositionMode(QPainter::CompositionMode_SoftLight); + p.setOpacity(patternOpacity); + } + const auto &pix = background->pixmap(); + if (!pix.isNull()) { + const auto sx = (pix.width() > pix.height()) + ? ((pix.width() - pix.height()) / 2) + : 0; + const auto sy = (pix.height() > pix.width()) + ? ((pix.height() - pix.width()) / 2) + : 0; + const auto s = (pix.width() > pix.height()) + ? pix.height() + : pix.width(); + p.drawPixmap(0, 0, size, size, pix, sx, sy, s, s); + } } } Images::prepareRound(back, ImageRoundRadius::Small); diff --git a/Telegram/SourceFiles/window/section_widget.cpp b/Telegram/SourceFiles/window/section_widget.cpp index 1e4fa8702..41a7cc9f4 100644 --- a/Telegram/SourceFiles/window/section_widget.cpp +++ b/Telegram/SourceFiles/window/section_widget.cpp @@ -90,8 +90,9 @@ void SectionWidget::PaintBackground( QRect clip) { Painter p(widget); + const auto background = Window::Theme::Background(); auto fill = QRect(0, 0, widget->width(), controller->content()->height()); - if (const auto color = Window::Theme::Background()->colorForFill()) { + if (const auto color = background->colorForFill()) { p.fillRect(fill, *color); return; } @@ -99,31 +100,45 @@ void SectionWidget::PaintBackground( auto x = 0, y = 0; auto cached = controller->content()->cachedBackground(fill, x, y); if (cached.isNull()) { - if (Window::Theme::Background()->tile()) { - auto &pix = Window::Theme::Background()->pixmapForTiled(); - auto left = clip.left(); - auto top = clip.top(); - auto right = clip.left() + clip.width(); - auto bottom = clip.top() + clip.height(); - auto w = pix.width() / cRetinaFactor(); - auto h = pix.height() / cRetinaFactor(); - auto sx = qFloor(left / w); - auto sy = qFloor((top - fromy) / h); - auto cx = qCeil(right / w); - auto cy = qCeil((bottom - fromy) / h); - for (auto i = sx; i < cx; ++i) { - for (auto j = sy; j < cy; ++j) { - p.drawPixmap(QPointF(i * w, fromy + j * h), pix); + const auto gradient = background->gradientForFill(); + const auto patternOpacity = background->paper().patternOpacity(); + const auto &bg = background->pixmap(); + if (background->tile() || bg.isNull()) { + if (!gradient.isNull()) { + auto hq = PainterHighQualityEnabler(p); + p.drawImage(fill, gradient); + p.setCompositionMode(QPainter::CompositionMode_SoftLight); + p.setOpacity(patternOpacity); + } + if (!bg.isNull()) { + auto &tiled = background->pixmapForTiled(); + auto left = clip.left(); + auto top = clip.top(); + auto right = clip.left() + clip.width(); + auto bottom = clip.top() + clip.height(); + auto w = tiled.width() / cRetinaFactor(); + auto h = tiled.height() / cRetinaFactor(); + auto sx = qFloor(left / w); + auto sy = qFloor((top - fromy) / h); + auto cx = qCeil(right / w); + auto cy = qCeil((bottom - fromy) / h); + for (auto i = sx; i < cx; ++i) { + for (auto j = sy; j < cy; ++j) { + p.drawPixmap(QPointF(i * w, fromy + j * h), tiled); + } } } } else { - PainterHighQualityEnabler hq(p); - - auto &pix = Window::Theme::Background()->pixmap(); + auto hq = PainterHighQualityEnabler(p); QRect to, from; - Window::Theme::ComputeBackgroundRects(fill, pix.size(), to, from); + Window::Theme::ComputeBackgroundRects(fill, bg.size(), to, from); + if (!gradient.isNull()) { + p.drawImage(to, gradient); + p.setCompositionMode(QPainter::CompositionMode_SoftLight); + p.setOpacity(patternOpacity); + } to.moveTop(to.top() + fromy); - p.drawPixmap(to, pix, from); + p.drawPixmap(to, bg, from); } } else { p.drawPixmap(x, fromy + y, cached); diff --git a/Telegram/SourceFiles/window/themes/window_theme.cpp b/Telegram/SourceFiles/window/themes/window_theme.cpp index dc7b8dc6b..d2bb49973 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme.cpp @@ -58,18 +58,23 @@ inline bool AreTestingTheme() { return !GlobalApplying.paletteForRevert.isEmpty(); } -bool CalculateIsMonoColorImage(const QImage &image) { - if (!image.isNull()) { - 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 false; - } - } - return true; +std::optional CalculateImageMonoColor(const QImage &image) { + if (image.isNull()) { + return std::nullopt; } - return false; + 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.format() == QImage::Format_ARGB32_Premultiplied) + && !image.size().isEmpty(); } QByteArray readThemeContent(const QString &path) { @@ -731,7 +736,7 @@ void ChatBackground::set(const Data::WallPaper &paper, QImage image) { } if (Data::IsThemeWallPaper(_paper)) { (nightMode() ? _tileNightValue : _tileDayValue) = _themeTile; - setPreparedImage(_themeImage, _themeImage); + setPrepared(_themeImage, _themeImage, QImage()); } else if (Data::details::IsTestingThemeWallPaper(_paper) || Data::details::IsTestingDefaultWallPaper(_paper) || Data::details::IsTestingEditorWallPaper(_paper)) { @@ -741,8 +746,9 @@ void ChatBackground::set(const Data::WallPaper &paper, QImage image) { setPaper(Data::details::TestingDefaultWallPaper()); } image = postprocessBackgroundImage(std::move(image)); - setPreparedImage(image, image); + setPrepared(image, image, QImage()); } else { + const auto &bgColors = _paper.backgroundColors(); if (Data::IsLegacy1DefaultWallPaper(_paper)) { image.load(qsl(":/gui/art/bg_initial.jpg")); const auto scale = cScale() * cIntRetinaFactor(); @@ -752,7 +758,7 @@ void ChatBackground::set(const Data::WallPaper &paper, QImage image) { Qt::SmoothTransformation); } } else if (Data::IsDefaultWallPaper(_paper) - || (_paper.backgroundColors().empty() && image.isNull())) { + || (bgColors.empty() && image.isNull())) { setPaper(Data::DefaultWallPaper().withParamsFrom(_paper)); image.load(qsl(":/gui/art/background.jpg")); } @@ -762,29 +768,38 @@ void ChatBackground::set(const Data::WallPaper &paper, QImage image) { || Data::IsLegacy1DefaultWallPaper(_paper)) ? QImage() : image)); - if (const auto fill = _paper.backgroundColor()) { - if (_paper.isPattern() && !image.isNull()) { + if (_paper.isPattern() && !image.isNull()) { + if (bgColors.size() < 2) { auto prepared = postprocessBackgroundImage( Data::PreparePatternImage( image, - *fill, - Data::PatternColor(*fill), - _paper.patternIntensity())); - setPreparedImage(std::move(image), std::move(prepared)); + bgColors, + _paper.gradientRotation(), + _paper.patternOpacity())); + setPrepared( + std::move(image), + std::move(prepared), + QImage()); } else { - _original = QImage(); - _pixmap = QPixmap(); - _pixmapForTiled = QPixmap(); - if (adjustPaletteRequired()) { - adjustPaletteUsingColor(*fill); - } + setPrepared( + image, + image, + Data::GenerateDitheredGradient(_paper)); } + } else if (bgColors.size() == 1) { + setPrepared(QImage(), QImage(), QImage()); + } else if (!bgColors.empty()) { + setPrepared( + QImage(), + QImage(), + Data::GenerateDitheredGradient(_paper)); } else { image = postprocessBackgroundImage(std::move(image)); - setPreparedImage(image, image); + setPrepared(image, image, QImage()); } } Assert(colorForFill() + || !_gradient.isNull() || (!_original.isNull() && !_pixmap.isNull() && !_pixmapForTiled.isNull())); @@ -797,27 +812,37 @@ void ChatBackground::set(const Data::WallPaper &paper, QImage image) { checkUploadWallPaper(); } -void ChatBackground::setPreparedImage(QImage original, QImage prepared) { - Expects(original.format() == QImage::Format_ARGB32_Premultiplied); - Expects(original.width() > 0 && original.height() > 0); - Expects(prepared.format() == QImage::Format_ARGB32_Premultiplied); - Expects(prepared.width() > 0 && prepared.height() > 0); +void ChatBackground::setPrepared( + QImage original, + QImage prepared, + QImage gradient) { + Expects(original.isNull() || GoodImageFormatAndSize(original)); + Expects(prepared.isNull() || GoodImageFormatAndSize(prepared)); + Expects(gradient.isNull() || GoodImageFormatAndSize(gradient)); - _original = std::move(original); - if (!_paper.isPattern() && _paper.isBlurred()) { + if (!prepared.isNull() && !_paper.isPattern() && _paper.isBlurred()) { prepared = Data::PrepareBlurredBackground(std::move(prepared)); } if (adjustPaletteRequired()) { - adjustPaletteUsingBackground(prepared); + if (!gradient.isNull()) { + adjustPaletteUsingBackground(gradient); + } else if (!prepared.isNull()) { + adjustPaletteUsingBackground(prepared); + } else if (!_paper.backgroundColors().empty()) { + adjustPaletteUsingColor(_paper.backgroundColors().front()); + } } + + _original = std::move(original); + _gradient = std::move(gradient); preparePixmaps(std::move(prepared)); } void ChatBackground::preparePixmaps(QImage image) { const auto width = image.width(); const auto height = image.height(); - const auto isSmallForTiled = (width < kMinimumTiledSize) - || (height < kMinimumTiledSize); + const auto isSmallForTiled = (width > 0 && height > 0) + && (width < kMinimumTiledSize || height < kMinimumTiledSize); if (isSmallForTiled) { const auto repeatTimesX = qCeil(kMinimumTiledSize / (1. * width)); const auto repeatTimesY = qCeil(kMinimumTiledSize / (1. * height)); @@ -841,8 +866,12 @@ void ChatBackground::preparePixmaps(QImage image) { } _pixmapForTiled = Ui::PixmapFromImage(std::move(imageForTiled)); } - _isMonoColorImage = CalculateIsMonoColorImage(image); - _pixmap = Ui::PixmapFromImage(std::move(image)); + _imageMonoColor = _gradient.isNull() + ? CalculateImageMonoColor(image) + : std::nullopt; + _pixmap = image.isNull() + ? QPixmap() + : Ui::PixmapFromImage(std::move(image)); if (!isSmallForTiled) { _pixmapForTiled = _pixmap; } @@ -910,7 +939,15 @@ void ChatBackground::adjustPaletteUsingColor(QColor color) { } std::optional ChatBackground::colorForFill() const { - return _pixmap.isNull() ? _paper.backgroundColor() : std::nullopt; + return !_pixmap.isNull() + ? imageMonoColor() + : !_gradient.isNull() + ? std::nullopt + : _paper.backgroundColor(); +} + +QImage ChatBackground::gradientForFill() const { + return _gradient; } QImage ChatBackground::createCurrentImage() const { @@ -921,8 +958,24 @@ QImage ChatBackground::createCurrentImage() const { QImage::Format_ARGB32_Premultiplied); result.fill(*fill); return result; + } else if (_gradient.isNull()) { + return pixmap().toImage(); + } else if (pixmap().isNull()) { + return _gradient; } - return pixmap().toImage(); + const auto &pattern = pixmap(); + auto result = _gradient.scaled( + pattern.size(), + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + result.setDevicePixelRatio(1.); + { + auto p = QPainter(&result); + p.setCompositionMode(QPainter::CompositionMode_SoftLight); + p.setOpacity(paper().patternOpacity()); + p.drawPixmap(QRect(QPoint(), pattern.size()), pattern); + } + return result; } bool ChatBackground::tile() const { @@ -949,8 +1002,8 @@ bool ChatBackground::tileNight() const { return _tileNightValue; } -bool ChatBackground::isMonoColorImage() const { - return _isMonoColorImage; +std::optional ChatBackground::imageMonoColor() const { + return _imageMonoColor; } void ChatBackground::setTile(bool tile) { diff --git a/Telegram/SourceFiles/window/themes/window_theme.h b/Telegram/SourceFiles/window/themes/window_theme.h index c7b8ec572..9758d2fce 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.h +++ b/Telegram/SourceFiles/window/themes/window_theme.h @@ -164,7 +164,7 @@ public: void appliedEditedPalette(); void downloadingStarted(bool tile); - [[nodiscard]] Data::WallPaper paper() const { + [[nodiscard]] const Data::WallPaper &paper() const { return _paper; } [[nodiscard]] WallPaperId id() const { @@ -177,11 +177,12 @@ public: return _pixmapForTiled; } [[nodiscard]] std::optional colorForFill() const; + [[nodiscard]] QImage gradientForFill() const; [[nodiscard]] QImage createCurrentImage() const; [[nodiscard]] bool tile() const; [[nodiscard]] bool tileDay() const; [[nodiscard]] bool tileNight() const; - [[nodiscard]] bool isMonoColorImage() const; + [[nodiscard]] std::optional imageMonoColor() const; [[nodiscard]] bool nightModeChangeAllowed() const; private: @@ -195,7 +196,7 @@ private: [[nodiscard]] bool started() const; void initialRead(); void saveForRevert(); - void setPreparedImage(QImage original, QImage prepared); + void setPrepared(QImage original, QImage prepared, QImage gradient); void preparePixmaps(QImage image); void writeNewBackgroundSettings(); void setPaper(const Data::WallPaper &paper); @@ -236,6 +237,7 @@ private: rpl::event_stream _updates; Data::WallPaper _paper = Data::details::UninitializedWallPaper(); std::optional _paperColor; + QImage _gradient; QImage _original; QPixmap _pixmap; QPixmap _pixmapForTiled; @@ -245,7 +247,7 @@ private: std::optional _localStoredTileDayValue; std::optional _localStoredTileNightValue; - bool _isMonoColorImage = false; + std::optional _imageMonoColor; Object _themeObject; QImage _themeImage; diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index 74431484a..3eb496fce 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -87,9 +87,10 @@ constexpr auto kMinDiffIntensity = 0.25; [[nodiscard]] bool IsFilledCover() { const auto background = Window::Theme::Background(); + return false; AssertIsDebug(); return background->tile() || background->colorForFill().has_value() - || background->isMonoColorImage() + || !background->gradientForFill().isNull() || background->paper().isPattern() || Data::IsLegacy1DefaultWallPaper(background->paper()); }