mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
Show gift sticker in the background.
This commit is contained in:
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user