Start gift theme background parsing.

This commit is contained in:
John Preston
2025-08-26 22:11:48 +04:00
parent 9e8ae54821
commit 4419ba55e8
13 changed files with 207 additions and 106 deletions

View File

@@ -39,44 +39,38 @@ CloudTheme CloudTheme::Parse(
bool parseSettings) {
const auto document = data.vdocument();
const auto paper = [&](const MTPThemeSettings &settings) {
return settings.match([&](const MTPDthemeSettings &data) {
return data.vwallpaper()
? WallPaper::Create(session, *data.vwallpaper())
: std::nullopt;
});
const auto &data = settings.data();
return data.vwallpaper()
? WallPaper::Create(session, *data.vwallpaper())
: std::nullopt;
};
const auto outgoingMessagesColors = [&](
const MTPThemeSettings &settings) {
auto result = std::vector<QColor>();
settings.match([&](const MTPDthemeSettings &data) {
if (const auto colors = data.vmessage_colors()) {
for (const auto &color : colors->v) {
result.push_back(Ui::ColorFromSerialized(color));
}
const auto &data = settings.data();
if (const auto colors = data.vmessage_colors()) {
for (const auto &color : colors->v) {
result.push_back(Ui::ColorFromSerialized(color));
}
});
}
return result;
};
const auto accentColor = [&](const MTPThemeSettings &settings) {
return settings.match([&](const MTPDthemeSettings &data) {
return Ui::ColorFromSerialized(data.vaccent_color());
});
const auto &data = settings.data();
return Ui::ColorFromSerialized(data.vaccent_color());
};
const auto outgoingAccentColor = [&](const MTPThemeSettings &settings) {
return settings.match([&](const MTPDthemeSettings &data) {
return Ui::MaybeColorFromSerialized(data.voutbox_accent_color());
});
const auto &data = settings.data();
return Ui::MaybeColorFromSerialized(data.voutbox_accent_color());
};
const auto basedOnDark = [&](const MTPThemeSettings &settings) {
return settings.match([&](const MTPDthemeSettings &data) {
return data.vbase_theme().match([](
const MTPDbaseThemeNight &) {
return true;
}, [](const MTPDbaseThemeTinted &) {
return true;
}, [](const auto &) {
return false;
});
const auto &data = settings.data();
return data.vbase_theme().match([](const MTPDbaseThemeNight &) {
return true;
}, [](const MTPDbaseThemeTinted &) {
return true;
}, [](const auto &) {
return false;
});
};
const auto settings = [&] {
@@ -122,45 +116,48 @@ CloudTheme CloudTheme::Parse(
return {};
}
const auto paper = [&](const MTPThemeSettings &settings) {
return settings.match([&](const MTPDthemeSettings &data) {
return data.vwallpaper()
? WallPaper::Create(session, *data.vwallpaper())
: std::nullopt;
const auto &data = settings.data();
return data.vwallpaper()
? WallPaper::Create(session, *data.vwallpaper())
: std::nullopt;
};
const auto basedOnDark = [&](const MTPThemeSettings &settings) {
const auto &data = settings.data();
return data.vbase_theme().match([](const MTPDbaseThemeNight &) {
return true;
}, [](const MTPDbaseThemeTinted &) {
return true;
}, [](const auto &) {
return false;
});
};
const auto outgoingMessagesColors = [&](
const MTPThemeSettings &settings) {
auto result = std::vector<QColor>();
settings.match([&](const MTPDthemeSettings &data) {
if (const auto colors = data.vmessage_colors()) {
for (const auto &color : colors->v) {
result.push_back(Ui::ColorFromSerialized(color));
}
const auto &data = settings.data();
if (const auto colors = data.vmessage_colors()) {
for (const auto &color : colors->v) {
result.push_back(Ui::ColorFromSerialized(color));
}
});
//} else if (basedOnDark(settings)) {
// result.push_back(gift->unique->backdrop.edgeColor);
//} else {
// result.push_back(anim::color(
// gift->unique->backdrop.patternColor,
// QColor(255, 255, 255, 255),
// 0.75));
}
return result;
};
const auto accentColor = [&](const MTPThemeSettings &settings) {
return settings.match([&](const MTPDthemeSettings &data) {
return Ui::ColorFromSerialized(data.vaccent_color());
});
const auto &data = settings.data();
return Ui::ColorFromSerialized(data.vaccent_color());
};
const auto outgoingAccentColor = [&](const MTPThemeSettings &settings) {
return settings.match([&](const MTPDthemeSettings &data) {
return Ui::MaybeColorFromSerialized(data.voutbox_accent_color());
});
};
const auto basedOnDark = [&](const MTPThemeSettings &settings) {
return settings.match([&](const MTPDthemeSettings &data) {
return data.vbase_theme().match([](
const MTPDbaseThemeNight &) {
return true;
}, [](const MTPDbaseThemeTinted &) {
return true;
}, [](const auto &) {
return false;
});
});
const auto &data = settings.data();
return Ui::MaybeColorFromSerialized(
data.voutbox_accent_color()
);// .value_or(gift->unique->backdrop.patternColor);
};
const auto settings = [&] {
auto result = base::flat_map<Type, Settings>();
@@ -538,7 +535,7 @@ void CloudThemes::myGiftThemesLoadMore(bool reload) {
});
}
_myGiftThemesLoaded = (got < kGiftThemesLimit);
_chatThemesUpdates.fire({});
_myGiftThemesUpdates.fire({});
}, [&](const MTPDaccount_chatThemesNotModified &) {
if (!reload) {
_myGiftThemesLoaded = true;

View File

@@ -653,7 +653,11 @@ bool DocumentData::checkWallPaperProperties() {
if (type == WallPaperDocument) {
return true;
}
if (type != FileDocument
if (hasMimeType(u"application/x-tgwallpattern"_q)
&& dimensions.isEmpty()) {
dimensions = QSize(1125, 2313);
AssertIsDebug();
} else if (type != FileDocument
|| !hasThumbnail()
|| dimensions.isEmpty()
|| dimensions.width() > Storage::kMaxWallPaperDimension

View File

@@ -169,7 +169,7 @@ base::binary_guard ReadBackgroundImageAsync(
guard = result.make_guard(),
callback = std::move(done)
]() mutable {
auto image = Ui::ReadBackgroundImage(path, bytes, gzipSvg);
auto image = Ui::ReadBackgroundImage(path, bytes, gzipSvg).image;
if (postprocess) {
image = postprocess(std::move(image));
}

View File

@@ -1739,7 +1739,8 @@ PeerId PeerData::groupCallDefaultJoinAs() const {
void PeerData::setThemeToken(const QString &token) {
if (_themeToken == token) {
return;
} else if (Ui::Emoji::Find(_themeToken) == Ui::Emoji::Find(token)) {
} else if (!token.startsWith(u"gift:"_q)
&& Ui::Emoji::Find(_themeToken) == Ui::Emoji::Find(token)) {
_themeToken = token;
return;
}

View File

@@ -415,6 +415,7 @@ void ChatTheme::setBackground(ChatThemeBackground &&background) {
void ChatTheme::updateBackgroundImageFrom(ChatThemeBackground &&background) {
_mutableBackground.key = background.key;
_mutableBackground.prepared = std::move(background.prepared);
_mutableBackground.giftSymbols = std::move(background.giftSymbols);
_mutableBackground.preparedForTiled = std::move(
background.preparedForTiled);
if (!_backgroundState.now.pixmap.isNull()) {
@@ -905,19 +906,83 @@ QImage PrepareImageForTiled(const QImage &prepared) {
return result;
}
[[nodiscard]] QImage ReadBackgroundImage(
std::vector<QRectF> ParseGiftSymbols(
const QByteArray &rects,
QSize size,
float64 scale) {
if (size.isEmpty()) {
return {};
}
auto result = std::vector<QRectF>();
auto offset = 0;
const auto jumpPast = [&](const QByteArray &what) {
const auto pos = rects.indexOf(what, offset);
if (pos < 0) {
return false;
}
offset = pos + what.size();
return true;
};
const auto takeDouble = [&](float64 &value) {
const auto end = rects.indexOf('"', offset);
if (end < 0) {
return false;
}
value = rects.mid(offset, end - offset).toDouble();
offset = end + 1;
return true;
};
const auto cw = scale / float64(size.width());
const auto ch = scale / float64(size.height());
while (true) {
auto x = float64(), y = float64(), w = float64(), h = float64();
if (!jumpPast("<rect "_q)
|| !jumpPast("x=\""_q)
|| !takeDouble(x)
|| !jumpPast("y=\""_q)
|| !takeDouble(y)
|| !jumpPast("width=\""_q)
|| !takeDouble(w)
|| !jumpPast("height=\""_q)
|| !takeDouble(h)
|| !jumpPast("/>"_q)) {
break;
}
result.push_back({
x * cw,
y * ch,
w * cw,
h * ch,
});
}
return result;
}
BackgroundImageFields ReadBackgroundImage(
const QString &path,
const QByteArray &content,
bool gzipSvg) {
auto result = Images::Read({
bool gzipSvg,
bool findGiftSymbols) {
auto read = Images::Read({
.path = path,
.content = content,
.svgCutOutId = findGiftSymbols ? "GiftPatterns"_q : QByteArray(),
.maxSize = QSize(kMaxSize, kMaxSize),
.gzipSvg = gzipSvg,
}).image;
if (result.isNull()) {
result = QImage(1, 1, QImage::Format_ARGB32_Premultiplied);
result.fill(Qt::black);
});
if (read.image.isNull()) {
read.image = QImage(1, 1, QImage::Format_ARGB32_Premultiplied);
read.image.fill(Qt::black);
}
auto result = BackgroundImageFields{
.image = std::move(read.image),
};
if (const auto &rects = read.svgCutOutContent; !rects.isEmpty()) {
result.giftSymbols = ParseGiftSymbols(
rects,
result.image.size(),
read.scale);
}
return result;
}
@@ -1022,9 +1087,16 @@ QImage GenerateDitheredGradient(
ChatThemeBackground PrepareBackgroundImage(
const ChatThemeBackgroundData &data) {
auto prepared = (data.isPattern || data.colors.empty())
? PreprocessBackgroundImage(
ReadBackgroundImage(data.path, data.bytes, data.gzipSvg))
const auto needBackground = (data.isPattern || data.colors.empty());
auto read = needBackground
? ReadBackgroundImage(
data.path,
data.bytes,
data.gzipSvg,
data.findGiftSymbols)
: BackgroundImageFields();
auto prepared = needBackground
? PreprocessBackgroundImage(std::move(read.image))
: QImage();
if (data.isPattern && !prepared.isNull()) {
if (data.colors.size() < 2) {
@@ -1070,6 +1142,7 @@ ChatThemeBackground PrepareBackgroundImage(
? std::nullopt
: std::make_optional(data.colors.front())),
.colors = data.colors,
.giftSymbols = std::move(read.giftSymbols),
.patternOpacity = data.patternOpacity,
.gradientRotation = data.generateGradient ? data.gradientRotation : 0,
.isPattern = data.isPattern,

View File

@@ -29,6 +29,7 @@ struct ChatThemeBackground {
QImage gradientForFill;
std::optional<QColor> colorForFill;
std::vector<QColor> colors;
std::vector<QRectF> giftSymbols;
float64 patternOpacity = 1.;
int gradientRotation = 0;
bool isPattern = false;
@@ -54,6 +55,7 @@ struct ChatThemeBackgroundData {
bool isBlurred = false;
bool forDarkMode = false;
bool generateGradient = false;
bool findGiftSymbols = false;
int gradientRotation = 0;
};
@@ -238,10 +240,15 @@ struct ChatBackgroundRects {
const QImage &image);
[[nodiscard]] QImage PrepareImageForTiled(const QImage &prepared);
[[nodiscard]] QImage ReadBackgroundImage(
struct BackgroundImageFields {
QImage image;
std::vector<QRectF> giftSymbols;
};
[[nodiscard]] BackgroundImageFields ReadBackgroundImage(
const QString &path,
const QByteArray &content,
bool gzipSvg);
bool gzipSvg,
bool findGiftSymbols = false);
[[nodiscard]] QImage GenerateBackgroundImage(
QSize size,
const std::vector<QColor> &bg,

View File

@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "window/window_session_controller.h"
#include "window/themes/window_theme.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_session.h"
#include "data/data_peer.h"
#include "data/data_cloud_themes.h"
@@ -146,10 +147,12 @@ const auto kDisableElement = [] { return u"disable"_q; };
} // namespace
struct ChooseThemeController::Entry {
QString token;
Ui::ChatThemeKey key;
std::shared_ptr<Ui::ChatTheme> theme;
std::shared_ptr<Data::DocumentMedia> media;
std::shared_ptr<Data::UniqueGift> gift;
std::unique_ptr<Ui::Text::CustomEmoji> custom;
EmojiPtr emoji = nullptr;
QImage preview;
QRect geometry;
@@ -341,14 +344,22 @@ void ChooseThemeController::paintEntry(QPainter &p, const Entry &entry) {
const auto size = Ui::Emoji::GetSizeLarge();
const auto factor = style::DevicePixelRatio();
const auto emojiLeft = geometry.x()
+ (geometry.width() - (size / factor)) / 2;
const auto esize = size / factor;
const auto emojiLeft = geometry.x() + (geometry.width() - esize) / 2;
const auto emojiTop = geometry.y()
+ geometry.height()
- (size / factor)
- esize
- st::chatThemeEmojiBottom;
const auto customSize = Ui::Text::AdjustCustomEmojiSize(esize);
const auto customSkip = (esize - customSize) / 2;
if (const auto emoji = entry.emoji) {
Ui::Emoji::Draw(p, emoji, size, emojiLeft, emojiTop);
} else if (const auto custom = entry.custom.get()) {
custom->paint(p, {
.textColor = st::windowFg->c,
.position = { emojiLeft + customSkip, emojiTop + customSkip },
});
}
if (entry.chosen) {
@@ -395,7 +406,7 @@ void ChooseThemeController::initList() {
if (!entry) {
return QString();
} else if (entry->key) {
return entry->emoji->text();
return entry->token;
} else {
return kDisableElement();
}
@@ -445,7 +456,7 @@ void ChooseThemeController::initList() {
_controller->overridePeerTheme(
_peer,
entry->theme,
entry->emoji);
entry->token);
}
_inner->update();
}
@@ -508,7 +519,7 @@ void ChooseThemeController::updateInnerLeft(int now) {
void ChooseThemeController::close() {
if (const auto chosen = findChosen()) {
if (Ui::Emoji::Find(_peer->themeToken()) != chosen->emoji) {
if (_peer->themeToken() != chosen->token) {
clearCurrentBackgroundState();
}
}
@@ -531,7 +542,7 @@ auto ChooseThemeController::findChosen() -> Entry* {
for (auto &entry : _entries) {
if (!entry.key && chosen == kDisableElement()) {
return &entry;
} else if (chosen == entry.emoji->text()) {
} else if (chosen == entry.token) {
return &entry;
}
}
@@ -547,18 +558,9 @@ void ChooseThemeController::fill(
if (themes.empty()) {
return;
}
const auto count = int(themes.size()) + 1;
const auto single = st::chatThemePreviewSize;
const auto skip = st::chatThemeEntrySkip;
const auto &margin = st::chatThemeEntryMargin;
const auto full = margin.left()
+ single.width() * count
+ skip * (count - 1)
+ margin.right();
_inner->resize(
full,
margin.top() + single.height() + margin.bottom());
const auto initial = Ui::Emoji::Find(_peer->themeToken());
if (!initial) {
_chosen = kDisableElement();
@@ -601,9 +603,11 @@ void ChooseThemeController::fill(
if (!emoji || !theme.settings.contains(type)) {
continue;
}
const auto token = emoji->text();
const auto key = ChatThemeKey{ theme.id, dark };
const auto isChosen = (_chosen.current() == emoji->text());
const auto isChosen = (_chosen.current() == token);
_entries.push_back({
.token = token,
.key = key,
.emoji = emoji,
.geometry = QRect(QPoint(x, skip), single),
@@ -624,13 +628,11 @@ void ChooseThemeController::fill(
return;
}
const auto theme = data.get();
const auto token = i->token;
i->theme = std::move(data);
i->preview = GeneratePreview(theme);
if (_chosen.current() == i->emoji->text()) {
_controller->overridePeerTheme(
_peer,
i->theme,
i->emoji);
if (_chosen.current() == token) {
_controller->overridePeerTheme(_peer, i->theme, token);
}
_inner->update();
@@ -661,17 +663,24 @@ void ChooseThemeController::fill(
}, _cachingLifetime);
x += single.width() + skip;
}
const auto owner = &_controller->session().data();
const auto manager = &owner->customEmojiManager();
for (const auto &token : cloudThemes->myGiftThemesTokens()) {
const auto found = cloudThemes->themeForToken(token);
if (!found || found->settings.contains(type)) {
if (!found || !found->settings.contains(type)) {
continue;
}
const auto &theme = *found;
const auto key = ChatThemeKey{ theme.id, dark };
const auto isChosen = (_chosen.current() == token);
_entries.push_back({
.token = token,
.key = key,
.gift = found->unique,
.custom = manager->create(
found->unique->model.document,
[=] { _inner->update(); },
Data::CustomEmojiSizeTag::Large),
.geometry = QRect(QPoint(x, skip), single),
.chosen = isChosen,
});
@@ -690,13 +699,11 @@ void ChooseThemeController::fill(
return;
}
const auto theme = data.get();
const auto token = i->token;
i->theme = std::move(data);
i->preview = GeneratePreview(theme);
if (_chosen.current() == i->emoji->text()) {
_controller->overridePeerTheme(
_peer,
i->theme,
i->emoji);
if (_chosen.current() == token) {
_controller->overridePeerTheme(_peer, i->theme, token);
}
_inner->update();
@@ -728,6 +735,11 @@ void ChooseThemeController::fill(
x += single.width() + skip;
}
const auto full = x - skip + margin.right();
_inner->resize(
full,
margin.top() + single.height() + margin.bottom());
if (!_initialInnerLeftApplied && _content->width() > 0) {
applyInitialInnerLeft();
}

View File

@@ -507,7 +507,7 @@ auto ChatThemeValueFromPeer(
std::shared_ptr<Ui::ChatTheme> &&cloud,
PeerThemeOverride &&overriden) {
return (overriden.peer == peer.get()
&& Ui::Emoji::Find(peer->themeToken()) != overriden.emoji)
&& peer->themeToken() != overriden.token)
? std::move(overriden.theme)
: std::move(cloud);
});

View File

@@ -74,7 +74,8 @@ inline bool AreTestingTheme() {
return Ui::ReadBackgroundImage(
u":/gui/art/background.tgv"_q,
QByteArray(),
true);
true
).image;
}
[[nodiscard]] bool GoodImageFormatAndSize(const QImage &image) {

View File

@@ -468,7 +468,8 @@ void Generator::paintHistoryBackground() {
background = Ui::ReadBackgroundImage(
u":/gui/art/background.tgv"_q,
QByteArray(),
true);
true
).image;
const auto paper = Data::DefaultWallPaper();
background = Ui::PreparePatternImage(
std::move(background),

View File

@@ -1506,6 +1506,7 @@ struct SessionController::CachedTheme {
std::weak_ptr<Ui::ChatTheme> theme;
std::shared_ptr<Data::DocumentMedia> media;
Data::WallPaper paper;
bool findGiftSymbols = false;
bool basedOnDark = false;
bool caching = false;
rpl::lifetime lifetime;
@@ -3248,11 +3249,11 @@ void SessionController::clearCachedChatThemes() {
void SessionController::overridePeerTheme(
not_null<PeerData*> peer,
std::shared_ptr<Ui::ChatTheme> theme,
EmojiPtr emoji) {
QString token) {
_peerThemeOverride = PeerThemeOverride{
peer,
theme ? theme : _defaultChatTheme,
emoji,
token,
};
}
@@ -3294,12 +3295,14 @@ void SessionController::cacheChatTheme(
const auto &use = !paper.isNull() ? paper : *i->second.paper;
const auto document = use.document();
const auto media = document ? document->createMediaView() : nullptr;
const auto findGiftSymbols = (data.unique != nullptr);
use.loadDocument();
auto &theme = [&]() -> CachedTheme& {
const auto i = _customChatThemes.find(key);
if (i != end(_customChatThemes)) {
i->second.media = media;
i->second.paper = use;
i->second.findGiftSymbols = findGiftSymbols;
i->second.basedOnDark = dark;
i->second.caching = true;
return i->second;
@@ -3309,6 +3312,7 @@ void SessionController::cacheChatTheme(
CachedTheme{
.media = media,
.paper = use,
.findGiftSymbols = findGiftSymbols,
.basedOnDark = dark,
.caching = true,
}).first->second;
@@ -3428,6 +3432,7 @@ Ui::ChatThemeBackgroundData SessionController::backgroundData(
.isBlurred = isBlurred,
.forDarkMode = theme.basedOnDark,
.generateGradient = generateGradient,
.findGiftSymbols = theme.findGiftSymbols,
.gradientRotation = gradientRotation,
};
}

View File

@@ -107,7 +107,7 @@ struct SeparateId;
struct PeerThemeOverride {
PeerData *peer = nullptr;
std::shared_ptr<Ui::ChatTheme> theme;
EmojiPtr emoji = nullptr;
QString token;
};
bool operator==(const PeerThemeOverride &a, const PeerThemeOverride &b);
bool operator!=(const PeerThemeOverride &a, const PeerThemeOverride &b);
@@ -642,7 +642,7 @@ public:
void overridePeerTheme(
not_null<PeerData*> peer,
std::shared_ptr<Ui::ChatTheme> theme,
EmojiPtr emoji);
QString token);
void clearPeerThemeOverride(not_null<PeerData*> peer);
[[nodiscard]] auto peerThemeOverrideValue() const
-> rpl::producer<PeerThemeOverride> {