diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 60a871e45..94cb291f1 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -398,8 +398,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_theme_keep_changes" = "Keep changes"; "lng_theme_revert" = "Revert"; "lng_background_header" = "Background preview"; -"lng_background_text1" = "You can't swipe left or right to preview anything - this is tdesktop, sorry."; -"lng_background_text2" = "Sounds awful."; +"lng_background_text1" = "Ah, you kids today with techno music! You should enjoy the classics, like Hasselhoff!"; +"lng_background_text2" = "I can't even take you seriously right now."; "lng_background_bad_link" = "This background link appears to be invalid."; "lng_background_apply" = "Apply"; "lng_background_share" = "Share"; diff --git a/Telegram/SourceFiles/boxes/background_box.cpp b/Telegram/SourceFiles/boxes/background_box.cpp index 187453127..df01db3ea 100644 --- a/Telegram/SourceFiles/boxes/background_box.cpp +++ b/Telegram/SourceFiles/boxes/background_box.cpp @@ -74,9 +74,9 @@ AdminLog::OwnedItem GenerateTextItem( return AdminLog::OwnedItem(delegate, item); } -QImage PrepareScaledFromFull( +QImage PrepareScaledNonPattern( const QImage &image, - Images::Option blur = Images::Option(0)) { + Images::Option blur) { const auto size = st::boxWideWidth; const auto width = std::max(image.width(), 1); const auto height = std::max(image.height(), 1); @@ -90,17 +90,71 @@ QImage PrepareScaledFromFull( image, takeWidth, takeHeight, - Images::Option::Smooth | blur, + Images::Option::Smooth + | Images::Option::TransparentBackground + | blur, size, size); } -QPixmap PrepareScaledFromThumb(not_null thumb, bool good) { - return thumb->loaded() - ? App::pixmapFromImageInPlace(PrepareScaledFromFull( - thumb->original(), - good ? Images::Option(0) : Images::Option::Blurred)) - : QPixmap(); +QImage ColorizePattern(QImage image, QColor color) { + if (image.format() != QImage::Format_ARGB32_Premultiplied) { + image = std::move(image).convertToFormat( + QImage::Format_ARGB32_Premultiplied); + } + // Similar to style::colorizeImage. + // But style::colorizeImage takes pattern with all pixels having the + // same components value, from (0, 0, 0, 0) to (255, 255, 255, 255). + // + // While in patterns we have different value ranges, usually they are + // from (0, 0, 0, 0) to (0, 0, 0, 255), so we should use only 'alpha'. + + const auto width = image.width(); + const auto height = image.height(); + const auto pattern = anim::shifted(color); + + const auto resultBytesPerPixel = (image.depth() >> 3); + 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) { + auto maskOpacity = static_cast(*maskBytes) + 1; + *resultInts = anim::unshifted(pattern * maskOpacity); + maskBytes += maskBytesPerPixel; + resultInts += resultIntsPerPixel; + } + maskBytes += maskBytesAdded; + resultInts += resultIntsAdded; + } + return std::move(image); +} + +QImage PrepareScaledFromFull( + const QImage &image, + std::optional patternBackground, + Images::Option blur = Images::Option(0)) { + auto result = PrepareScaledNonPattern(image, blur); + if (patternBackground) { + result = ColorizePattern( + std::move(result), + Window::Theme::PatternColor(*patternBackground)); + } + return result; } } // namespace @@ -358,10 +412,11 @@ void BackgroundPreviewBox::paintEvent(QPaintEvent *e) { Painter p(this); const auto ms = getms(); - - if (const auto color = _paper.backgroundColor()) { + const auto color = _paper.backgroundColor(); + if (color) { p.fillRect(e->rect(), *color); - } else { + } + if (!color || _paper.isPattern()) { if (_scaled.isNull() && !setScaledFromThumb()) { p.fillRect(e->rect(), st::boxBg); return; @@ -375,6 +430,11 @@ void BackgroundPreviewBox::paintEvent(QPaintEvent *e) { void BackgroundPreviewBox::paintImage(Painter &p) { Expects(!_scaled.isNull()); + p.setOpacity(_paper.isPattern() + ? std::clamp(_paper.patternIntensity() / 100., 0., 1.) + : 1.); + const auto guard = gsl::finally([&] { p.setOpacity(1.); }); + const auto factor = cIntRetinaFactor(); const auto size = st::boxWideWidth; const auto from = QRect( @@ -457,10 +517,25 @@ void BackgroundPreviewBox::step_radial(TimeMs ms, bool timer) { } bool BackgroundPreviewBox::setScaledFromThumb() { - _scaled = PrepareScaledFromThumb( - _paper.thumbnail(), - !_paper.document()); - return !_scaled.isNull(); + Expects(_paper.thumbnail() != nullptr); + + const auto thumbnail = _paper.thumbnail(); + if (!thumbnail->loaded()) { + return false; + } + setScaledFromImage(PrepareScaledFromFull( + thumbnail->original(), + patternBackgroundColor(), + _paper.document() ? Images::Option::Blurred : Images::Option(0))); + return true; +} + +void BackgroundPreviewBox::setScaledFromImage(QImage &&image) { + _scaled = App::pixmapFromImageInPlace(std::move(image)); +} + +std::optional BackgroundPreviewBox::patternBackgroundColor() const { + return _paper.isPattern() ? _paper.backgroundColor() : std::nullopt; } void BackgroundPreviewBox::checkLoadedDocument() { @@ -477,9 +552,10 @@ void BackgroundPreviewBox::checkLoadedDocument() { crl::async([ this, image = std::move(image), + patternBackground = patternBackgroundColor(), guard = std::move(right) ]() mutable { - auto scaled = PrepareScaledFromFull(image); + auto scaled = PrepareScaledFromFull(image, patternBackground); crl::on_main([ this, image = std::move(image), @@ -489,7 +565,7 @@ void BackgroundPreviewBox::checkLoadedDocument() { if (!guard) { return; } - _scaled = App::pixmapFromImageInPlace(std::move(scaled)); + setScaledFromImage(std::move(scaled)); _full = std::move(image); update(); }); @@ -497,17 +573,19 @@ void BackgroundPreviewBox::checkLoadedDocument() { }); } -bool BackgroundPreviewBox::Start(const QString &slug, const QString &mode) { +bool BackgroundPreviewBox::Start( + const QString &slug, + const QMap ¶ms) { if (const auto paper = Data::WallPaper::FromColorSlug(slug)) { - Ui::show(Box(*paper)); + Ui::show(Box(paper->withUrlParams(params))); return true; } if (!IsValidWallPaperSlug(slug)) { Ui::show(Box(lang(lng_background_bad_link))); return false; } - Auth().api().requestWallPaper(slug, [](const Data::WallPaper &result) { - Ui::show(Box(result)); + Auth().api().requestWallPaper(slug, [=](const Data::WallPaper &result) { + Ui::show(Box(result.withUrlParams(params))); }, [](const RPCError &error) { Ui::show(Box(lang(lng_background_bad_link))); }); diff --git a/Telegram/SourceFiles/boxes/background_box.h b/Telegram/SourceFiles/boxes/background_box.h index 401521dd9..44d7156d0 100644 --- a/Telegram/SourceFiles/boxes/background_box.h +++ b/Telegram/SourceFiles/boxes/background_box.h @@ -39,7 +39,9 @@ class BackgroundPreviewBox public: BackgroundPreviewBox(QWidget*, const Data::WallPaper &paper); - static bool Start(const QString &slug, const QString &mode); + static bool Start( + const QString &slug, + const QMap ¶ms); using Element = HistoryView::Element; HistoryView::Context elementContext() override; @@ -67,6 +69,8 @@ private: void checkLoadedDocument(); bool setScaledFromThumb(); + void setScaledFromImage(QImage &&image); + std::optional patternBackgroundColor() const; void paintImage(Painter &p); void paintRadial(Painter &p, TimeMs ms); void paintTexts(Painter &p, TimeMs ms); diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 439d71c21..1d762c705 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -69,7 +69,7 @@ boxTitle: FlatLabel(defaultFlatLabel) { linkFontOver: font(17px semibold underline); } } -boxTitlePosition: point(23px, 20px); +boxTitlePosition: point(23px, 16px); boxTitleHeight: 56px; boxLayerTitlePosition: point(23px, 16px); boxLayerTitleHeight: 56px; diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 719d4db66..ac22b9bf9 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -173,7 +173,7 @@ bool ShowWallPaper(const Match &match, const QVariant &context) { qthelp::UrlParamNameTransform::ToLower); return BackgroundPreviewBox::Start( params.value(qsl("slug")), - params.value(qsl("mode"))); + params); } bool ResolveUsername(const Match &match, const QVariant &context) { diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index d1be15b0d..6f53d1ec6 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1057,7 +1057,7 @@ bool MainWidget::sendMessageFail(const RPCError &error) { } void MainWidget::cacheBackground() { - if (Window::Theme::Background()->color()) { + if (Window::Theme::Background()->colorForFill()) { return; } else if (Window::Theme::Background()->tile()) { auto &bg = Window::Theme::Background()->pixmapForTiled(); diff --git a/Telegram/SourceFiles/settings/settings_chat.cpp b/Telegram/SourceFiles/settings/settings_chat.cpp index e701ecf50..78239487e 100644 --- a/Telegram/SourceFiles/settings/settings_chat.cpp +++ b/Telegram/SourceFiles/settings/settings_chat.cpp @@ -265,7 +265,7 @@ void BackgroundRow::updateImage() { Painter p(&back); PainterHighQualityEnabler hq(p); - if (const auto color = Window::Theme::Background()->color()) { + if (const auto color = Window::Theme::Background()->colorForFill()) { p.fillRect( 0, 0, diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp index 383478ddd..2368346a2 100644 --- a/Telegram/SourceFiles/storage/localstorage.cpp +++ b/Telegram/SourceFiles/storage/localstorage.cpp @@ -48,9 +48,11 @@ constexpr auto kFileLoaderQueueStopTimeout = TimeMs(5000); constexpr auto kDefaultStickerInstallDate = TimeId(1); constexpr auto kProxyTypeShift = 1024; constexpr auto kWriteMapTimeout = TimeMs(1000); +constexpr auto kSavedBackgroundFormat = QImage::Format_ARGB32_Premultiplied; constexpr auto kWallPaperLegacySerializeTagId = int32(-111); constexpr auto kWallPaperSerializeTagId = int32(-112); +constexpr auto kWallPaperSidesLimit = 10000; constexpr auto kSinglePeerTypeUser = qint32(1); constexpr auto kSinglePeerTypeChat = qint32(2); @@ -3960,7 +3962,7 @@ void readSavedGifs() { } } -void writeBackground(const Data::WallPaper &paper, const QImage &img) { +void writeBackground(const Data::WallPaper &paper, const QImage &image) { if (!_working() || !_backgroundCanWrite) { return; } @@ -3973,11 +3975,33 @@ void writeBackground(const Data::WallPaper &paper, const QImage &img) { auto &backgroundKey = Window::Theme::IsNightMode() ? _backgroundKeyNight : _backgroundKeyDay; - QByteArray bmp; - if (!img.isNull()) { - QBuffer buf(&bmp); - if (!img.save(&buf, "BMP")) { - return; + auto imageData = QByteArray(); + if (!image.isNull()) { + const auto width = qint32(image.width()); + const auto height = qint32(image.height()); + const auto perpixel = (image.depth() >> 3); + const auto srcperline = image.bytesPerLine(); + const auto srcsize = srcperline * height; + const auto dstperline = width * perpixel; + const auto dstsize = dstperline * height; + const auto copy = (image.format() != kSavedBackgroundFormat) + ? image.convertToFormat(kSavedBackgroundFormat) + : image; + imageData.resize(2 * sizeof(qint32) + dstsize); + + auto dst = bytes::make_detached_span(imageData); + bytes::copy(dst, bytes::object_as_span(&width)); + dst = dst.subspan(sizeof(qint32)); + bytes::copy(dst, bytes::object_as_span(&height)); + dst = dst.subspan(sizeof(qint32)); + const auto src = bytes::make_span(image.constBits(), srcsize); + if (srcsize == dstsize) { + bytes::copy(dst, src); + } else { + for (auto y = 0; y != height; ++y) { + bytes::copy(dst, src.subspan(y * srcperline, dstperline)); + dst = dst.subspan(dstperline); + } } } if (!backgroundKey) { @@ -3988,20 +4012,12 @@ void writeBackground(const Data::WallPaper &paper, const QImage &img) { const auto serialized = paper.serialize(); quint32 size = sizeof(qint32) + Serialize::bytearraySize(serialized) - + Serialize::bytearraySize(bmp); + + Serialize::bytearraySize(imageData); EncryptedDescriptor data(size); data.stream << qint32(kWallPaperSerializeTagId) << serialized - << bmp; - //+2 * sizeof(quint64) - // + sizeof(quint32) - // + Serialize::stringSize(paper.slug) - - // << quint64(paper.id) - // << quint64(paper.accessHash) - // << quint32(paper.flags.value()) - // << paper.slug + << imageData; FileWriteDescriptor file(backgroundKey); file.writeEncrypted(data); @@ -4052,8 +4068,8 @@ bool readBackground() { return false; } - QByteArray bmp; - bg.stream >> bmp; + QByteArray imageData; + bg.stream >> imageData; const auto isOldEmptyImage = (bg.stream.status() != QDataStream::Ok); if (isOldEmptyImage || Data::IsLegacy1DefaultWallPaper(*paper) @@ -4067,20 +4083,56 @@ bool readBackground() { } _backgroundCanWrite = true; return true; - } else if (Data::IsThemeWallPaper(*paper) && bmp.isEmpty()) { + } else if (Data::IsThemeWallPaper(*paper) && imageData.isEmpty()) { _backgroundCanWrite = false; Window::Theme::Background()->setImage(*paper); _backgroundCanWrite = true; return true; } - auto image = QImage(); - auto buffer = QBuffer(&bmp); - auto reader = QImageReader(&buffer); + if (legacyId == kWallPaperSerializeTagId) { + const auto perpixel = 4; + auto src = bytes::make_span(imageData); + auto width = qint32(); + auto height = qint32(); + if (src.size() > 2 * sizeof(qint32)) { + bytes::copy( + bytes::object_as_span(&width), + src.subspan(0, sizeof(qint32))); + src = src.subspan(sizeof(qint32)); + bytes::copy( + bytes::object_as_span(&height), + src.subspan(0, sizeof(qint32))); + src = src.subspan(sizeof(qint32)); + if (width + height <= kWallPaperSidesLimit + && src.size() == width * height * perpixel) { + image = QImage( + width, + height, + QImage::Format_ARGB32_Premultiplied); + if (!image.isNull()) { + const auto srcperline = width * perpixel; + const auto srcsize = srcperline * height; + const auto dstperline = image.bytesPerLine(); + const auto dstsize = dstperline * height; + Assert(srcsize == dstsize); + bytes::copy( + bytes::make_span(image.bits(), dstsize), + src); + } + } + } + } else { + auto buffer = QBuffer(&imageData); + auto reader = QImageReader(&buffer); #ifndef OS_MAC_OLD - reader.setAutoTransform(true); + reader.setAutoTransform(true); #endif // OS_MAC_OLD - if (reader.read(&image) || paper->backgroundColor()) { + if (!reader.read(&image)) { + image = QImage(); + } + } + if (!image.isNull()|| paper->backgroundColor()) { _backgroundCanWrite = false; Window::Theme::Background()->setImage(*paper, std::move(image)); _backgroundCanWrite = true; diff --git a/Telegram/SourceFiles/storage/localstorage.h b/Telegram/SourceFiles/storage/localstorage.h index 6b562eedb..8536c2dd5 100644 --- a/Telegram/SourceFiles/storage/localstorage.h +++ b/Telegram/SourceFiles/storage/localstorage.h @@ -140,7 +140,7 @@ void writeSavedGifs(); void readSavedGifs(); int32 countSavedGifsHash(); -void writeBackground(const Data::WallPaper &paper, const QImage &img); +void writeBackground(const Data::WallPaper &paper, const QImage &image); bool readBackground(); void writeTheme(const Window::Theme::Saved &saved); diff --git a/Telegram/SourceFiles/ui/style/style_core.cpp b/Telegram/SourceFiles/ui/style/style_core.cpp index b518599b0..287efde11 100644 --- a/Telegram/SourceFiles/ui/style/style_core.cpp +++ b/Telegram/SourceFiles/ui/style/style_core.cpp @@ -62,6 +62,10 @@ void stopManager() { } void colorizeImage(const QImage &src, QColor c, QImage *outResult, QRect srcRect, QPoint dstPoint) { + // In background_box ColorizePattern we use the fact that + // colorizeImage takes only first byte of the mask, so it + // could be used for wallpaper patterns, which have values + // in ranges (0, 0, 0, 0) to (0, 0, 0, 255) (only 'alpha'). if (srcRect.isNull()) { srcRect = src.rect(); } else { diff --git a/Telegram/SourceFiles/window/section_widget.cpp b/Telegram/SourceFiles/window/section_widget.cpp index fd5bc33c3..010a88e00 100644 --- a/Telegram/SourceFiles/window/section_widget.cpp +++ b/Telegram/SourceFiles/window/section_widget.cpp @@ -77,7 +77,7 @@ void SectionWidget::PaintBackground(QWidget *widget, QPaintEvent *event) { auto clip = event->rect(); auto fill = QRect(0, 0, widget->width(), App::main()->height()); - if (const auto color = Window::Theme::Background()->color()) { + if (const auto color = Window::Theme::Background()->colorForFill()) { p.fillRect(fill, *color); return; } diff --git a/Telegram/SourceFiles/window/themes/window_theme.cpp b/Telegram/SourceFiles/window/themes/window_theme.cpp index a53fed9de..8fc55838f 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme.cpp @@ -75,6 +75,92 @@ std::optional MaybeColorFromSerialized(quint32 serialized) { int(serialized & 0xFFU))); } +std::optional ColorFromString(const QString &string) { + if (string.size() != 6) { + return {}; + } else if (ranges::find_if(string, [](QChar ch) { + return (ch < 'a' || ch > 'f') + && (ch < 'A' || ch > 'F') + && (ch < '0' || ch > '9'); + }) != string.end()) { + return {}; + } + const auto component = [](const QString &text, int index) { + const auto decimal = [](QChar hex) { + const auto code = hex.unicode(); + return (code >= '0' && code <= '9') + ? int(code - '0') + : (code >= 'a' && code <= 'f') + ? int(code - 'a' + 0x0a) + : int(code - 'A' + 0x0a); + }; + index *= 2; + return decimal(text[index]) * 0x10 + decimal(text[index + 1]); + }; + return QColor( + component(string, 0), + component(string, 1), + component(string, 2), + 255); +} + +QImage PreparePatternImage(QImage image, QColor bg, QColor fg, int intensity) { + 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 std::move(image); + } + fg.setAlpha(255); + const auto patternBg = anim::shifted(bg); + const auto patternFg = anim::shifted(fg); + + const auto resultBytesPerPixel = (image.depth() >> 3); + 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 std::move(image); +} + } // namespace WallPaper::WallPaper(WallPaperId id) : _id(id) { @@ -105,6 +191,22 @@ Image *WallPaper::thumbnail() const { return _thumbnail; } +bool WallPaper::isPattern() const { + return _flags & MTPDwallPaper::Flag::f_pattern; +} + +bool WallPaper::isDefault() const { + return _flags & MTPDwallPaper::Flag::f_default; +} + +bool WallPaper::isCreator() const { + return _flags & MTPDwallPaper::Flag::f_creator; +} + +int WallPaper::patternIntensity() const { + return _intensity; +} + bool WallPaper::hasShareUrl() const { return !_slug.isEmpty(); } @@ -131,6 +233,39 @@ FileOrigin WallPaper::fileOrigin() const { return FileOriginWallpaper(_id, _accessHash); } +WallPaper WallPaper::withUrlParams( + const QMap ¶ms) const { + using Flag = MTPDwallPaperSettings::Flag; + + auto result = *this; + result._settings = Flag(0); + result._backgroundColor = ColorFromString(_slug); + result._intensity = kDefaultIntensity; + + if (auto mode = params.value("mode"); !mode.isEmpty()) { + const auto list = mode.replace('+', ' ').split(' '); + for (const auto &change : list) { + if (change == qstr("blur")) { + result._settings |= Flag::f_blur; + } else if (change == qstr("motion")) { + result._settings |= Flag::f_motion; + } + } + } + if (const auto color = ColorFromString(params.value("bg_color"))) { + result._backgroundColor = color; + } + if (const auto string = params.value("intensity"); !string.isEmpty()) { + auto ok = false; + const auto intensity = string.toInt(&ok); + if (ok && base::in_range(intensity, 0, 100)) { + result._intensity = intensity; + } + } + + return result; +} + std::optional WallPaper::Create(const MTPWallPaper &data) { return data.match([](const MTPDwallPaper &data) { return Create(data); @@ -138,6 +273,8 @@ std::optional WallPaper::Create(const MTPWallPaper &data) { } std::optional WallPaper::Create(const MTPDwallPaper &data) { + using Flag = MTPDwallPaper::Flag; + const auto document = Auth().data().processDocument( data.vdocument); if (!document->checkWallPaperProperties()) { @@ -150,14 +287,21 @@ std::optional WallPaper::Create(const MTPDwallPaper &data) { result._document = document; result._thumbnail = document->thumbnail(); if (data.has_settings()) { + const auto isPattern = ((result._flags & Flag::f_pattern) != 0); data.vsettings.match([&](const MTPDwallPaperSettings &data) { + using Flag = MTPDwallPaperSettings::Flag; + result._settings = data.vflags.v; - if (data.has_background_color()) { + if (isPattern && data.has_background_color()) { result._backgroundColor = MaybeColorFromSerialized( data.vbackground_color.v); + } else { + result._settings &= ~Flag::f_background_color; } - if (data.has_intensity()) { + if (isPattern && data.has_intensity()) { result._intensity = data.vintensity.v; + } else { + result._settings &= ~Flag::f_intensity; } }); } @@ -203,7 +347,6 @@ std::optional WallPaper::FromSerialized( auto settings = qint32(); auto backgroundColor = quint32(); auto intensity = qint32(); - auto documentId = quint64(); auto stream = QDataStream(serialized); stream.setVersion(QDataStream::Qt_5_1); @@ -214,8 +357,7 @@ std::optional WallPaper::FromSerialized( >> slug >> settings >> backgroundColor - >> intensity - >> documentId; + >> intensity; if (stream.status() != QDataStream::Ok) { return std::nullopt; } else if (intensity < 0 || intensity > 100) { @@ -243,7 +385,7 @@ std::optional WallPaper::FromLegacySerialized( result._accessHash = accessHash; result._flags = MTPDwallPaper::Flags::from_raw(flags); result._slug = slug; - result._backgroundColor = Window::Theme::GetWallPaperColor(slug); + result._backgroundColor = ColorFromString(slug); if (!ValidateFlags(result._flags)) { return std::nullopt; } @@ -259,7 +401,7 @@ std::optional WallPaper::FromLegacyId(qint32 legacyId) { } std::optional WallPaper::FromColorSlug(const QString &slug) { - if (const auto color = Window::Theme::GetWallPaperColor(slug)) { + if (const auto color = ColorFromString(slug)) { auto result = CustomWallPaper(); result._slug = slug; result._backgroundColor = color; @@ -707,6 +849,11 @@ void ChatBackground::start() { void ChatBackground::setImage( const Data::WallPaper &paper, QImage &&image) { + if (image.format() != QImage::Format_ARGB32_Premultiplied) { + image = std::move(image).convertToFormat( + QImage::Format_ARGB32_Premultiplied); + } + const auto needResetAdjustable = Data::IsDefaultWallPaper(paper) && !Data::IsDefaultWallPaper(_paper) && !nightMode() @@ -744,7 +891,7 @@ void ChatBackground::setImage( Qt::SmoothTransformation); } } else if (Data::IsDefaultWallPaper(_paper) - || (!color() && image.isNull())) { + || (!_paper.backgroundColor() && image.isNull())) { setPaper(Data::DefaultWallPaper()); image.load(qsl(":/gui/art/bg.jpg")); } @@ -754,15 +901,26 @@ void ChatBackground::setImage( || Data::IsLegacy1DefaultWallPaper(_paper)) ? QImage() : image)); - if (const auto fill = color()) { - if (adjustPaletteRequired()) { - adjustPaletteUsingColor(*fill); + if (const auto fill = _paper.backgroundColor()) { + if (_paper.isPattern() && !image.isNull()) { + setPreparedImage(Data::PreparePatternImage( + std::move(image), + *fill, + PatternColor(*fill), + _paper.patternIntensity())); + } else { + _pixmap = QPixmap(); + _pixmapForTiled = QPixmap(); + if (adjustPaletteRequired()) { + adjustPaletteUsingColor(*fill); + } } } else { setPreparedImage(prepareBackgroundImage(std::move(image))); } } - Assert((!_pixmap.isNull() && !_pixmapForTiled.isNull()) || color()); + Assert((!_pixmap.isNull() && !_pixmapForTiled.isNull()) + || colorForFill()); notify(BackgroundUpdate(BackgroundUpdate::Type::New, tile())); if (needResetAdjustable) { @@ -865,15 +1023,19 @@ void ChatBackground::adjustPaletteUsingBackground(const QImage &img) { } void ChatBackground::adjustPaletteUsingColor(QColor color) { - auto hue = color.hslHueF(); - auto saturation = color.hslSaturationF(); + const auto hue = color.hslHueF(); + const auto saturation = color.hslSaturationF(); for (const auto &color : _adjustableColors) { adjustColor(color.item, hue, saturation); } } +std::optional ChatBackground::colorForFill() const { + return _pixmap.isNull() ? _paper.backgroundColor() : std::nullopt; +} + QImage ChatBackground::createCurrentImage() const { - if (const auto fill = color()) { + if (const auto fill = colorForFill()) { auto result = QImage( kMinimumTiledSize, kMinimumTiledSize, @@ -881,7 +1043,7 @@ QImage ChatBackground::createCurrentImage() const { result.fill(*fill); return result; } - return pixmap().toImage(); + return pixmap().toImage(); // #TODO patterns } bool ChatBackground::tile() const { @@ -909,7 +1071,7 @@ bool ChatBackground::tileNight() const { } void ChatBackground::ensureStarted() { - if (_pixmap.isNull()) { + if (_pixmap.isNull() && !_paper.backgroundColor()) { // We should start first, otherwise the default call // to start() will reset this value to _themeTile. start(); @@ -1019,6 +1181,7 @@ void ChatBackground::setTestingTheme(Instance &&theme) { setTile(theme.tiled); } else { // Apply current background image so that service bg colors are recounted. + // #TODO patterns setImage(_paper, std::move(_pixmap).toImage()); } notify(BackgroundUpdate(BackgroundUpdate::Type::TestingTheme, tile()), true); @@ -1086,7 +1249,7 @@ void ChatBackground::writeNewBackgroundSettings() { ((Data::IsThemeWallPaper(_paper) || Data::IsDefaultWallPaper(_paper)) ? QImage() - : _pixmap.toImage())); + : _pixmap.toImage())); // #TODO patterns } void ChatBackground::revert() { @@ -1097,6 +1260,7 @@ void ChatBackground::revert() { setImage(_paperForRevert, std::move(_imageForRevert)); } else { // Apply current background image so that service bg colors are recounted. + // #TODO patterns setImage(_paper, std::move(_pixmap).toImage()); } notify(BackgroundUpdate(BackgroundUpdate::Type::RevertingTheme, tile()), true); @@ -1335,35 +1499,6 @@ bool IsPaletteTestingPath(const QString &path) { return false; } -std::optional GetWallPaperColor(const QString &slug) { - if (slug.size() != 6) { - return {}; - } else if (ranges::find_if(slug, [](QChar ch) { - return (ch < 'a' || ch > 'f') - && (ch < 'A' || ch > 'F') - && (ch < '0' || ch > '9'); - }) != slug.end()) { - return {}; - } - const auto component = [](const QString &text, int index) { - const auto decimal = [](QChar hex) { - const auto code = hex.unicode(); - return (code >= '0' && code <= '9') - ? int(code - '0') - : (code >= 'a' && code <= 'f') - ? int(code - 'a' + 0x0a) - : int(code - 'A' + 0x0a); - }; - index *= 2; - return decimal(text[index]) * 0x10 + decimal(text[index + 1]); - }; - return QColor( - component(slug, 0), - component(slug, 1), - component(slug, 2), - 255); -} - void ComputeBackgroundRects(QRect wholeFill, QSize imageSize, QRect &to, QRect &from) { if (uint64(imageSize.width()) * wholeFill.height() > uint64(imageSize.height()) * wholeFill.width()) { float64 pxsize = wholeFill.height() / float64(imageSize.height()); @@ -1444,5 +1579,19 @@ bool ReadPaletteValues(const QByteArray &content, Fn 0.5 + ? std::max(0., value * 0.65) + : std::max(0., std::min(1., 1. - value * 0.65))), + 0.4 + ).toRgb(); +} + } // namespace Theme } // namespace Window diff --git a/Telegram/SourceFiles/window/themes/window_theme.h b/Telegram/SourceFiles/window/themes/window_theme.h index a8cd498df..4b7646806 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.h +++ b/Telegram/SourceFiles/window/themes/window_theme.h @@ -23,6 +23,10 @@ public: [[nodiscard]] std::optional backgroundColor() const; [[nodiscard]] DocumentData *document() const; [[nodiscard]] Image *thumbnail() const; + [[nodiscard]] bool isPattern() const; + [[nodiscard]] bool isDefault() const; + [[nodiscard]] bool isCreator() const; + [[nodiscard]] int patternIntensity() const; [[nodiscard]] bool hasShareUrl() const; [[nodiscard]] QString shareUrl() const; @@ -30,6 +34,9 @@ public: void loadThumbnail() const; [[nodiscard]] FileOrigin fileOrigin() const; + [[nodiscard]] WallPaper withUrlParams( + const QMap ¶ms) const; + [[nodiscard]] static std::optional Create( const MTPWallPaper &data); [[nodiscard]] static std::optional Create( @@ -49,6 +56,8 @@ public: const QString &slug); private: + static constexpr auto kDefaultIntensity = 40; + WallPaperId _id = WallPaperId(); uint64 _accessHash = 0; MTPDwallPaper::Flags _flags; @@ -56,7 +65,7 @@ private: MTPDwallPaperSettings::Flags _settings; std::optional _backgroundColor; - int _intensity = 40; + int _intensity = kDefaultIntensity; DocumentData *_document = nullptr; Image *_thumbnail = nullptr; @@ -138,7 +147,7 @@ void Revert(); bool LoadFromFile(const QString &file, Instance *out, QByteArray *outContent); bool IsPaletteTestingPath(const QString &path); -[[nodiscard]] std::optional GetWallPaperColor(const QString &slug); +QColor PatternColor(QColor background); struct BackgroundUpdate { enum class Type { @@ -190,9 +199,7 @@ public: [[nodiscard]] const QPixmap &pixmapForTiled() const { return _pixmapForTiled; } - [[nodiscard]] std::optional color() const { - return _paper.backgroundColor(); - } + [[nodiscard]] std::optional colorForFill() const; [[nodiscard]] QImage createCurrentImage() const; [[nodiscard]] bool tile() const; [[nodiscard]] bool tileDay() const;