mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Move all background helper methods to chat_theme module.
This commit is contained in:
parent
0a1e84ddb2
commit
f5a323e40a
16 changed files with 491 additions and 299 deletions
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "ui/effects/round_checkbox.h"
|
#include "ui/effects/round_checkbox.h"
|
||||||
#include "ui/image/image.h"
|
#include "ui/image/image.h"
|
||||||
|
#include "ui/chat/chat_theme.h"
|
||||||
#include "ui/ui_utility.h"
|
#include "ui/ui_utility.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
@ -331,7 +332,7 @@ void BackgroundBox::Inner::validatePaperThumbnail(
|
||||||
}
|
}
|
||||||
} else if (!paper.data.backgroundColors().empty()) {
|
} else if (!paper.data.backgroundColors().empty()) {
|
||||||
paper.thumbnail = Ui::PixmapFromImage(
|
paper.thumbnail = Ui::PixmapFromImage(
|
||||||
Data::GenerateWallPaper(
|
Ui::GenerateBackgroundImage(
|
||||||
st::backgroundSize * cIntRetinaFactor(),
|
st::backgroundSize * cIntRetinaFactor(),
|
||||||
paper.data.backgroundColors(),
|
paper.data.backgroundColors(),
|
||||||
paper.data.gradientRotation()));
|
paper.data.gradientRotation()));
|
||||||
|
@ -346,7 +347,7 @@ void BackgroundBox::Inner::validatePaperThumbnail(
|
||||||
: paper.dataMedia->thumbnail();
|
: paper.dataMedia->thumbnail();
|
||||||
auto original = thumbnail->original();
|
auto original = thumbnail->original();
|
||||||
if (paper.data.isPattern()) {
|
if (paper.data.isPattern()) {
|
||||||
original = Data::PreparePatternImage(
|
original = Ui::PreparePatternImage(
|
||||||
std::move(original),
|
std::move(original),
|
||||||
paper.data.backgroundColors(),
|
paper.data.backgroundColors(),
|
||||||
paper.data.gradientRotation(),
|
paper.data.gradientRotation(),
|
||||||
|
|
|
@ -341,7 +341,7 @@ bool ServiceCheck::checkRippleStartPosition(QPoint position) const {
|
||||||
Images::Option blur = Images::Option(0)) {
|
Images::Option blur = Images::Option(0)) {
|
||||||
auto result = PrepareScaledNonPattern(image, blur);
|
auto result = PrepareScaledNonPattern(image, blur);
|
||||||
if (isPattern) {
|
if (isPattern) {
|
||||||
result = Data::PreparePatternImage(
|
result = Ui::PreparePatternImage(
|
||||||
std::move(result),
|
std::move(result),
|
||||||
background,
|
background,
|
||||||
gradientRotation,
|
gradientRotation,
|
||||||
|
@ -395,7 +395,7 @@ void BackgroundPreviewBox::generateBackground() {
|
||||||
const auto size = QSize(st::boxWideWidth, st::boxWideWidth)
|
const auto size = QSize(st::boxWideWidth, st::boxWideWidth)
|
||||||
* cIntRetinaFactor();
|
* cIntRetinaFactor();
|
||||||
_generated = Ui::PixmapFromImage((_paper.patternOpacity() >= 0.)
|
_generated = Ui::PixmapFromImage((_paper.patternOpacity() >= 0.)
|
||||||
? Data::GenerateWallPaper(
|
? Ui::GenerateBackgroundImage(
|
||||||
size,
|
size,
|
||||||
_paper.backgroundColors(),
|
_paper.backgroundColors(),
|
||||||
_paper.gradientRotation())
|
_paper.gradientRotation())
|
||||||
|
@ -668,7 +668,7 @@ void BackgroundPreviewBox::setScaledFromThumb() {
|
||||||
auto blurred = (_paper.document() || _paper.isPattern())
|
auto blurred = (_paper.document() || _paper.isPattern())
|
||||||
? QImage()
|
? QImage()
|
||||||
: PrepareScaledNonPattern(
|
: PrepareScaledNonPattern(
|
||||||
Data::PrepareBlurredBackground(thumbnail->original()),
|
Ui::PrepareBlurredBackground(thumbnail->original()),
|
||||||
Images::Option(0));
|
Images::Option(0));
|
||||||
setScaledFromImage(std::move(scaled), std::move(blurred));
|
setScaledFromImage(std::move(scaled), std::move(blurred));
|
||||||
}
|
}
|
||||||
|
@ -676,7 +676,7 @@ void BackgroundPreviewBox::setScaledFromThumb() {
|
||||||
void BackgroundPreviewBox::setScaledFromImage(
|
void BackgroundPreviewBox::setScaledFromImage(
|
||||||
QImage &&image,
|
QImage &&image,
|
||||||
QImage &&blurred) {
|
QImage &&blurred) {
|
||||||
updateServiceBg({ Window::Theme::CountAverageColor(image) });
|
updateServiceBg({ Ui::CountAverageColor(image) });
|
||||||
if (!_full.isNull()) {
|
if (!_full.isNull()) {
|
||||||
startFadeInFrom(std::move(_scaled));
|
startFadeInFrom(std::move(_scaled));
|
||||||
}
|
}
|
||||||
|
@ -714,7 +714,7 @@ void BackgroundPreviewBox::updateServiceBg(const std::vector<QColor> &bg) {
|
||||||
green += color.green();
|
green += color.green();
|
||||||
blue += color.blue();
|
blue += color.blue();
|
||||||
}
|
}
|
||||||
_serviceBg = Window::Theme::AdjustedColor(
|
_serviceBg = Ui::ThemeAdjustedColor(
|
||||||
st::msgServiceBg->c,
|
st::msgServiceBg->c,
|
||||||
QColor(red / count, green / count, blue / count));
|
QColor(red / count, green / count, blue / count));
|
||||||
}
|
}
|
||||||
|
@ -748,7 +748,7 @@ void BackgroundPreviewBox::checkLoadedDocument() {
|
||||||
patternOpacity);
|
patternOpacity);
|
||||||
auto blurred = !isPattern
|
auto blurred = !isPattern
|
||||||
? PrepareScaledNonPattern(
|
? PrepareScaledNonPattern(
|
||||||
Data::PrepareBlurredBackground(image),
|
Ui::PrepareBlurredBackground(image),
|
||||||
Images::Option(0))
|
Images::Option(0))
|
||||||
: QImage();
|
: QImage();
|
||||||
crl::on_main(std::move(guard), [
|
crl::on_main(std::move(guard), [
|
||||||
|
@ -763,9 +763,9 @@ void BackgroundPreviewBox::checkLoadedDocument() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
_generating = Data::ReadImageAsync(
|
_generating = Data::ReadBackgroundImageAsync(
|
||||||
_media.get(),
|
_media.get(),
|
||||||
Window::Theme::PreprocessBackgroundImage,
|
Ui::PreprocessBackgroundImage,
|
||||||
generateCallback);
|
generateCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "media/player/media_player_instance.h"
|
#include "media/player/media_player_instance.h"
|
||||||
#include "platform/platform_file_utilities.h"
|
#include "platform/platform_file_utilities.h"
|
||||||
|
#include "ui/chat/chat_theme.h"
|
||||||
#include "ui/text/text_utilities.h"
|
#include "ui/text/text_utilities.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
|
|
||||||
|
@ -33,8 +34,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
namespace Data {
|
namespace Data {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kMaxWallpaperSize = 3072;
|
|
||||||
|
|
||||||
void LaunchWithWarning(
|
void LaunchWithWarning(
|
||||||
// not_null<Window::Controller*> controller,
|
// not_null<Window::Controller*> controller,
|
||||||
const QString &name,
|
const QString &name,
|
||||||
|
@ -175,19 +174,7 @@ bool IsIpRevealingName(const QString &filepath) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] QImage ReadImage(
|
base::binary_guard ReadBackgroundImageAsync(
|
||||||
const QString &path,
|
|
||||||
const QByteArray &content,
|
|
||||||
bool gzipSvg) {
|
|
||||||
return Images::Read({
|
|
||||||
.path = path,
|
|
||||||
.content = content,
|
|
||||||
.maxSize = QSize(kMaxWallpaperSize, kMaxWallpaperSize),
|
|
||||||
.gzipSvg = gzipSvg,
|
|
||||||
}).image;
|
|
||||||
}
|
|
||||||
|
|
||||||
base::binary_guard ReadImageAsync(
|
|
||||||
not_null<Data::DocumentMedia*> media,
|
not_null<Data::DocumentMedia*> media,
|
||||||
FnMut<QImage(QImage)> postprocess,
|
FnMut<QImage(QImage)> postprocess,
|
||||||
FnMut<void(QImage&&)> done) {
|
FnMut<void(QImage&&)> done) {
|
||||||
|
@ -201,7 +188,7 @@ base::binary_guard ReadImageAsync(
|
||||||
guard = result.make_guard(),
|
guard = result.make_guard(),
|
||||||
callback = std::move(done)
|
callback = std::move(done)
|
||||||
]() mutable {
|
]() mutable {
|
||||||
auto image = ReadImage(path, bytes, gzipSvg);
|
auto image = Ui::ReadBackgroundImage(path, bytes, gzipSvg);
|
||||||
if (postprocess) {
|
if (postprocess) {
|
||||||
image = postprocess(std::move(image));
|
image = postprocess(std::move(image));
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ class DocumentMedia;
|
||||||
// [[nodiscard]] bool IsValidMediaFile(const QString &filepath);
|
// [[nodiscard]] bool IsValidMediaFile(const QString &filepath);
|
||||||
[[nodiscard]] bool IsExecutableName(const QString &filepath);
|
[[nodiscard]] bool IsExecutableName(const QString &filepath);
|
||||||
[[nodiscard]] bool IsIpRevealingName(const QString &filepath);
|
[[nodiscard]] bool IsIpRevealingName(const QString &filepath);
|
||||||
base::binary_guard ReadImageAsync(
|
base::binary_guard ReadBackgroundImageAsync(
|
||||||
not_null<Data::DocumentMedia*> media,
|
not_null<Data::DocumentMedia*> media,
|
||||||
FnMut<QImage(QImage)> postprocess,
|
FnMut<QImage(QImage)> postprocess,
|
||||||
FnMut<void(QImage&&)> done);
|
FnMut<void(QImage&&)> done);
|
||||||
|
|
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_file_origin.h"
|
#include "data/data_file_origin.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "storage/serialize_common.h"
|
#include "storage/serialize_common.h"
|
||||||
|
#include "ui/chat/chat_theme.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
|
||||||
|
@ -45,10 +46,6 @@ constexpr auto kVersion = 1;
|
||||||
return color ? SerializeColor(*color) : quint32(-1);
|
return color ? SerializeColor(*color) : quint32(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] QColor DefaultBackgroundColor() {
|
|
||||||
return QColor(213, 223, 233);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] std::vector<QColor> ColorsFromMTP(
|
[[nodiscard]] std::vector<QColor> ColorsFromMTP(
|
||||||
const MTPDwallPaperSettings &data) {
|
const MTPDwallPaperSettings &data) {
|
||||||
auto result = std::vector<QColor>();
|
auto result = std::vector<QColor>();
|
||||||
|
@ -683,89 +680,8 @@ bool IsCloudWallPaper(const WallPaper &paper) {
|
||||||
&& !details::IsTestingEditorWallPaper(paper);
|
&& !details::IsTestingEditorWallPaper(paper);
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage GenerateWallPaper(
|
|
||||||
QSize size,
|
|
||||||
const std::vector<QColor> &bg,
|
|
||||||
int gradientRotation,
|
|
||||||
float64 patternOpacity,
|
|
||||||
Fn<void(QPainter&)> drawPattern) {
|
|
||||||
auto result = bg.empty()
|
|
||||||
? Images::GenerateGradient(size, { DefaultBackgroundColor() })
|
|
||||||
: Images::GenerateGradient(size, bg, gradientRotation);
|
|
||||||
if (bg.size() > 1 && (!drawPattern || patternOpacity >= 0.)) {
|
|
||||||
result = Images::DitherImage(std::move(result));
|
|
||||||
}
|
|
||||||
if (drawPattern) {
|
|
||||||
auto p = QPainter(&result);
|
|
||||||
if (patternOpacity >= 0.) {
|
|
||||||
p.setCompositionMode(QPainter::CompositionMode_SoftLight);
|
|
||||||
p.setOpacity(patternOpacity);
|
|
||||||
} else {
|
|
||||||
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
|
|
||||||
}
|
|
||||||
drawPattern(p);
|
|
||||||
if (patternOpacity < 0. && patternOpacity > -1.) {
|
|
||||||
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
|
||||||
p.setOpacity(1. + patternOpacity);
|
|
||||||
p.fillRect(QRect{ QPoint(), size }, Qt::black);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::move(result).convertToFormat(
|
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage PreparePatternImage(
|
|
||||||
QImage pattern,
|
|
||||||
const std::vector<QColor> &bg,
|
|
||||||
int gradientRotation,
|
|
||||||
float64 patternOpacity) {
|
|
||||||
auto result = GenerateWallPaper(
|
|
||||||
pattern.size(),
|
|
||||||
bg,
|
|
||||||
gradientRotation,
|
|
||||||
patternOpacity,
|
|
||||||
[&](QPainter &p) {
|
|
||||||
p.drawImage(QRect(QPoint(), pattern.size()), pattern);
|
|
||||||
});
|
|
||||||
|
|
||||||
pattern = QImage();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage PrepareBlurredBackground(QImage image) {
|
|
||||||
constexpr auto kSize = 900;
|
|
||||||
constexpr auto kRadius = 24;
|
|
||||||
if (image.width() > kSize || image.height() > kSize) {
|
|
||||||
image = image.scaled(
|
|
||||||
kSize,
|
|
||||||
kSize,
|
|
||||||
Qt::KeepAspectRatio,
|
|
||||||
Qt::SmoothTransformation);
|
|
||||||
}
|
|
||||||
return Images::BlurLargeImage(image, kRadius);
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage GenerateDitheredGradient(
|
|
||||||
const std::vector<QColor> &colors,
|
|
||||||
int rotation) {
|
|
||||||
constexpr auto kSize = 512;
|
|
||||||
const auto size = QSize(kSize, kSize);
|
|
||||||
if (colors.empty()) {
|
|
||||||
return Images::GenerateGradient(size, { DefaultBackgroundColor() });
|
|
||||||
}
|
|
||||||
auto result = Images::GenerateGradient(size, colors, rotation);
|
|
||||||
if (colors.size() > 1) {
|
|
||||||
result = Images::DitherImage(std::move(result));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage GenerateDitheredGradient(const Data::WallPaper &paper) {
|
QImage GenerateDitheredGradient(const Data::WallPaper &paper) {
|
||||||
if (paper.backgroundColors().empty()) {
|
return Ui::GenerateDitheredGradient(
|
||||||
return GenerateDitheredGradient({ DefaultBackgroundColor() }, 0);
|
|
||||||
}
|
|
||||||
return GenerateDitheredGradient(
|
|
||||||
paper.backgroundColors(),
|
paper.backgroundColors(),
|
||||||
paper.gradientRotation());
|
paper.gradientRotation());
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,21 +121,6 @@ private:
|
||||||
[[nodiscard]] bool IsDefaultWallPaper(const WallPaper &paper);
|
[[nodiscard]] bool IsDefaultWallPaper(const WallPaper &paper);
|
||||||
[[nodiscard]] bool IsCloudWallPaper(const WallPaper &paper);
|
[[nodiscard]] bool IsCloudWallPaper(const WallPaper &paper);
|
||||||
|
|
||||||
[[nodiscard]] QImage GenerateWallPaper(
|
|
||||||
QSize size,
|
|
||||||
const std::vector<QColor> &bg,
|
|
||||||
int gradientRotation,
|
|
||||||
float64 patternOpacity = 1.,
|
|
||||||
Fn<void(QPainter&)> drawPattern = nullptr);
|
|
||||||
[[nodiscard]] QImage PreparePatternImage(
|
|
||||||
QImage pattern,
|
|
||||||
const std::vector<QColor> &bg,
|
|
||||||
int gradientRotation,
|
|
||||||
float64 patternOpacity);
|
|
||||||
[[nodiscard]] QImage PrepareBlurredBackground(QImage image);
|
|
||||||
[[nodiscard]] QImage GenerateDitheredGradient(
|
|
||||||
const std::vector<QColor> &colors,
|
|
||||||
int rotation);
|
|
||||||
[[nodiscard]] QImage GenerateDitheredGradient(const WallPaper &paper);
|
[[nodiscard]] QImage GenerateDitheredGradient(const WallPaper &paper);
|
||||||
|
|
||||||
[[nodiscard]] QColor ColorFromSerialized(quint32 serialized);
|
[[nodiscard]] QColor ColorFromSerialized(quint32 serialized);
|
||||||
|
|
|
@ -279,7 +279,7 @@ void ThemeDocument::validateThumbnail() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThemeDocument::generateThumbnail() const {
|
void ThemeDocument::generateThumbnail() const {
|
||||||
_thumbnail = Ui::PixmapFromImage(Data::GenerateWallPaper(
|
_thumbnail = Ui::PixmapFromImage(Ui::GenerateBackgroundImage(
|
||||||
QSize(_pixw, _pixh) * cIntRetinaFactor(),
|
QSize(_pixw, _pixh) * cIntRetinaFactor(),
|
||||||
_background,
|
_background,
|
||||||
_gradientRotation,
|
_gradientRotation,
|
||||||
|
@ -316,7 +316,7 @@ void ThemeDocument::prepareThumbnailFrom(
|
||||||
_pixw,
|
_pixw,
|
||||||
_pixh);
|
_pixh);
|
||||||
if (isPattern) {
|
if (isPattern) {
|
||||||
original = Data::PreparePatternImage(
|
original = Ui::PreparePatternImage(
|
||||||
std::move(original),
|
std::move(original),
|
||||||
_background,
|
_background,
|
||||||
_gradientRotation,
|
_gradientRotation,
|
||||||
|
|
|
@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_histories.h"
|
#include "data/data_histories.h"
|
||||||
#include "data/stickers/data_stickers.h"
|
#include "data/stickers/data_stickers.h"
|
||||||
#include "api/api_text_entities.h"
|
#include "api/api_text_entities.h"
|
||||||
|
#include "ui/chat/chat_theme.h"
|
||||||
#include "ui/special_buttons.h"
|
#include "ui/special_buttons.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
#include "ui/widgets/shadow.h"
|
#include "ui/widgets/shadow.h"
|
||||||
|
@ -1183,9 +1184,9 @@ void MainWidget::checkChatBackground() {
|
||||||
: background->data;
|
: background->data;
|
||||||
setReadyChatBackground(ready, std::move(image));
|
setReadyChatBackground(ready, std::move(image));
|
||||||
};
|
};
|
||||||
_background->generating = Data::ReadImageAsync(
|
_background->generating = Data::ReadBackgroundImageAsync(
|
||||||
media.get(),
|
media.get(),
|
||||||
Window::Theme::PreprocessBackgroundImage,
|
Ui::PreprocessBackgroundImage,
|
||||||
generateCallback);
|
generateCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,12 @@ constexpr auto kMaxChatEntryHistorySize = 50;
|
||||||
constexpr auto kCacheBackgroundTimeout = 3 * crl::time(1000);
|
constexpr auto kCacheBackgroundTimeout = 3 * crl::time(1000);
|
||||||
constexpr auto kCacheBackgroundFastTimeout = crl::time(200);
|
constexpr auto kCacheBackgroundFastTimeout = crl::time(200);
|
||||||
constexpr auto kBackgroundFadeDuration = crl::time(200);
|
constexpr auto kBackgroundFadeDuration = crl::time(200);
|
||||||
|
constexpr auto kMinimumTiledSize = 512;
|
||||||
|
constexpr auto kMaxSize = 2960;
|
||||||
|
|
||||||
|
[[nodiscard]] QColor DefaultBackgroundColor() {
|
||||||
|
return QColor(213, 223, 233);
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] CacheBackgroundResult CacheBackground(
|
[[nodiscard]] CacheBackgroundResult CacheBackground(
|
||||||
const CacheBackgroundRequest &request) {
|
const CacheBackgroundRequest &request) {
|
||||||
|
@ -141,7 +147,7 @@ bool operator!=(
|
||||||
}
|
}
|
||||||
|
|
||||||
CachedBackground::CachedBackground(CacheBackgroundResult &&result)
|
CachedBackground::CachedBackground(CacheBackgroundResult &&result)
|
||||||
: pixmap(Ui::PixmapFromImage(std::move(result.image)))
|
: pixmap(PixmapFromImage(std::move(result.image)))
|
||||||
, area(result.area)
|
, area(result.area)
|
||||||
, x(result.x)
|
, x(result.x)
|
||||||
, y(result.y) {
|
, y(result.y) {
|
||||||
|
@ -169,6 +175,18 @@ void ChatTheme::setBackground(ChatThemeBackground &&background) {
|
||||||
_repaintBackgroundRequests.fire({});
|
_repaintBackgroundRequests.fire({});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChatTheme::updateBackgroundImageFrom(ChatThemeBackground &&background) {
|
||||||
|
_mutableBackground.prepared = std::move(background.prepared);
|
||||||
|
_mutableBackground.preparedForTiled = std::move(
|
||||||
|
background.preparedForTiled);
|
||||||
|
if (!_backgroundState.now.pixmap.isNull()) {
|
||||||
|
if (_cacheBackgroundTimer) {
|
||||||
|
_cacheBackgroundTimer->cancel();
|
||||||
|
}
|
||||||
|
cacheBackgroundNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uint64 ChatTheme::key() const {
|
uint64 ChatTheme::key() const {
|
||||||
return _id;
|
return _id;
|
||||||
}
|
}
|
||||||
|
@ -184,7 +202,7 @@ void ChatTheme::setBubblesBackground(QImage image) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!_bubblesBackgroundPattern) {
|
if (!_bubblesBackgroundPattern) {
|
||||||
_bubblesBackgroundPattern = Ui::PrepareBubblePattern();
|
_bubblesBackgroundPattern = PrepareBubblePattern();
|
||||||
}
|
}
|
||||||
_bubblesBackgroundPattern->pixmap = _bubblesBackground.pixmap;
|
_bubblesBackgroundPattern->pixmap = _bubblesBackground.pixmap;
|
||||||
_repaintBackgroundRequests.fire({});
|
_repaintBackgroundRequests.fire({});
|
||||||
|
@ -427,4 +445,244 @@ ChatBackgroundRects ComputeChatBackgroundRects(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QColor CountAverageColor(const QImage &image) {
|
||||||
|
Expects(image.format() == QImage::Format_ARGB32_Premultiplied
|
||||||
|
|| image.format() == QImage::Format_RGB32);
|
||||||
|
|
||||||
|
uint64 components[3] = { 0 };
|
||||||
|
const auto w = image.width();
|
||||||
|
const auto h = image.height();
|
||||||
|
const auto size = w * h;
|
||||||
|
if (const auto pix = image.constBits()) {
|
||||||
|
for (auto i = 0, l = size * 4; i != l; i += 4) {
|
||||||
|
components[2] += pix[i + 0];
|
||||||
|
components[1] += pix[i + 1];
|
||||||
|
components[0] += pix[i + 2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (size) {
|
||||||
|
for (auto i = 0; i != 3; ++i) {
|
||||||
|
components[i] /= size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QColor(components[0], components[1], components[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor ThemeAdjustedColor(QColor original, QColor background) {
|
||||||
|
return QColor::fromHslF(
|
||||||
|
background.hslHueF(),
|
||||||
|
background.hslSaturationF(),
|
||||||
|
original.lightnessF(),
|
||||||
|
original.alphaF()
|
||||||
|
).toRgb();
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage PreprocessBackgroundImage(QImage image) {
|
||||||
|
if (image.isNull()) {
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
if (image.format() != QImage::Format_ARGB32_Premultiplied) {
|
||||||
|
image = std::move(image).convertToFormat(
|
||||||
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
}
|
||||||
|
if (image.width() > 40 * image.height()) {
|
||||||
|
const auto width = 40 * image.height();
|
||||||
|
const auto height = image.height();
|
||||||
|
image = image.copy((image.width() - width) / 2, 0, width, height);
|
||||||
|
} else if (image.height() > 40 * image.width()) {
|
||||||
|
const auto width = image.width();
|
||||||
|
const auto height = 40 * image.width();
|
||||||
|
image = image.copy(0, (image.height() - height) / 2, width, height);
|
||||||
|
}
|
||||||
|
if (image.width() > kMaxSize || image.height() > kMaxSize) {
|
||||||
|
image = image.scaled(
|
||||||
|
kMaxSize,
|
||||||
|
kMaxSize,
|
||||||
|
Qt::KeepAspectRatio,
|
||||||
|
Qt::SmoothTransformation);
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<QColor> CalculateImageMonoColor(const QImage &image) {
|
||||||
|
Expects(image.bytesPerLine() == 4 * image.width());
|
||||||
|
|
||||||
|
if (image.isNull()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const auto bits = reinterpret_cast<const uint32*>(image.constBits());
|
||||||
|
const auto first = bits[0];
|
||||||
|
for (auto i = 0; i < image.width() * image.height(); i++) {
|
||||||
|
if (first != bits[i]) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return image.pixelColor(QPoint());
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage PrepareImageForTiled(const QImage &prepared) {
|
||||||
|
const auto width = prepared.width();
|
||||||
|
const auto height = prepared.height();
|
||||||
|
const auto isSmallForTiled = (width > 0 && height > 0)
|
||||||
|
&& (width < kMinimumTiledSize || height < kMinimumTiledSize);
|
||||||
|
if (!isSmallForTiled) {
|
||||||
|
return prepared;
|
||||||
|
}
|
||||||
|
const auto repeatTimesX = (kMinimumTiledSize + width - 1) / width;
|
||||||
|
const auto repeatTimesY = (kMinimumTiledSize + height - 1) / height;
|
||||||
|
auto result = QImage(
|
||||||
|
width * repeatTimesX,
|
||||||
|
height * repeatTimesY,
|
||||||
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
result.setDevicePixelRatio(prepared.devicePixelRatio());
|
||||||
|
auto imageForTiledBytes = result.bits();
|
||||||
|
auto bytesInLine = width * sizeof(uint32);
|
||||||
|
for (auto timesY = 0; timesY != repeatTimesY; ++timesY) {
|
||||||
|
auto imageBytes = prepared.constBits();
|
||||||
|
for (auto y = 0; y != height; ++y) {
|
||||||
|
for (auto timesX = 0; timesX != repeatTimesX; ++timesX) {
|
||||||
|
memcpy(imageForTiledBytes, imageBytes, bytesInLine);
|
||||||
|
imageForTiledBytes += bytesInLine;
|
||||||
|
}
|
||||||
|
imageBytes += prepared.bytesPerLine();
|
||||||
|
imageForTiledBytes += result.bytesPerLine() - (repeatTimesX * bytesInLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QImage ReadBackgroundImage(
|
||||||
|
const QString &path,
|
||||||
|
const QByteArray &content,
|
||||||
|
bool gzipSvg) {
|
||||||
|
return Images::Read({
|
||||||
|
.path = path,
|
||||||
|
.content = content,
|
||||||
|
.maxSize = QSize(kMaxSize, kMaxSize),
|
||||||
|
.gzipSvg = gzipSvg,
|
||||||
|
}).image;
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage GenerateBackgroundImage(
|
||||||
|
QSize size,
|
||||||
|
const std::vector<QColor> &bg,
|
||||||
|
int gradientRotation,
|
||||||
|
float64 patternOpacity,
|
||||||
|
Fn<void(QPainter&)> drawPattern) {
|
||||||
|
auto result = bg.empty()
|
||||||
|
? Images::GenerateGradient(size, { DefaultBackgroundColor() })
|
||||||
|
: Images::GenerateGradient(size, bg, gradientRotation);
|
||||||
|
if (bg.size() > 1 && (!drawPattern || patternOpacity >= 0.)) {
|
||||||
|
result = Images::DitherImage(std::move(result));
|
||||||
|
}
|
||||||
|
if (drawPattern) {
|
||||||
|
auto p = QPainter(&result);
|
||||||
|
if (patternOpacity >= 0.) {
|
||||||
|
p.setCompositionMode(QPainter::CompositionMode_SoftLight);
|
||||||
|
p.setOpacity(patternOpacity);
|
||||||
|
} else {
|
||||||
|
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
|
||||||
|
}
|
||||||
|
drawPattern(p);
|
||||||
|
if (patternOpacity < 0. && patternOpacity > -1.) {
|
||||||
|
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||||
|
p.setOpacity(1. + patternOpacity);
|
||||||
|
p.fillRect(QRect{ QPoint(), size }, Qt::black);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::move(result).convertToFormat(
|
||||||
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage PreparePatternImage(
|
||||||
|
QImage pattern,
|
||||||
|
const std::vector<QColor> &bg,
|
||||||
|
int gradientRotation,
|
||||||
|
float64 patternOpacity) {
|
||||||
|
auto result = GenerateBackgroundImage(
|
||||||
|
pattern.size(),
|
||||||
|
bg,
|
||||||
|
gradientRotation,
|
||||||
|
patternOpacity,
|
||||||
|
[&](QPainter &p) {
|
||||||
|
p.drawImage(QRect(QPoint(), pattern.size()), pattern);
|
||||||
|
});
|
||||||
|
|
||||||
|
pattern = QImage();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage PrepareBlurredBackground(QImage image) {
|
||||||
|
constexpr auto kSize = 900;
|
||||||
|
constexpr auto kRadius = 24;
|
||||||
|
if (image.width() > kSize || image.height() > kSize) {
|
||||||
|
image = image.scaled(
|
||||||
|
kSize,
|
||||||
|
kSize,
|
||||||
|
Qt::KeepAspectRatio,
|
||||||
|
Qt::SmoothTransformation);
|
||||||
|
}
|
||||||
|
return Images::BlurLargeImage(image, kRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage GenerateDitheredGradient(
|
||||||
|
const std::vector<QColor> &colors,
|
||||||
|
int rotation) {
|
||||||
|
constexpr auto kSize = 512;
|
||||||
|
const auto size = QSize(kSize, kSize);
|
||||||
|
if (colors.empty()) {
|
||||||
|
return Images::GenerateGradient(size, { DefaultBackgroundColor() });
|
||||||
|
}
|
||||||
|
auto result = Images::GenerateGradient(size, colors, rotation);
|
||||||
|
if (colors.size() > 1) {
|
||||||
|
result = Images::DitherImage(std::move(result));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatThemeBackground PrepareBackgroundImage(
|
||||||
|
const QString &path,
|
||||||
|
const QByteArray &bytes,
|
||||||
|
bool gzipSvg,
|
||||||
|
const std::vector<QColor> &colors,
|
||||||
|
bool isPattern,
|
||||||
|
float64 patternOpacity,
|
||||||
|
bool isBlurred) {
|
||||||
|
auto prepared = (isPattern || colors.empty())
|
||||||
|
? PreprocessBackgroundImage(ReadBackgroundImage(path, bytes, gzipSvg))
|
||||||
|
: QImage();
|
||||||
|
if (isPattern && !prepared.isNull()) {
|
||||||
|
if (colors.size() < 2) {
|
||||||
|
const auto gradientRotation = 0; // No gradient here.
|
||||||
|
prepared = PreparePatternImage(
|
||||||
|
std::move(prepared),
|
||||||
|
colors,
|
||||||
|
gradientRotation,
|
||||||
|
patternOpacity);
|
||||||
|
}
|
||||||
|
prepared.setDevicePixelRatio(style::DevicePixelRatio());
|
||||||
|
} else if (colors.empty()) {
|
||||||
|
prepared.setDevicePixelRatio(style::DevicePixelRatio());
|
||||||
|
}
|
||||||
|
const auto imageMonoColor = (colors.size() < 2)
|
||||||
|
? CalculateImageMonoColor(prepared)
|
||||||
|
: std::nullopt;
|
||||||
|
if (!prepared.isNull() && !isPattern && isBlurred) {
|
||||||
|
prepared = PrepareBlurredBackground(std::move(prepared));
|
||||||
|
}
|
||||||
|
return ChatThemeBackground{
|
||||||
|
.prepared = prepared,
|
||||||
|
.preparedForTiled = PrepareImageForTiled(prepared),
|
||||||
|
.colorForFill = (!prepared.isNull()
|
||||||
|
? imageMonoColor
|
||||||
|
: (colors.size() > 1 || colors.empty())
|
||||||
|
? std::nullopt
|
||||||
|
: std::make_optional(colors.front())),
|
||||||
|
.colors = colors,
|
||||||
|
.patternOpacity = patternOpacity,
|
||||||
|
.isPattern = isPattern,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Window::Theme
|
} // namespace Window::Theme
|
||||||
|
|
|
@ -117,6 +117,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
void setBackground(ChatThemeBackground &&background);
|
void setBackground(ChatThemeBackground &&background);
|
||||||
|
void updateBackgroundImageFrom(ChatThemeBackground &&background);
|
||||||
const ChatThemeBackground &background() const {
|
const ChatThemeBackground &background() const {
|
||||||
return _mutableBackground;
|
return _mutableBackground;
|
||||||
}
|
}
|
||||||
|
@ -175,4 +176,40 @@ struct ChatBackgroundRects {
|
||||||
QSize fillSize,
|
QSize fillSize,
|
||||||
QSize imageSize);
|
QSize imageSize);
|
||||||
|
|
||||||
|
[[nodiscard]] QColor CountAverageColor(const QImage &image);
|
||||||
|
[[nodiscard]] QColor ThemeAdjustedColor(QColor original, QColor background);
|
||||||
|
[[nodiscard]] QImage PreprocessBackgroundImage(QImage image);
|
||||||
|
[[nodiscard]] std::optional<QColor> CalculateImageMonoColor(
|
||||||
|
const QImage &image);
|
||||||
|
[[nodiscard]] QImage PrepareImageForTiled(const QImage &prepared);
|
||||||
|
|
||||||
|
[[nodiscard]] QImage ReadBackgroundImage(
|
||||||
|
const QString &path,
|
||||||
|
const QByteArray &content,
|
||||||
|
bool gzipSvg);
|
||||||
|
[[nodiscard]] QImage GenerateBackgroundImage(
|
||||||
|
QSize size,
|
||||||
|
const std::vector<QColor> &bg,
|
||||||
|
int gradientRotation,
|
||||||
|
float64 patternOpacity = 1.,
|
||||||
|
Fn<void(QPainter&)> drawPattern = nullptr);
|
||||||
|
[[nodiscard]] QImage PreparePatternImage(
|
||||||
|
QImage pattern,
|
||||||
|
const std::vector<QColor> &bg,
|
||||||
|
int gradientRotation,
|
||||||
|
float64 patternOpacity);
|
||||||
|
[[nodiscard]] QImage PrepareBlurredBackground(QImage image);
|
||||||
|
[[nodiscard]] QImage GenerateDitheredGradient(
|
||||||
|
const std::vector<QColor> &colors,
|
||||||
|
int rotation);
|
||||||
|
|
||||||
|
[[nodiscard]] ChatThemeBackground PrepareBackgroundImage(
|
||||||
|
const QString &path,
|
||||||
|
const QByteArray &bytes,
|
||||||
|
bool gzipSvg,
|
||||||
|
const std::vector<QColor> &colors,
|
||||||
|
bool isPattern,
|
||||||
|
float64 patternOpacity,
|
||||||
|
bool isBlurred);
|
||||||
|
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
@ -24,8 +24,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "base/unixtime.h"
|
#include "base/unixtime.h"
|
||||||
#include "base/crc32hash.h"
|
#include "base/crc32hash.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_document_resolver.h"
|
||||||
#include "main/main_account.h" // Account::local.
|
#include "main/main_account.h" // Account::local.
|
||||||
#include "main/main_domain.h" // Domain::activeSessionValue.
|
#include "main/main_domain.h" // Domain::activeSessionValue.
|
||||||
|
#include "ui/chat/chat_theme.h"
|
||||||
#include "ui/image/image.h"
|
#include "ui/image/image.h"
|
||||||
#include "ui/ui_utility.h"
|
#include "ui/ui_utility.h"
|
||||||
#include "boxes/confirm_box.h"
|
#include "boxes/confirm_box.h"
|
||||||
|
@ -43,7 +45,6 @@ namespace {
|
||||||
constexpr auto kThemeFileSizeLimit = 5 * 1024 * 1024;
|
constexpr auto kThemeFileSizeLimit = 5 * 1024 * 1024;
|
||||||
constexpr auto kBackgroundSizeLimit = 25 * 1024 * 1024;
|
constexpr auto kBackgroundSizeLimit = 25 * 1024 * 1024;
|
||||||
constexpr auto kNightThemeFile = ":/gui/night.tdesktop-theme"_cs;
|
constexpr auto kNightThemeFile = ":/gui/night.tdesktop-theme"_cs;
|
||||||
constexpr auto kMinimumTiledSize = 512;
|
|
||||||
|
|
||||||
struct Applying {
|
struct Applying {
|
||||||
Saved data;
|
Saved data;
|
||||||
|
@ -58,20 +59,6 @@ inline bool AreTestingTheme() {
|
||||||
return !GlobalApplying.paletteForRevert.isEmpty();
|
return !GlobalApplying.paletteForRevert.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<QColor> CalculateImageMonoColor(const QImage &image) {
|
|
||||||
if (image.isNull()) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
const auto bits = reinterpret_cast<const uint32*>(image.constBits());
|
|
||||||
const auto first = bits[0];
|
|
||||||
for (auto i = 0; i < image.width() * image.height(); i++) {
|
|
||||||
if (first != bits[i]) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return image.pixelColor(QPoint());
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] bool GoodImageFormatAndSize(const QImage &image) {
|
[[nodiscard]] bool GoodImageFormatAndSize(const QImage &image) {
|
||||||
return !image.size().isEmpty()
|
return !image.size().isEmpty()
|
||||||
&& (image.format() == QImage::Format_ARGB32_Premultiplied
|
&& (image.format() == QImage::Format_ARGB32_Premultiplied
|
||||||
|
@ -646,7 +633,7 @@ QImage ChatBackground::postprocessBackgroundImage(QImage image) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatBackground::set(const Data::WallPaper &paper, QImage image) {
|
void ChatBackground::set(const Data::WallPaper &paper, QImage image) {
|
||||||
image = PreprocessBackgroundImage(std::move(image));
|
image = Ui::PreprocessBackgroundImage(std::move(image));
|
||||||
|
|
||||||
const auto needResetAdjustable = Data::IsDefaultWallPaper(paper)
|
const auto needResetAdjustable = Data::IsDefaultWallPaper(paper)
|
||||||
&& !Data::IsDefaultWallPaper(_paper)
|
&& !Data::IsDefaultWallPaper(_paper)
|
||||||
|
@ -701,7 +688,7 @@ void ChatBackground::set(const Data::WallPaper &paper, QImage image) {
|
||||||
if (_paper.isPattern() && !image.isNull()) {
|
if (_paper.isPattern() && !image.isNull()) {
|
||||||
if (bgColors.size() < 2) {
|
if (bgColors.size() < 2) {
|
||||||
auto prepared = postprocessBackgroundImage(
|
auto prepared = postprocessBackgroundImage(
|
||||||
Data::PreparePatternImage(
|
Ui::PreparePatternImage(
|
||||||
image,
|
image,
|
||||||
bgColors,
|
bgColors,
|
||||||
_paper.gradientRotation(),
|
_paper.gradientRotation(),
|
||||||
|
@ -752,7 +739,7 @@ void ChatBackground::setPrepared(
|
||||||
Expects(gradient.isNull() || GoodImageFormatAndSize(gradient));
|
Expects(gradient.isNull() || GoodImageFormatAndSize(gradient));
|
||||||
|
|
||||||
if (!prepared.isNull() && !_paper.isPattern() && _paper.isBlurred()) {
|
if (!prepared.isNull() && !_paper.isPattern() && _paper.isBlurred()) {
|
||||||
prepared = Data::PrepareBlurredBackground(std::move(prepared));
|
prepared = Ui::PrepareBlurredBackground(std::move(prepared));
|
||||||
}
|
}
|
||||||
if (adjustPaletteRequired()) {
|
if (adjustPaletteRequired()) {
|
||||||
if (!gradient.isNull()) {
|
if (!gradient.isNull()) {
|
||||||
|
@ -768,40 +755,9 @@ void ChatBackground::setPrepared(
|
||||||
_prepared = std::move(prepared);
|
_prepared = std::move(prepared);
|
||||||
_gradient = std::move(gradient);
|
_gradient = std::move(gradient);
|
||||||
_imageMonoColor = _gradient.isNull()
|
_imageMonoColor = _gradient.isNull()
|
||||||
? CalculateImageMonoColor(_prepared)
|
? Ui::CalculateImageMonoColor(_prepared)
|
||||||
: std::nullopt;
|
: std::nullopt;
|
||||||
prepareImageForTiled();
|
_preparedForTiled = Ui::PrepareImageForTiled(_prepared);
|
||||||
}
|
|
||||||
|
|
||||||
void ChatBackground::prepareImageForTiled() {
|
|
||||||
const auto width = _prepared.width();
|
|
||||||
const auto height = _prepared.height();
|
|
||||||
const auto isSmallForTiled = (width > 0 && height > 0)
|
|
||||||
&& (width < kMinimumTiledSize || height < kMinimumTiledSize);
|
|
||||||
if (!isSmallForTiled) {
|
|
||||||
_preparedForTiled = _prepared;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto repeatTimesX = qCeil(kMinimumTiledSize / (1. * width));
|
|
||||||
const auto repeatTimesY = qCeil(kMinimumTiledSize / (1. * height));
|
|
||||||
_preparedForTiled = QImage(
|
|
||||||
width * repeatTimesX,
|
|
||||||
height * repeatTimesY,
|
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
|
||||||
_preparedForTiled.setDevicePixelRatio(_prepared.devicePixelRatio());
|
|
||||||
auto imageForTiledBytes = _preparedForTiled.bits();
|
|
||||||
auto bytesInLine = width * sizeof(uint32);
|
|
||||||
for (auto timesY = 0; timesY != repeatTimesY; ++timesY) {
|
|
||||||
auto imageBytes = _prepared.constBits();
|
|
||||||
for (auto y = 0; y != height; ++y) {
|
|
||||||
for (auto timesX = 0; timesX != repeatTimesX; ++timesX) {
|
|
||||||
memcpy(imageForTiledBytes, imageBytes, bytesInLine);
|
|
||||||
imageForTiledBytes += bytesInLine;
|
|
||||||
}
|
|
||||||
imageBytes += _prepared.bytesPerLine();
|
|
||||||
imageForTiledBytes += _preparedForTiled.bytesPerLine() - (repeatTimesX * bytesInLine);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatBackground::setPaper(const Data::WallPaper &paper) {
|
void ChatBackground::setPaper(const Data::WallPaper &paper) {
|
||||||
|
@ -850,13 +806,13 @@ void ChatBackground::clearEditingTheme(ClearEditing clear) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatBackground::adjustPaletteUsingBackground(const QImage &image) {
|
void ChatBackground::adjustPaletteUsingBackground(const QImage &image) {
|
||||||
adjustPaletteUsingColor(CountAverageColor(image));
|
adjustPaletteUsingColor(Ui::CountAverageColor(image));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatBackground::adjustPaletteUsingColor(QColor color) {
|
void ChatBackground::adjustPaletteUsingColor(QColor color) {
|
||||||
const auto prepared = color.toHsl();
|
const auto prepared = color.toHsl();
|
||||||
for (const auto &adjustable : _adjustableColors) {
|
for (const auto &adjustable : _adjustableColors) {
|
||||||
const auto adjusted = AdjustedColor(adjustable.item->c, prepared);
|
const auto adjusted = Ui::ThemeAdjustedColor(adjustable.item->c, prepared);
|
||||||
adjustable.item.set(
|
adjustable.item.set(
|
||||||
adjusted.red(),
|
adjusted.red(),
|
||||||
adjusted.green(),
|
adjusted.green(),
|
||||||
|
@ -885,10 +841,7 @@ void ChatBackground::recacheGradientForFill(QImage gradient) {
|
||||||
|
|
||||||
QImage ChatBackground::createCurrentImage() const {
|
QImage ChatBackground::createCurrentImage() const {
|
||||||
if (const auto fill = colorForFill()) {
|
if (const auto fill = colorForFill()) {
|
||||||
auto result = QImage(
|
auto result = QImage(512, 512, QImage::Format_ARGB32_Premultiplied);
|
||||||
kMinimumTiledSize,
|
|
||||||
kMinimumTiledSize,
|
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
|
||||||
result.fill(*fill);
|
result.fill(*fill);
|
||||||
return result;
|
return result;
|
||||||
} else if (_gradient.isNull()) {
|
} else if (_gradient.isNull()) {
|
||||||
|
@ -1477,64 +1430,6 @@ QString EditingPalettePath() {
|
||||||
return cWorkingDir() + "tdata/editing-theme.tdesktop-palette";
|
return cWorkingDir() + "tdata/editing-theme.tdesktop-palette";
|
||||||
}
|
}
|
||||||
|
|
||||||
QColor CountAverageColor(const QImage &image) {
|
|
||||||
Expects(image.format() == QImage::Format_ARGB32_Premultiplied
|
|
||||||
|| image.format() == QImage::Format_RGB32);
|
|
||||||
|
|
||||||
uint64 components[3] = { 0 };
|
|
||||||
const auto w = image.width();
|
|
||||||
const auto h = image.height();
|
|
||||||
const auto size = w * h;
|
|
||||||
if (const auto pix = image.constBits()) {
|
|
||||||
for (auto i = 0, l = size * 4; i != l; i += 4) {
|
|
||||||
components[2] += pix[i + 0];
|
|
||||||
components[1] += pix[i + 1];
|
|
||||||
components[0] += pix[i + 2];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (size) {
|
|
||||||
for (auto i = 0; i != 3; ++i) {
|
|
||||||
components[i] /= size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return QColor(components[0], components[1], components[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
QColor AdjustedColor(QColor original, QColor background) {
|
|
||||||
return QColor::fromHslF(
|
|
||||||
background.hslHueF(),
|
|
||||||
background.hslSaturationF(),
|
|
||||||
original.lightnessF(),
|
|
||||||
original.alphaF()
|
|
||||||
).toRgb();
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage PreprocessBackgroundImage(QImage image) {
|
|
||||||
constexpr auto kMaxSize = 2960;
|
|
||||||
|
|
||||||
if (image.format() != QImage::Format_ARGB32_Premultiplied) {
|
|
||||||
image = std::move(image).convertToFormat(
|
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
|
||||||
}
|
|
||||||
if (image.width() > 40 * image.height()) {
|
|
||||||
const auto width = 40 * image.height();
|
|
||||||
const auto height = image.height();
|
|
||||||
image = image.copy((image.width() - width) / 2, 0, width, height);
|
|
||||||
} else if (image.height() > 40 * image.width()) {
|
|
||||||
const auto width = image.width();
|
|
||||||
const auto height = 40 * image.width();
|
|
||||||
image = image.copy(0, (image.height() - height) / 2, width, height);
|
|
||||||
}
|
|
||||||
if (image.width() > kMaxSize || image.height() > kMaxSize) {
|
|
||||||
image = image.scaled(
|
|
||||||
kMaxSize,
|
|
||||||
kMaxSize,
|
|
||||||
Qt::KeepAspectRatio,
|
|
||||||
Qt::SmoothTransformation);
|
|
||||||
}
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ReadPaletteValues(const QByteArray &content, Fn<bool(QLatin1String name, QLatin1String value)> callback) {
|
bool ReadPaletteValues(const QByteArray &content, Fn<bool(QLatin1String name, QLatin1String value)> callback) {
|
||||||
if (content.size() > kThemeSchemeSizeLimit) {
|
if (content.size() > kThemeSchemeSizeLimit) {
|
||||||
LOG(("Theme Error: color scheme file too large (should be less than 1 MB, got %2)").arg(content.size()));
|
LOG(("Theme Error: color scheme file too large (should be less than 1 MB, got %2)").arg(content.size()));
|
||||||
|
|
|
@ -18,6 +18,10 @@ namespace Window {
|
||||||
class Controller;
|
class Controller;
|
||||||
} // namespace Window
|
} // namespace Window
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
struct ChatThemeBackground;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
namespace Window {
|
namespace Window {
|
||||||
namespace Theme {
|
namespace Theme {
|
||||||
|
|
||||||
|
@ -107,9 +111,6 @@ bool LoadFromContent(
|
||||||
const QByteArray &content,
|
const QByteArray &content,
|
||||||
not_null<Instance*> out,
|
not_null<Instance*> out,
|
||||||
Cached *outCache);
|
Cached *outCache);
|
||||||
[[nodiscard]] QColor CountAverageColor(const QImage &image);
|
|
||||||
[[nodiscard]] QColor AdjustedColor(QColor original, QColor background);
|
|
||||||
[[nodiscard]] QImage PreprocessBackgroundImage(QImage image);
|
|
||||||
|
|
||||||
struct BackgroundUpdate {
|
struct BackgroundUpdate {
|
||||||
enum class Type {
|
enum class Type {
|
||||||
|
@ -280,7 +281,9 @@ private:
|
||||||
|
|
||||||
[[nodiscard]] ChatBackground *Background();
|
[[nodiscard]] ChatBackground *Background();
|
||||||
|
|
||||||
bool ReadPaletteValues(const QByteArray &content, Fn<bool(QLatin1String name, QLatin1String value)> callback);
|
bool ReadPaletteValues(
|
||||||
|
const QByteArray &content,
|
||||||
|
Fn<bool(QLatin1String name, QLatin1String value)> callback);
|
||||||
|
|
||||||
} // namespace Theme
|
} // namespace Theme
|
||||||
} // namespace Window
|
} // namespace Window
|
||||||
|
|
|
@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_document_media.h"
|
#include "data/data_document_media.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
#include "ui/chat/chat_theme.h"
|
||||||
#include "ui/image/image_prepare.h"
|
#include "ui/image/image_prepare.h"
|
||||||
#include "ui/widgets/popup_menu.h"
|
#include "ui/widgets/popup_menu.h"
|
||||||
#include "ui/toast/toast.h"
|
#include "ui/toast/toast.h"
|
||||||
|
@ -185,7 +186,7 @@ void CloudListCheck::ensureContrast() {
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
const auto active = style::internal::EnsureContrast(
|
const auto active = style::internal::EnsureContrast(
|
||||||
_colors->radiobuttonActive,
|
_colors->radiobuttonActive,
|
||||||
CountAverageColor(image));
|
Ui::CountAverageColor(image));
|
||||||
_colors->radiobuttonInactive = _colors->radiobuttonActive = QColor(
|
_colors->radiobuttonInactive = _colors->radiobuttonActive = QColor(
|
||||||
active.red(),
|
active.red(),
|
||||||
active.green(),
|
active.green(),
|
||||||
|
|
|
@ -996,7 +996,7 @@ void MainMenu::refreshBackground() {
|
||||||
prepared.size());
|
prepared.size());
|
||||||
|
|
||||||
auto backgroundImage = paper.isPattern()
|
auto backgroundImage = paper.isPattern()
|
||||||
? Data::GenerateWallPaper(
|
? Ui::GenerateBackgroundImage(
|
||||||
fill * cIntRetinaFactor(),
|
fill * cIntRetinaFactor(),
|
||||||
paper.backgroundColors(),
|
paper.backgroundColors(),
|
||||||
paper.gradientRotation(),
|
paper.gradientRotation(),
|
||||||
|
|
|
@ -28,6 +28,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_chat.h"
|
#include "data/data_chat.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
|
#include "data/data_document.h"
|
||||||
|
#include "data/data_document_media.h"
|
||||||
|
#include "data/data_document_resolver.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
#include "data/data_group_call.h"
|
#include "data/data_group_call.h"
|
||||||
#include "data/data_chat_filters.h"
|
#include "data/data_chat_filters.h"
|
||||||
|
@ -71,6 +74,34 @@ namespace {
|
||||||
|
|
||||||
constexpr auto kMaxChatEntryHistorySize = 50;
|
constexpr auto kMaxChatEntryHistorySize = 50;
|
||||||
|
|
||||||
|
[[nodiscard]] Fn<void(style::palette&)> PreparePaletteCallback(
|
||||||
|
bool dark,
|
||||||
|
std::optional<QColor> accent) {
|
||||||
|
return [=](style::palette &palette) {
|
||||||
|
using namespace Theme;
|
||||||
|
if (dark) {
|
||||||
|
const auto &embedded = EmbeddedThemes();
|
||||||
|
const auto i = ranges::find(
|
||||||
|
embedded,
|
||||||
|
EmbeddedType::Night,
|
||||||
|
&EmbeddedScheme::type);
|
||||||
|
Assert(i != end(embedded));
|
||||||
|
|
||||||
|
auto instance = Instance();
|
||||||
|
const auto loaded = LoadFromFile(
|
||||||
|
i->path,
|
||||||
|
&instance,
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
accent ? ColorizerFrom(*i, *accent) : Colorizer());
|
||||||
|
Assert(loaded);
|
||||||
|
palette = instance.palette;
|
||||||
|
} else {
|
||||||
|
// #TODO themes apply accent color to classic theme
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void ActivateWindow(not_null<SessionController*> controller) {
|
void ActivateWindow(not_null<SessionController*> controller) {
|
||||||
|
@ -458,6 +489,13 @@ void SessionNavigation::showPollResults(
|
||||||
showSection(std::make_shared<Info::Memento>(poll, contextId), params);
|
showSection(std::make_shared<Info::Memento>(poll, contextId), params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SessionController::CachedTheme {
|
||||||
|
std::shared_ptr<Ui::ChatTheme> theme;
|
||||||
|
std::shared_ptr<Data::DocumentMedia> media;
|
||||||
|
Data::WallPaper paper;
|
||||||
|
rpl::lifetime lifetime;
|
||||||
|
};
|
||||||
|
|
||||||
SessionController::SessionController(
|
SessionController::SessionController(
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
not_null<Controller*> window)
|
not_null<Controller*> window)
|
||||||
|
@ -1325,8 +1363,8 @@ auto SessionController::cachedChatThemeValue(
|
||||||
return rpl::single(_defaultChatTheme);
|
return rpl::single(_defaultChatTheme);
|
||||||
}
|
}
|
||||||
const auto i = _customChatThemes.find(key);
|
const auto i = _customChatThemes.find(key);
|
||||||
if (i != end(_customChatThemes) && i->second) {
|
if (i != end(_customChatThemes) && i->second.theme) {
|
||||||
return rpl::single(i->second);
|
return rpl::single(i->second.theme);
|
||||||
}
|
}
|
||||||
if (i == end(_customChatThemes)) {
|
if (i == end(_customChatThemes)) {
|
||||||
cacheChatTheme(data);
|
cacheChatTheme(data);
|
||||||
|
@ -1358,57 +1396,119 @@ void SessionController::pushDefaultChatBackground() {
|
||||||
|
|
||||||
void SessionController::cacheChatTheme(const Data::CloudTheme &data) {
|
void SessionController::cacheChatTheme(const Data::CloudTheme &data) {
|
||||||
Expects(data.id != 0);
|
Expects(data.id != 0);
|
||||||
|
Expects(data.paper.has_value());
|
||||||
using namespace Theme;
|
Expects(!data.paper->backgroundColors().empty());
|
||||||
|
|
||||||
const auto key = data.id;
|
const auto key = data.id;
|
||||||
const auto dark = data.basedOnDark;
|
const auto document = data.paper->document();
|
||||||
const auto accent = data.accentColor;
|
const auto media = document ? document->createMediaView() : nullptr;
|
||||||
if (data.paper) {
|
data.paper->loadDocument();
|
||||||
const auto document = data.paper->document();
|
auto &theme = _customChatThemes.emplace(
|
||||||
|
key,
|
||||||
}
|
CachedTheme{ .media = media, .paper = *data.paper }).first->second;
|
||||||
const auto preparePalette = [=](style::palette &palette) {
|
|
||||||
if (dark) {
|
|
||||||
const auto &embedded = Theme::EmbeddedThemes();
|
|
||||||
const auto i = ranges::find(
|
|
||||||
embedded,
|
|
||||||
EmbeddedType::Night,
|
|
||||||
&EmbeddedScheme::type);
|
|
||||||
Assert(i != end(embedded));
|
|
||||||
|
|
||||||
auto instance = Instance();
|
|
||||||
const auto loaded = LoadFromFile(
|
|
||||||
i->path,
|
|
||||||
&instance,
|
|
||||||
nullptr,
|
|
||||||
nullptr,
|
|
||||||
accent ? ColorizerFrom(*i, *accent) : Colorizer());
|
|
||||||
Assert(loaded);
|
|
||||||
palette = instance.palette;
|
|
||||||
} else {
|
|
||||||
// #TODO themes apply accent color to classic theme
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const auto prepareBackground = [] {
|
|
||||||
return Ui::ChatThemeBackground{
|
|
||||||
|
|
||||||
};
|
|
||||||
};
|
|
||||||
auto descriptor = Ui::ChatThemeDescriptor{
|
auto descriptor = Ui::ChatThemeDescriptor{
|
||||||
.id = key,
|
.id = key,
|
||||||
.preparePalette = preparePalette,
|
.preparePalette = PreparePaletteCallback(
|
||||||
.prepareBackground = prepareBackground,
|
data.basedOnDark,
|
||||||
|
data.accentColor),
|
||||||
|
.prepareBackground = backgroundGenerator(theme),
|
||||||
};
|
};
|
||||||
crl::async([this, descriptor = std::move(descriptor), weak = base::make_weak(this)] {
|
crl::async([
|
||||||
crl::on_main(weak, [
|
this,
|
||||||
|
descriptor = std::move(descriptor),
|
||||||
|
weak = base::make_weak(this)
|
||||||
|
]{
|
||||||
|
crl::on_main(weak,[
|
||||||
this,
|
this,
|
||||||
result = std::make_shared<Ui::ChatTheme>(std::move(descriptor))
|
result = std::make_shared<Ui::ChatTheme>(std::move(descriptor))
|
||||||
]() mutable {
|
]() mutable {
|
||||||
_customChatThemes.emplace(result->key(), result);
|
cacheChatThemeDone(std::move(result));
|
||||||
_cachedThemesStream.fire(std::move(result));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
if (media && media->loaded(true)) {
|
||||||
|
theme.media = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SessionController::cacheChatThemeDone(
|
||||||
|
std::shared_ptr<Ui::ChatTheme> result) {
|
||||||
|
Expects(result != nullptr);
|
||||||
|
|
||||||
|
const auto key = result->key();
|
||||||
|
const auto i = _customChatThemes.find(key);
|
||||||
|
if (i == end(_customChatThemes)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
i->second.theme = result;
|
||||||
|
if (i->second.media) {
|
||||||
|
if (i->second.media->loaded(true)) {
|
||||||
|
updateCustomThemeBackground(i->second);
|
||||||
|
} else {
|
||||||
|
session().downloaderTaskFinished(
|
||||||
|
) | rpl::filter([=] {
|
||||||
|
const auto i = _customChatThemes.find(key);
|
||||||
|
Assert(i != end(_customChatThemes));
|
||||||
|
return !i->second.media || i->second.media->loaded(true);
|
||||||
|
}) | rpl::start_with_next([=] {
|
||||||
|
const auto i = _customChatThemes.find(key);
|
||||||
|
Assert(i != end(_customChatThemes));
|
||||||
|
updateCustomThemeBackground(i->second);
|
||||||
|
}, i->second.lifetime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_cachedThemesStream.fire(std::move(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SessionController::updateCustomThemeBackground(CachedTheme &theme) {
|
||||||
|
const auto guard = gsl::finally([&] {
|
||||||
|
theme.lifetime.destroy();
|
||||||
|
theme.media = nullptr;
|
||||||
|
});
|
||||||
|
if (!theme.media || !theme.theme || !theme.media->loaded(true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto key = theme.theme->key();
|
||||||
|
const auto weak = base::make_weak(this);
|
||||||
|
crl::async([=, generator = backgroundGenerator(theme, false)] {
|
||||||
|
crl::on_main(weak, [=, result = generator()]() mutable {
|
||||||
|
const auto i = _customChatThemes.find(key);
|
||||||
|
if (i != end(_customChatThemes)) {
|
||||||
|
i->second.theme->updateBackgroundImageFrom(std::move(result));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Fn<Ui::ChatThemeBackground()> SessionController::backgroundGenerator(
|
||||||
|
CachedTheme &theme,
|
||||||
|
bool generateGradient) {
|
||||||
|
const auto &paper = theme.paper;
|
||||||
|
const auto &media = theme.media;
|
||||||
|
const auto paperPath = media ? media->owner()->filepath() : QString();
|
||||||
|
const auto paperBytes = media ? media->bytes() : QByteArray();
|
||||||
|
const auto gzipSvg = media && media->owner()->isPatternWallPaperSVG();
|
||||||
|
const auto &colors = paper.backgroundColors();
|
||||||
|
const auto isPattern = paper.isPattern();
|
||||||
|
const auto patternOpacity = paper.patternOpacity();
|
||||||
|
const auto isBlurred = paper.isBlurred();
|
||||||
|
const auto gradientRotation = paper.gradientRotation();
|
||||||
|
return [=] {
|
||||||
|
auto result = Ui::PrepareBackgroundImage(
|
||||||
|
paperPath,
|
||||||
|
paperBytes,
|
||||||
|
gzipSvg,
|
||||||
|
colors,
|
||||||
|
isPattern,
|
||||||
|
patternOpacity,
|
||||||
|
isBlurred);
|
||||||
|
if (generateGradient) {
|
||||||
|
result.gradientForFill = (colors.size() > 1)
|
||||||
|
? Ui::GenerateDitheredGradient(colors, gradientRotation)
|
||||||
|
: QImage();
|
||||||
|
result.gradientRotation = gradientRotation;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryView::PaintContext SessionController::preparePaintContext(
|
HistoryView::PaintContext SessionController::preparePaintContext(
|
||||||
|
|
|
@ -47,6 +47,7 @@ class LayerWidget;
|
||||||
enum class ReportReason;
|
enum class ReportReason;
|
||||||
class ChatTheme;
|
class ChatTheme;
|
||||||
struct ChatPaintContext;
|
struct ChatPaintContext;
|
||||||
|
struct ChatThemeBackground;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
@ -419,6 +420,8 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct CachedTheme;
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
void initSupportMode();
|
void initSupportMode();
|
||||||
void refreshFiltersMenu();
|
void refreshFiltersMenu();
|
||||||
|
@ -445,6 +448,11 @@ private:
|
||||||
|
|
||||||
void pushDefaultChatBackground();
|
void pushDefaultChatBackground();
|
||||||
void cacheChatTheme(const Data::CloudTheme &data);
|
void cacheChatTheme(const Data::CloudTheme &data);
|
||||||
|
void cacheChatThemeDone(std::shared_ptr<Ui::ChatTheme> result);
|
||||||
|
void updateCustomThemeBackground(CachedTheme &theme);
|
||||||
|
[[nodiscard]] Fn<Ui::ChatThemeBackground()> backgroundGenerator(
|
||||||
|
CachedTheme &theme,
|
||||||
|
bool generateGradient = true);
|
||||||
|
|
||||||
const not_null<Controller*> _window;
|
const not_null<Controller*> _window;
|
||||||
|
|
||||||
|
@ -474,7 +482,7 @@ private:
|
||||||
rpl::event_stream<> _filtersMenuChanged;
|
rpl::event_stream<> _filtersMenuChanged;
|
||||||
|
|
||||||
std::shared_ptr<Ui::ChatTheme> _defaultChatTheme;
|
std::shared_ptr<Ui::ChatTheme> _defaultChatTheme;
|
||||||
base::flat_map<uint64, std::shared_ptr<Ui::ChatTheme>> _customChatThemes;
|
base::flat_map<uint64, CachedTheme> _customChatThemes;
|
||||||
rpl::event_stream<std::shared_ptr<Ui::ChatTheme>> _cachedThemesStream;
|
rpl::event_stream<std::shared_ptr<Ui::ChatTheme>> _cachedThemesStream;
|
||||||
|
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
|
|
Loading…
Add table
Reference in a new issue