Show gift sticker in the background.

This commit is contained in:
John Preston
2025-08-30 00:37:29 +04:00
parent e792088ceb
commit 76cc59acab
5 changed files with 161 additions and 25 deletions
+79 -18
View File
@@ -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<QRectF> &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,
@@ -35,6 +35,7 @@ struct ChatThemeBackground {
std::vector<QColor> colors;
std::vector<QRectF> 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<QColor> 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<Text::CustomEmoji> gift;
bool waitingForNegativePattern = false;
};
+64 -5
View File
@@ -325,12 +325,33 @@ void SectionWidget::PaintBackground(
not_null<Ui::ChatTheme*> theme,
not_null<QWidget*> 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<Ui::Text::LimitedLoopsEmoji>(
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<QWidget*> 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<Ui::ChatTheme*> 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();
+4 -2
View File
@@ -207,12 +207,14 @@ public:
not_null<QWidget*> widget,
int fillHeight,
int fromy,
QRect clip);
QRect clip,
bool paused = false);
static void PaintBackground(
QPainter &p,
not_null<Ui::ChatTheme*> theme,
QSize fill,
QRect clip);
QRect clip,
bool paused = false);
protected:
void paintEvent(QPaintEvent *e) override;
@@ -1506,6 +1506,7 @@ struct SessionController::CachedTheme {
std::weak_ptr<Ui::ChatTheme> theme;
std::shared_ptr<Data::DocumentMedia> media;
std::unique_ptr<Ui::Text::CustomEmoji> 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,