Move all background helper methods to chat_theme module.

This commit is contained in:
John Preston 2021-08-27 14:32:18 +03:00
parent 0a1e84ddb2
commit f5a323e40a
16 changed files with 491 additions and 299 deletions

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "ui/effects/round_checkbox.h"
#include "ui/image/image.h"
#include "ui/chat/chat_theme.h"
#include "ui/ui_utility.h"
#include "main/main_session.h"
#include "apiwrap.h"
@ -331,7 +332,7 @@ void BackgroundBox::Inner::validatePaperThumbnail(
}
} else if (!paper.data.backgroundColors().empty()) {
paper.thumbnail = Ui::PixmapFromImage(
Data::GenerateWallPaper(
Ui::GenerateBackgroundImage(
st::backgroundSize * cIntRetinaFactor(),
paper.data.backgroundColors(),
paper.data.gradientRotation()));
@ -346,7 +347,7 @@ void BackgroundBox::Inner::validatePaperThumbnail(
: paper.dataMedia->thumbnail();
auto original = thumbnail->original();
if (paper.data.isPattern()) {
original = Data::PreparePatternImage(
original = Ui::PreparePatternImage(
std::move(original),
paper.data.backgroundColors(),
paper.data.gradientRotation(),

View file

@ -341,7 +341,7 @@ bool ServiceCheck::checkRippleStartPosition(QPoint position) const {
Images::Option blur = Images::Option(0)) {
auto result = PrepareScaledNonPattern(image, blur);
if (isPattern) {
result = Data::PreparePatternImage(
result = Ui::PreparePatternImage(
std::move(result),
background,
gradientRotation,
@ -395,7 +395,7 @@ void BackgroundPreviewBox::generateBackground() {
const auto size = QSize(st::boxWideWidth, st::boxWideWidth)
* cIntRetinaFactor();
_generated = Ui::PixmapFromImage((_paper.patternOpacity() >= 0.)
? Data::GenerateWallPaper(
? Ui::GenerateBackgroundImage(
size,
_paper.backgroundColors(),
_paper.gradientRotation())
@ -668,7 +668,7 @@ void BackgroundPreviewBox::setScaledFromThumb() {
auto blurred = (_paper.document() || _paper.isPattern())
? QImage()
: PrepareScaledNonPattern(
Data::PrepareBlurredBackground(thumbnail->original()),
Ui::PrepareBlurredBackground(thumbnail->original()),
Images::Option(0));
setScaledFromImage(std::move(scaled), std::move(blurred));
}
@ -676,7 +676,7 @@ void BackgroundPreviewBox::setScaledFromThumb() {
void BackgroundPreviewBox::setScaledFromImage(
QImage &&image,
QImage &&blurred) {
updateServiceBg({ Window::Theme::CountAverageColor(image) });
updateServiceBg({ Ui::CountAverageColor(image) });
if (!_full.isNull()) {
startFadeInFrom(std::move(_scaled));
}
@ -714,7 +714,7 @@ void BackgroundPreviewBox::updateServiceBg(const std::vector<QColor> &bg) {
green += color.green();
blue += color.blue();
}
_serviceBg = Window::Theme::AdjustedColor(
_serviceBg = Ui::ThemeAdjustedColor(
st::msgServiceBg->c,
QColor(red / count, green / count, blue / count));
}
@ -748,7 +748,7 @@ void BackgroundPreviewBox::checkLoadedDocument() {
patternOpacity);
auto blurred = !isPattern
? PrepareScaledNonPattern(
Data::PrepareBlurredBackground(image),
Ui::PrepareBlurredBackground(image),
Images::Option(0))
: QImage();
crl::on_main(std::move(guard), [
@ -763,9 +763,9 @@ void BackgroundPreviewBox::checkLoadedDocument() {
});
});
};
_generating = Data::ReadImageAsync(
_generating = Data::ReadBackgroundImageAsync(
_media.get(),
Window::Theme::PreprocessBackgroundImage,
Ui::PreprocessBackgroundImage,
generateCallback);
}

View file

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item.h"
#include "media/player/media_player_instance.h"
#include "platform/platform_file_utilities.h"
#include "ui/chat/chat_theme.h"
#include "ui/text/text_utilities.h"
#include "window/window_session_controller.h"
@ -33,8 +34,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Data {
namespace {
constexpr auto kMaxWallpaperSize = 3072;
void LaunchWithWarning(
// not_null<Window::Controller*> controller,
const QString &name,
@ -175,19 +174,7 @@ bool IsIpRevealingName(const QString &filepath) {
);
}
[[nodiscard]] QImage ReadImage(
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(
base::binary_guard ReadBackgroundImageAsync(
not_null<Data::DocumentMedia*> media,
FnMut<QImage(QImage)> postprocess,
FnMut<void(QImage&&)> done) {
@ -201,7 +188,7 @@ base::binary_guard ReadImageAsync(
guard = result.make_guard(),
callback = std::move(done)
]() mutable {
auto image = ReadImage(path, bytes, gzipSvg);
auto image = Ui::ReadBackgroundImage(path, bytes, gzipSvg);
if (postprocess) {
image = postprocess(std::move(image));
}

View file

@ -24,7 +24,7 @@ class DocumentMedia;
// [[nodiscard]] bool IsValidMediaFile(const QString &filepath);
[[nodiscard]] bool IsExecutableName(const QString &filepath);
[[nodiscard]] bool IsIpRevealingName(const QString &filepath);
base::binary_guard ReadImageAsync(
base::binary_guard ReadBackgroundImageAsync(
not_null<Data::DocumentMedia*> media,
FnMut<QImage(QImage)> postprocess,
FnMut<void(QImage&&)> done);

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_file_origin.h"
#include "data/data_session.h"
#include "storage/serialize_common.h"
#include "ui/chat/chat_theme.h"
#include "core/application.h"
#include "main/main_session.h"
@ -45,10 +46,6 @@ constexpr auto kVersion = 1;
return color ? SerializeColor(*color) : quint32(-1);
}
[[nodiscard]] QColor DefaultBackgroundColor() {
return QColor(213, 223, 233);
}
[[nodiscard]] std::vector<QColor> ColorsFromMTP(
const MTPDwallPaperSettings &data) {
auto result = std::vector<QColor>();
@ -683,89 +680,8 @@ bool IsCloudWallPaper(const WallPaper &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) {
if (paper.backgroundColors().empty()) {
return GenerateDitheredGradient({ DefaultBackgroundColor() }, 0);
}
return GenerateDitheredGradient(
return Ui::GenerateDitheredGradient(
paper.backgroundColors(),
paper.gradientRotation());
}

View file

@ -121,21 +121,6 @@ private:
[[nodiscard]] bool IsDefaultWallPaper(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]] QColor ColorFromSerialized(quint32 serialized);

View file

@ -279,7 +279,7 @@ void ThemeDocument::validateThumbnail() const {
}
void ThemeDocument::generateThumbnail() const {
_thumbnail = Ui::PixmapFromImage(Data::GenerateWallPaper(
_thumbnail = Ui::PixmapFromImage(Ui::GenerateBackgroundImage(
QSize(_pixw, _pixh) * cIntRetinaFactor(),
_background,
_gradientRotation,
@ -316,7 +316,7 @@ void ThemeDocument::prepareThumbnailFrom(
_pixw,
_pixh);
if (isPattern) {
original = Data::PreparePatternImage(
original = Ui::PreparePatternImage(
std::move(original),
_background,
_gradientRotation,

View file

@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_histories.h"
#include "data/stickers/data_stickers.h"
#include "api/api_text_entities.h"
#include "ui/chat/chat_theme.h"
#include "ui/special_buttons.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/shadow.h"
@ -1183,9 +1184,9 @@ void MainWidget::checkChatBackground() {
: background->data;
setReadyChatBackground(ready, std::move(image));
};
_background->generating = Data::ReadImageAsync(
_background->generating = Data::ReadBackgroundImageAsync(
media.get(),
Window::Theme::PreprocessBackgroundImage,
Ui::PreprocessBackgroundImage,
generateCallback);
}

View file

@ -21,6 +21,12 @@ constexpr auto kMaxChatEntryHistorySize = 50;
constexpr auto kCacheBackgroundTimeout = 3 * crl::time(1000);
constexpr auto kCacheBackgroundFastTimeout = 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(
const CacheBackgroundRequest &request) {
@ -141,7 +147,7 @@ bool operator!=(
}
CachedBackground::CachedBackground(CacheBackgroundResult &&result)
: pixmap(Ui::PixmapFromImage(std::move(result.image)))
: pixmap(PixmapFromImage(std::move(result.image)))
, area(result.area)
, x(result.x)
, y(result.y) {
@ -169,6 +175,18 @@ void ChatTheme::setBackground(ChatThemeBackground &&background) {
_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 {
return _id;
}
@ -184,7 +202,7 @@ void ChatTheme::setBubblesBackground(QImage image) {
});
}
if (!_bubblesBackgroundPattern) {
_bubblesBackgroundPattern = Ui::PrepareBubblePattern();
_bubblesBackgroundPattern = PrepareBubblePattern();
}
_bubblesBackgroundPattern->pixmap = _bubblesBackground.pixmap;
_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

View file

@ -117,6 +117,7 @@ public:
}
void setBackground(ChatThemeBackground &&background);
void updateBackgroundImageFrom(ChatThemeBackground &&background);
const ChatThemeBackground &background() const {
return _mutableBackground;
}
@ -175,4 +176,40 @@ struct ChatBackgroundRects {
QSize fillSize,
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

View file

@ -24,8 +24,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h"
#include "base/crc32hash.h"
#include "data/data_session.h"
#include "data/data_document_resolver.h"
#include "main/main_account.h" // Account::local.
#include "main/main_domain.h" // Domain::activeSessionValue.
#include "ui/chat/chat_theme.h"
#include "ui/image/image.h"
#include "ui/ui_utility.h"
#include "boxes/confirm_box.h"
@ -43,7 +45,6 @@ namespace {
constexpr auto kThemeFileSizeLimit = 5 * 1024 * 1024;
constexpr auto kBackgroundSizeLimit = 25 * 1024 * 1024;
constexpr auto kNightThemeFile = ":/gui/night.tdesktop-theme"_cs;
constexpr auto kMinimumTiledSize = 512;
struct Applying {
Saved data;
@ -58,20 +59,6 @@ inline bool AreTestingTheme() {
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) {
return !image.size().isEmpty()
&& (image.format() == QImage::Format_ARGB32_Premultiplied
@ -646,7 +633,7 @@ QImage ChatBackground::postprocessBackgroundImage(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)
&& !Data::IsDefaultWallPaper(_paper)
@ -701,7 +688,7 @@ void ChatBackground::set(const Data::WallPaper &paper, QImage image) {
if (_paper.isPattern() && !image.isNull()) {
if (bgColors.size() < 2) {
auto prepared = postprocessBackgroundImage(
Data::PreparePatternImage(
Ui::PreparePatternImage(
image,
bgColors,
_paper.gradientRotation(),
@ -752,7 +739,7 @@ void ChatBackground::setPrepared(
Expects(gradient.isNull() || GoodImageFormatAndSize(gradient));
if (!prepared.isNull() && !_paper.isPattern() && _paper.isBlurred()) {
prepared = Data::PrepareBlurredBackground(std::move(prepared));
prepared = Ui::PrepareBlurredBackground(std::move(prepared));
}
if (adjustPaletteRequired()) {
if (!gradient.isNull()) {
@ -768,40 +755,9 @@ void ChatBackground::setPrepared(
_prepared = std::move(prepared);
_gradient = std::move(gradient);
_imageMonoColor = _gradient.isNull()
? CalculateImageMonoColor(_prepared)
? Ui::CalculateImageMonoColor(_prepared)
: std::nullopt;
prepareImageForTiled();
}
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);
}
}
_preparedForTiled = Ui::PrepareImageForTiled(_prepared);
}
void ChatBackground::setPaper(const Data::WallPaper &paper) {
@ -850,13 +806,13 @@ void ChatBackground::clearEditingTheme(ClearEditing clear) {
}
void ChatBackground::adjustPaletteUsingBackground(const QImage &image) {
adjustPaletteUsingColor(CountAverageColor(image));
adjustPaletteUsingColor(Ui::CountAverageColor(image));
}
void ChatBackground::adjustPaletteUsingColor(QColor color) {
const auto prepared = color.toHsl();
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(
adjusted.red(),
adjusted.green(),
@ -885,10 +841,7 @@ void ChatBackground::recacheGradientForFill(QImage gradient) {
QImage ChatBackground::createCurrentImage() const {
if (const auto fill = colorForFill()) {
auto result = QImage(
kMinimumTiledSize,
kMinimumTiledSize,
QImage::Format_ARGB32_Premultiplied);
auto result = QImage(512, 512, QImage::Format_ARGB32_Premultiplied);
result.fill(*fill);
return result;
} else if (_gradient.isNull()) {
@ -1477,64 +1430,6 @@ QString EditingPalettePath() {
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) {
if (content.size() > kThemeSchemeSizeLimit) {
LOG(("Theme Error: color scheme file too large (should be less than 1 MB, got %2)").arg(content.size()));

View file

@ -18,6 +18,10 @@ namespace Window {
class Controller;
} // namespace Window
namespace Ui {
struct ChatThemeBackground;
} // namespace Ui
namespace Window {
namespace Theme {
@ -107,9 +111,6 @@ bool LoadFromContent(
const QByteArray &content,
not_null<Instance*> out,
Cached *outCache);
[[nodiscard]] QColor CountAverageColor(const QImage &image);
[[nodiscard]] QColor AdjustedColor(QColor original, QColor background);
[[nodiscard]] QImage PreprocessBackgroundImage(QImage image);
struct BackgroundUpdate {
enum class Type {
@ -280,7 +281,9 @@ private:
[[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 Window

View file

@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_session.h"
#include "ui/chat/chat_theme.h"
#include "ui/image/image_prepare.h"
#include "ui/widgets/popup_menu.h"
#include "ui/toast/toast.h"
@ -185,7 +186,7 @@ void CloudListCheck::ensureContrast() {
QImage::Format_ARGB32_Premultiplied);
const auto active = style::internal::EnsureContrast(
_colors->radiobuttonActive,
CountAverageColor(image));
Ui::CountAverageColor(image));
_colors->radiobuttonInactive = _colors->radiobuttonActive = QColor(
active.red(),
active.green(),

View file

@ -996,7 +996,7 @@ void MainMenu::refreshBackground() {
prepared.size());
auto backgroundImage = paper.isPattern()
? Data::GenerateWallPaper(
? Ui::GenerateBackgroundImage(
fill * cIntRetinaFactor(),
paper.backgroundColors(),
paper.gradientRotation(),

View file

@ -28,6 +28,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_channel.h"
#include "data/data_chat.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_group_call.h"
#include "data/data_chat_filters.h"
@ -71,6 +74,34 @@ namespace {
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
void ActivateWindow(not_null<SessionController*> controller) {
@ -458,6 +489,13 @@ void SessionNavigation::showPollResults(
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(
not_null<Main::Session*> session,
not_null<Controller*> window)
@ -1325,8 +1363,8 @@ auto SessionController::cachedChatThemeValue(
return rpl::single(_defaultChatTheme);
}
const auto i = _customChatThemes.find(key);
if (i != end(_customChatThemes) && i->second) {
return rpl::single(i->second);
if (i != end(_customChatThemes) && i->second.theme) {
return rpl::single(i->second.theme);
}
if (i == end(_customChatThemes)) {
cacheChatTheme(data);
@ -1358,57 +1396,119 @@ void SessionController::pushDefaultChatBackground() {
void SessionController::cacheChatTheme(const Data::CloudTheme &data) {
Expects(data.id != 0);
using namespace Theme;
Expects(data.paper.has_value());
Expects(!data.paper->backgroundColors().empty());
const auto key = data.id;
const auto dark = data.basedOnDark;
const auto accent = data.accentColor;
if (data.paper) {
const auto document = data.paper->document();
}
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{
};
};
const auto document = data.paper->document();
const auto media = document ? document->createMediaView() : nullptr;
data.paper->loadDocument();
auto &theme = _customChatThemes.emplace(
key,
CachedTheme{ .media = media, .paper = *data.paper }).first->second;
auto descriptor = Ui::ChatThemeDescriptor{
.id = key,
.preparePalette = preparePalette,
.prepareBackground = prepareBackground,
.preparePalette = PreparePaletteCallback(
data.basedOnDark,
data.accentColor),
.prepareBackground = backgroundGenerator(theme),
};
crl::async([this, descriptor = std::move(descriptor), weak = base::make_weak(this)] {
crl::on_main(weak, [
crl::async([
this,
descriptor = std::move(descriptor),
weak = base::make_weak(this)
]{
crl::on_main(weak,[
this,
result = std::make_shared<Ui::ChatTheme>(std::move(descriptor))
]() mutable {
_customChatThemes.emplace(result->key(), result);
_cachedThemesStream.fire(std::move(result));
cacheChatThemeDone(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(

View file

@ -47,6 +47,7 @@ class LayerWidget;
enum class ReportReason;
class ChatTheme;
struct ChatPaintContext;
struct ChatThemeBackground;
} // namespace Ui
namespace Data {
@ -419,6 +420,8 @@ public:
}
private:
struct CachedTheme;
void init();
void initSupportMode();
void refreshFiltersMenu();
@ -445,6 +448,11 @@ private:
void pushDefaultChatBackground();
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;
@ -474,7 +482,7 @@ private:
rpl::event_stream<> _filtersMenuChanged;
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::lifetime _lifetime;