diff --git a/Telegram/SourceFiles/ui/chat/chat_theme.cpp b/Telegram/SourceFiles/ui/chat/chat_theme.cpp index c745a0f180..34c7aa367a 100644 --- a/Telegram/SourceFiles/ui/chat/chat_theme.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_theme.cpp @@ -61,7 +61,27 @@ constexpr auto kMinAcceptableContrast = 1.14;// 4.5; const auto b = int(base::SafeRound(symbol.y() * 255)); const auto value = uint16((uint16(a) << 8) | uint16(b)); const auto shuffled = uint16(value << 5) | uint16(value >> 3); - return (shuffled % 90) - 45; + return (shuffled % 60) - 30; +} + +[[nodiscard]] int ChooseGiftSymbolSkip(const std::vector &symbols) { + if (symbols.empty()) { + return -1; + } + auto maxIndex = -1; + auto maxValue = 0.; + for (auto i = 0, count = int(symbols.size()); i != count; ++i) { + const auto &symbol = symbols[i]; + const auto center = symbol.center(); + const auto value = std::min(center.x(), 1. - center.x()) + * std::min(center.y(), 1. - center.y()) + * std::min(symbol.width(), symbol.height()); + if (maxIndex < 0 || maxValue < value) { + maxIndex = i; + maxValue = value; + } + } + return maxIndex; } [[nodiscard]] CacheBackgroundResult CacheBackgroundByRequest( @@ -90,6 +110,8 @@ constexpr auto kMinAcceptableContrast = 1.14;// 4.5; Qt::IgnoreAspectRatio, Qt::SmoothTransformation); result.setDevicePixelRatio(ratio); + auto giftArea = QRect(); + int giftRotation = 0; if (!request.background.prepared.isNull()) { QPainter p(&result); if (!gradient.isNull()) { @@ -115,27 +137,37 @@ constexpr auto kMinAcceptableContrast = 1.14;// 4.5; const auto h = tiled.height() / float(ratio); const auto &giftSymbols = request.background.giftSymbols; + const auto giftSymbolsCount = int(giftSymbols.size()); + const auto giftSymbolSkip = ChooseGiftSymbolSkip(giftSymbols); const auto &giftSymbolFrame = request.background.giftSymbolFrame; const auto giftSymbolSize = giftSymbolFrame.size() / ratio; + const auto giftSymbolRect = [&](const QRectF &symbol) { + return QRectF( + symbol.x() * w, + symbol.y() * h, + symbol.width() * w, + symbol.height() * h); + }; + const auto giftSymbolPaint = [&](QPainter &p, QRectF symbol) { + const auto rect = giftSymbolRect(symbol); + p.save(); + p.translate(rect.center()); + p.scale( + rect.width() / giftSymbolSize.width(), + rect.height() / giftSymbolSize.height()); + p.rotate(RotationForSymbol(symbol)); + p.translate(-rect.center()); + p.drawImage(rect.topLeft(), giftSymbolFrame); + p.restore(); + }; if (hasGiftSymbols) { auto q = QPainter(&tiled); auto hq = PainterHighQualityEnabler(q); q.setOpacity(0.8); - for (const auto &symbol : giftSymbols) { - const auto rect = QRectF( - symbol.x() * w, - symbol.y() * h, - symbol.width() * w, - symbol.height() * h); - q.save(); - q.translate(rect.center()); - q.scale( - rect.width() / giftSymbolSize.width(), - rect.height() / giftSymbolSize.height()); - q.rotate(RotationForSymbol(symbol)); - q.translate(-rect.center()); - q.drawImage(rect.topLeft(), giftSymbolFrame); - q.restore(); + for (auto i = 0; i != giftSymbolsCount; ++i) { + if (i != giftSymbolSkip) { + giftSymbolPaint(q, giftSymbols[i]); + } } } const auto cx = int(std::ceil(request.area.width() / w)); @@ -148,10 +180,33 @@ constexpr auto kMinAcceptableContrast = 1.14;// 4.5; ? (request.area.width() * ratio - cols * tiled.width()) / 2 : 0; const auto useshift = xshift / float(ratio); + const auto drawTile = [&](int x, int y) { + const auto position = QPointF(useshift + x * w, y * h); + p.drawImage(position, tiled); + }; + + // Skip a symbol in the center for the gift itself. + drawTile(cols / 2, 0); + if (hasGiftSymbols) { + const auto &gift = giftSymbols[giftSymbolSkip]; + auto q = QPainter(&tiled); + auto hq = PainterHighQualityEnabler(q); + q.setOpacity(0.8); + giftSymbolPaint(q, gift); + const auto exact = giftSymbolRect(gift); + giftArea = QRect( + useshift + (cols / 2) * w + exact.x(), + exact.y(), + exact.width(), + exact.height()); + giftRotation = RotationForSymbol(gift); + } + for (auto y = 0; y != rows; ++y) { for (auto x = 0; x != cols; ++x) { - const auto position = QPointF(useshift + x * w, y * h); - p.drawImage(position, tiled); + if (y || x != (cols / 2)) { + drawTile(x, y); + } } } if (!gradient.isNull() @@ -167,6 +222,8 @@ constexpr auto kMinAcceptableContrast = 1.14;// 4.5; QImage::Format_ARGB32_Premultiplied), .gradient = gradient, .area = request.area, + .giftArea = giftArea, + .giftRotation = giftRotation, .waitingForNegativePattern = request.background.waitingForNegativePattern() }; @@ -239,6 +296,8 @@ CachedBackground::CachedBackground(CacheBackgroundResult &&result) , area(result.area) , x(result.x) , y(result.y) +, giftArea(result.giftArea) +, giftRotation(result.giftRotation) , waitingForNegativePattern(result.waitingForNegativePattern) { } @@ -456,6 +515,7 @@ void ChatTheme::updateBackgroundImageFrom(ChatThemeBackground &&background) { _mutableBackground.key = background.key; _mutableBackground.prepared = std::move(background.prepared); _mutableBackground.giftSymbols = std::move(background.giftSymbols); + _mutableBackground.giftId = background.giftId; _mutableBackground.preparedForTiled = std::move( background.preparedForTiled); if (!_backgroundState.now.pixmap.isNull()) { @@ -1184,6 +1244,7 @@ ChatThemeBackground PrepareBackgroundImage( .colors = data.colors, .giftSymbols = std::move(read.giftSymbols), .giftSymbolFrame = data.giftSymbolFrame, + .giftId = data.giftId, .patternOpacity = data.patternOpacity, .gradientRotation = data.generateGradient ? data.gradientRotation : 0, .isPattern = data.isPattern, diff --git a/Telegram/SourceFiles/ui/chat/chat_theme.h b/Telegram/SourceFiles/ui/chat/chat_theme.h index caa7dfb591..0426455780 100644 --- a/Telegram/SourceFiles/ui/chat/chat_theme.h +++ b/Telegram/SourceFiles/ui/chat/chat_theme.h @@ -35,6 +35,7 @@ struct ChatThemeBackground { std::vector colors; std::vector giftSymbols; QImage giftSymbolFrame; + uint64_t giftId = 0; float64 patternOpacity = 1.; int gradientRotation = 0; bool isPattern = false; @@ -53,6 +54,7 @@ struct ChatThemeBackgroundData { QString path; QByteArray bytes; QImage giftSymbolFrame; + uint64 giftId = 0; bool gzipSvg = false; std::vector colors; bool isPattern = false; @@ -94,6 +96,8 @@ struct CacheBackgroundResult { QSize area; int x = 0; int y = 0; + QRect giftArea; + int giftRotation = 0; bool waitingForNegativePattern = false; }; @@ -108,6 +112,9 @@ struct CachedBackground { QSize area; int x = 0; int y = 0; + QRect giftArea; + int giftRotation = 0; + mutable std::unique_ptr gift; bool waitingForNegativePattern = false; }; diff --git a/Telegram/SourceFiles/window/section_widget.cpp b/Telegram/SourceFiles/window/section_widget.cpp index f61134868d..36fd532c31 100644 --- a/Telegram/SourceFiles/window/section_widget.cpp +++ b/Telegram/SourceFiles/window/section_widget.cpp @@ -325,12 +325,33 @@ void SectionWidget::PaintBackground( not_null theme, not_null widget, QRect clip) { + if (const auto id = theme->background().giftId) { + const auto fillHeight = controller->content()->height(); + const auto fill = QSize(widget->width(), fillHeight); + const auto &state = theme->backgroundState(fill); + const auto make = [&] { + return std::make_unique( + controller->session().data().customEmojiManager().create( + id, + crl::guard(widget, [=] { widget->update(); }), + Data::CustomEmojiSizeTag::Isolated), + 1); + }; + if (!state.was.gift) { + state.was.gift = make(); + } + if (!state.now.gift) { + state.now.gift = make(); + } + } + PaintBackground( theme, widget, controller->content()->height(), controller->content()->backgroundFromY(), - clip); + clip, + controller->isGifPausedAtLeastFor(GifPauseReason::Any)); } void SectionWidget::PaintBackground( @@ -338,37 +359,68 @@ void SectionWidget::PaintBackground( not_null widget, int fillHeight, int fromy, - QRect clip) { + QRect clip, + bool paused) { auto p = QPainter(widget); if (fromy) { p.translate(0, fromy); clip = clip.translated(0, -fromy); } - PaintBackground(p, theme, QSize(widget->width(), fillHeight), clip); + const auto fill = QSize(widget->width(), fillHeight); + PaintBackground(p, theme, fill, clip, paused); } void SectionWidget::PaintBackground( QPainter &p, not_null theme, QSize fill, - QRect clip) { + QRect clip, + bool paused) { const auto &background = theme->background(); if (background.colorForFill) { p.fillRect(clip, *background.colorForFill); return; } const auto &gradient = background.gradientForFill; - auto state = theme->backgroundState(fill); + const auto &state = theme->backgroundState(fill); const auto paintCache = [&](const Ui::CachedBackground &cache) { const auto to = QRect( QPoint(cache.x, cache.y), cache.pixmap.size() / style::DevicePixelRatio()); + const auto paintGift = [&](QRect area) { + if (!cache.gift) { + return; + } + auto hq = PainterHighQualityEnabler(p); + const auto center = area.center(); + const auto size = Data::FrameSizeFromTag( + Data::CustomEmojiSizeTag::Isolated + ) / style::DevicePixelRatio(); + p.save(); + p.translate(center); + p.rotate(cache.giftRotation); + p.translate(-center); + p.setOpacity(0.5); + cache.gift->paint(p, { + .textColor = st::windowFg->c, + .size = QSize(size, size), + .now = crl::now(), + .scale = (area.width() / float64(size)), + .position = area.topLeft(), + .paused = paused, + .scaled = true, + }); + p.restore(); + }; if (cache.waitingForNegativePattern) { // While we wait for pattern being loaded we paint just gradient. // But in case of negative patter opacity we just fill-black. p.fillRect(to, Qt::black); } else if (cache.area == fill) { p.drawPixmap(to, cache.pixmap); + if (background.giftId && !cache.giftArea.isEmpty()) { + paintGift(cache.giftArea.translated(to.topLeft())); + } } else { const auto sx = fill.width() / float64(cache.area.width()); const auto sy = fill.height() / float64(cache.area.height()); @@ -384,6 +436,13 @@ void SectionWidget::PaintBackground( round((to.x() + to.width()) * sx) - sto.x(), round((to.y() + to.height()) * sy) - sto.y(), cache.pixmap); + if (background.giftId && !cache.giftArea.isEmpty()) { + paintGift(QRect( + (to.x() + cache.giftArea.x()) * sx, + (to.y() + cache.giftArea.y()) * sy, + cache.giftArea.width() * sx, + cache.giftArea.height() * sy)); + } } }; const auto hasNow = !state.now.pixmap.isNull(); diff --git a/Telegram/SourceFiles/window/section_widget.h b/Telegram/SourceFiles/window/section_widget.h index b1d6f41cbc..2d288810bf 100644 --- a/Telegram/SourceFiles/window/section_widget.h +++ b/Telegram/SourceFiles/window/section_widget.h @@ -207,12 +207,14 @@ public: not_null widget, int fillHeight, int fromy, - QRect clip); + QRect clip, + bool paused = false); static void PaintBackground( QPainter &p, not_null theme, QSize fill, - QRect clip); + QRect clip, + bool paused = false); protected: void paintEvent(QPaintEvent *e) override; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 8546ffe054..88274ad5ec 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -1506,6 +1506,7 @@ struct SessionController::CachedTheme { std::weak_ptr theme; std::shared_ptr media; std::unique_ptr giftSymbol; + uint64 giftId = 0; Data::WallPaper paper; bool basedOnDark = false; bool caching = false; @@ -3302,6 +3303,9 @@ void SessionController::cacheChatTheme( crl::guard(this, [=] { _giftSymbolLoaded.fire({}); }), Data::CustomEmojiSizeTag::Large) : nullptr; + const auto giftId = findGiftSymbols + ? data.unique->model.document->id + : uint64(); const auto giftSymbolReady = !giftSymbol || giftSymbol->ready(); use.loadDocument(); auto &theme = [&]() -> CachedTheme& { @@ -3309,6 +3313,7 @@ void SessionController::cacheChatTheme( if (i != end(_customChatThemes)) { i->second.media = media; i->second.giftSymbol = std::move(giftSymbol); + i->second.giftId = giftId; i->second.paper = use; i->second.basedOnDark = dark; i->second.caching = true; @@ -3319,6 +3324,7 @@ void SessionController::cacheChatTheme( CachedTheme{ .media = media, .giftSymbol = std::move(giftSymbol), + .giftId = giftId, .paper = use, .basedOnDark = dark, .caching = true, @@ -3451,6 +3457,7 @@ Ui::ChatThemeBackgroundData SessionController::backgroundData( .path = paperPath, .bytes = paperBytes, .giftSymbolFrame = Ui::PrepareGiftSymbol(theme.giftSymbol), + .giftId = theme.giftId, .gzipSvg = gzipSvg, .colors = colors, .isPattern = isPattern,