From 95565c39edff9b088d50885a7144bcf82c4847d8 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Fri, 8 Feb 2019 19:20:08 +0300
Subject: [PATCH] Upload wallpapers to the cloud.

---
 Telegram/SourceFiles/apiwrap.cpp              |   1 -
 Telegram/SourceFiles/boxes/background_box.cpp |   4 +-
 Telegram/SourceFiles/boxes/boxes.style        |   2 +-
 Telegram/SourceFiles/data/data_wall_paper.cpp | 598 ++++++++++++++++
 Telegram/SourceFiles/data/data_wall_paper.h   | 113 +++
 Telegram/SourceFiles/storage/file_upload.cpp  |  20 +-
 .../SourceFiles/storage/localimageloader.cpp  |  65 ++
 .../SourceFiles/storage/localimageloader.h    |   2 +
 .../window/themes/window_theme.cpp            | 646 ++----------------
 .../SourceFiles/window/themes/window_theme.h  | 114 +---
 Telegram/gyp/telegram_sources.txt             |   2 +
 11 files changed, 880 insertions(+), 687 deletions(-)
 create mode 100644 Telegram/SourceFiles/data/data_wall_paper.cpp
 create mode 100644 Telegram/SourceFiles/data/data_wall_paper.h

diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index 6ff21a1ed..8f7b16f22 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -5142,7 +5142,6 @@ void ApiWrap::photoUploadReady(
 			)).done(applier).afterRequest(history->sendRequestId).send();
 		}
 	}
-
 }
 
 void ApiWrap::clearPeerPhoto(not_null<PhotoData*> photo) {
diff --git a/Telegram/SourceFiles/boxes/background_box.cpp b/Telegram/SourceFiles/boxes/background_box.cpp
index 0aaa2d4ba..3ba8d4229 100644
--- a/Telegram/SourceFiles/boxes/background_box.cpp
+++ b/Telegram/SourceFiles/boxes/background_box.cpp
@@ -110,7 +110,7 @@ BackgroundBox::Inner::Inner(QWidget *parent) : RpWidget(parent)
 , _check(std::make_unique<Ui::RoundCheckbox>(st::overviewCheck, [=] { update(); })) {
 	_check->setChecked(true, Ui::RoundCheckbox::SetStyle::Fast);
 	if (Auth().data().wallpapers().empty()) {
-		resize(kBackgroundsInRow * (st::backgroundSize.width() + st::backgroundPadding) + st::backgroundPadding, 2 * (st::backgroundSize.height() + st::backgroundPadding) + st::backgroundPadding);
+		resize(st::boxWideWidth, 2 * (st::backgroundSize.height() + st::backgroundPadding) + st::backgroundPadding);
 	} else {
 		updatePapers();
 	}
@@ -169,7 +169,7 @@ void BackgroundBox::Inner::updatePapers() {
 	const auto rows = (count / kBackgroundsInRow)
 		+ (count % kBackgroundsInRow ? 1 : 0);
 
-	resize(kBackgroundsInRow * (st::backgroundSize.width() + st::backgroundPadding) + st::backgroundPadding, rows * (st::backgroundSize.height() + st::backgroundPadding) + st::backgroundPadding);
+	resize(st::boxWideWidth, rows * (st::backgroundSize.height() + st::backgroundPadding) + st::backgroundPadding);
 
 	const auto preload = kBackgroundsInRow * 3;
 	for (const auto &paper : _papers | ranges::view::take(preload)) {
diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style
index 95ee03d69..e2d5f1faa 100644
--- a/Telegram/SourceFiles/boxes/boxes.style
+++ b/Telegram/SourceFiles/boxes/boxes.style
@@ -943,7 +943,7 @@ backgroundCheck: ServiceCheck {
 	diameter: 18px;
 	shift: 2px;
 	thickness: 2px;
-	tip: point(8px, 13px);
+	tip: point(7px, 13px);
 	small: 3px;
 	large: 6px;
 	stroke: 2px;
diff --git a/Telegram/SourceFiles/data/data_wall_paper.cpp b/Telegram/SourceFiles/data/data_wall_paper.cpp
new file mode 100644
index 000000000..798ac52ed
--- /dev/null
+++ b/Telegram/SourceFiles/data/data_wall_paper.cpp
@@ -0,0 +1,598 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "data/data_wall_paper.h"
+
+#include "data/data_document.h"
+#include "data/data_file_origin.h"
+#include "data/data_session.h"
+#include "storage/serialize_common.h"
+#include "core/application.h"
+#include "auth_session.h"
+
+namespace Data {
+namespace {
+
+constexpr auto FromLegacyBackgroundId(int32 legacyId) -> WallPaperId {
+	return uint64(0xFFFFFFFF00000000ULL) | uint64(uint32(legacyId));
+}
+
+constexpr auto kUninitializedBackground = FromLegacyBackgroundId(-999);
+constexpr auto kTestingThemeBackground = FromLegacyBackgroundId(-666);
+constexpr auto kTestingDefaultBackground = FromLegacyBackgroundId(-665);
+constexpr auto kTestingEditorBackground = FromLegacyBackgroundId(-664);
+constexpr auto kThemeBackground = FromLegacyBackgroundId(-2);
+constexpr auto kCustomBackground = FromLegacyBackgroundId(-1);
+constexpr auto kLegacy1DefaultBackground = FromLegacyBackgroundId(0);
+constexpr auto kDefaultBackground = 5947530738516623361;
+constexpr auto kIncorrectDefaultBackground = FromLegacyBackgroundId(105);
+
+quint32 SerializeMaybeColor(std::optional<QColor> color) {
+	return color
+		? ((quint32(std::clamp(color->red(), 0, 255)) << 16)
+			| (quint32(std::clamp(color->green(), 0, 255)) << 8)
+			| quint32(std::clamp(color->blue(), 0, 255)))
+		: quint32(-1);
+}
+
+std::optional<QColor> MaybeColorFromSerialized(quint32 serialized) {
+	return (serialized == quint32(-1))
+		? std::nullopt
+		: std::make_optional(QColor(
+			int((serialized >> 16) & 0xFFU),
+			int((serialized >> 8) & 0xFFU),
+			int(serialized & 0xFFU)));
+}
+
+std::optional<QColor> ColorFromString(const QString &string) {
+	if (string.size() != 6) {
+		return {};
+	} else if (ranges::find_if(string, [](QChar ch) {
+		return (ch < 'a' || ch > 'f')
+			&& (ch < 'A' || ch > 'F')
+			&& (ch < '0' || ch > '9');
+	}) != string.end()) {
+		return {};
+	}
+	const auto component = [](const QString &text, int index) {
+		const auto decimal = [](QChar hex) {
+			const auto code = hex.unicode();
+			return (code >= '0' && code <= '9')
+				? int(code - '0')
+				: (code >= 'a' && code <= 'f')
+				? int(code - 'a' + 0x0a)
+				: int(code - 'A' + 0x0a);
+		};
+		index *= 2;
+		return decimal(text[index]) * 0x10 + decimal(text[index + 1]);
+	};
+	return QColor(
+		component(string, 0),
+		component(string, 1),
+		component(string, 2),
+		255);
+}
+
+QString StringFromColor(QColor color) {
+	const auto component = [](int value) {
+		const auto hex = [](int value) {
+			value = std::clamp(value, 0, 15);
+			return (value > 9)
+				? ('a' + (value - 10))
+				: ('0' + value);
+		};
+		return QString() + hex(value / 16) + hex(value % 16);
+	};
+	return component(color.red())
+		+ component(color.green())
+		+ component(color.blue());
+}
+
+} // namespace
+
+WallPaper::WallPaper(WallPaperId id) : _id(id) {
+}
+
+void WallPaper::setLocalImageAsThumbnail(std::shared_ptr<Image> image) {
+	Expects(IsDefaultWallPaper(*this)
+		|| IsLegacy1DefaultWallPaper(*this)
+		|| IsCustomWallPaper(*this));
+	Expects(_thumbnail == nullptr);
+
+	_thumbnail = std::move(image);
+}
+
+WallPaperId WallPaper::id() const {
+	return _id;
+}
+
+std::optional<QColor> WallPaper::backgroundColor() const {
+	return _backgroundColor;
+}
+
+DocumentData *WallPaper::document() const {
+	return _document;
+}
+
+Image *WallPaper::thumbnail() const {
+	return _thumbnail
+		? _thumbnail.get()
+		: _document
+		? _document->thumbnail()
+		: nullptr;
+}
+
+bool WallPaper::isPattern() const {
+	return _flags & MTPDwallPaper::Flag::f_pattern;
+}
+
+bool WallPaper::isDefault() const {
+	return _flags & MTPDwallPaper::Flag::f_default;
+}
+
+bool WallPaper::isCreator() const {
+	return _flags & MTPDwallPaper::Flag::f_creator;
+}
+
+bool WallPaper::isDark() const {
+	return _flags & MTPDwallPaper::Flag::f_dark;
+}
+
+bool WallPaper::isLocal() const {
+	return !document() && thumbnail();
+}
+
+bool WallPaper::isBlurred() const {
+	return _settings & MTPDwallPaperSettings::Flag::f_blur;
+}
+
+int WallPaper::patternIntensity() const {
+	return _intensity;
+}
+
+bool WallPaper::hasShareUrl() const {
+	return !_slug.isEmpty();
+}
+
+QString WallPaper::shareUrl() const {
+	if (!hasShareUrl()) {
+		return QString();
+	}
+	const auto base = Core::App().createInternalLinkFull("bg/" + _slug);
+	auto params = QStringList();
+	if (isPattern()) {
+		if (_backgroundColor) {
+			params.push_back("bg_color=" + StringFromColor(*_backgroundColor));
+		}
+		if (_intensity) {
+			params.push_back("intensity=" + QString::number(_intensity));
+		}
+	}
+	auto mode = QStringList();
+	if (_settings & MTPDwallPaperSettings::Flag::f_blur) {
+		mode.push_back("blur");
+	}
+	if (_settings & MTPDwallPaperSettings::Flag::f_motion) {
+		mode.push_back("motion");
+	}
+	if (!mode.isEmpty()) {
+		params.push_back("mode=" + mode.join('+'));
+	}
+	return params.isEmpty()
+		? base
+		: base + '?' + params.join('&');
+}
+
+void WallPaper::loadThumbnail() const {
+	if (_thumbnail) {
+		_thumbnail->load(fileOrigin());
+	}
+	if (_document) {
+		_document->loadThumbnail(fileOrigin());
+	}
+}
+
+void WallPaper::loadDocument() const {
+	if (_document) {
+		_document->save(fileOrigin(), QString());
+	}
+}
+
+FileOrigin WallPaper::fileOrigin() const {
+	return FileOriginWallpaper(_id, _accessHash);
+}
+
+MTPWallPaperSettings WallPaper::mtpSettings() const {
+	return MTP_wallPaperSettings(
+		MTP_flags(_settings),
+		(_backgroundColor
+			? MTP_int(SerializeMaybeColor(_backgroundColor))
+			: MTP_int(0)),
+		MTP_int(_intensity));
+}
+
+WallPaper WallPaper::withUrlParams(
+		const QMap<QString, QString> &params) const {
+	using Flag = MTPDwallPaperSettings::Flag;
+
+	auto result = *this;
+	result._settings = Flag(0);
+	result._backgroundColor = ColorFromString(_slug);
+	result._intensity = kDefaultIntensity;
+
+	if (auto mode = params.value("mode"); !mode.isEmpty()) {
+		const auto list = mode.replace('+', ' ').split(' ');
+		for (const auto &change : list) {
+			if (change == qstr("blur")) {
+				result._settings |= Flag::f_blur;
+			} else if (change == qstr("motion")) {
+				result._settings |= Flag::f_motion;
+			}
+		}
+	}
+	if (const auto color = ColorFromString(params.value("bg_color"))) {
+		result._settings |= Flag::f_background_color;
+		result._backgroundColor = color;
+	}
+	if (const auto string = params.value("intensity"); !string.isEmpty()) {
+		auto ok = false;
+		const auto intensity = string.toInt(&ok);
+		if (ok && base::in_range(intensity, 0, 101)) {
+			result._settings |= Flag::f_intensity;
+			result._intensity = intensity;
+		}
+	}
+
+	return result;
+}
+
+WallPaper WallPaper::withBlurred(bool blurred) const {
+	using Flag = MTPDwallPaperSettings::Flag;
+
+	auto result = *this;
+	if (blurred) {
+		result._settings |= Flag::f_blur;
+	} else {
+		result._settings &= ~Flag::f_blur;
+	}
+	return result;
+}
+
+WallPaper WallPaper::withPatternIntensity(int intensity) const {
+	using Flag = MTPDwallPaperSettings::Flag;
+
+	auto result = *this;
+	result._settings |= Flag::f_intensity;
+	result._intensity = intensity;
+	return result;
+}
+
+WallPaper WallPaper::withBackgroundColor(QColor color) const {
+	using Flag = MTPDwallPaperSettings::Flag;
+
+	auto result = *this;
+	result._settings |= Flag::f_background_color;
+	result._backgroundColor = color;
+	if (ColorFromString(_slug)) {
+		result._slug = StringFromColor(color);
+	}
+	return result;
+}
+
+WallPaper WallPaper::withParamsFrom(const WallPaper &other) const {
+	auto result = *this;
+	result._settings = other._settings;
+	if (other._backgroundColor || !ColorFromString(_slug)) {
+		result._backgroundColor = other._backgroundColor;
+		if (ColorFromString(_slug)) {
+			result._slug = StringFromColor(*result._backgroundColor);
+		}
+	}
+	result._intensity = other._intensity;
+	return result;
+}
+
+WallPaper WallPaper::withoutImageData() const {
+	auto result = *this;
+	result._thumbnail = nullptr;
+	return result;
+}
+
+std::optional<WallPaper> WallPaper::Create(const MTPWallPaper &data) {
+	return data.match([](const MTPDwallPaper &data) {
+		return Create(data);
+	});
+}
+
+std::optional<WallPaper> WallPaper::Create(const MTPDwallPaper &data) {
+	using Flag = MTPDwallPaper::Flag;
+
+	const auto document = Auth().data().processDocument(
+		data.vdocument);
+	if (!document->checkWallPaperProperties()) {
+		return std::nullopt;
+	}
+	auto result = WallPaper(data.vid.v);
+	result._accessHash = data.vaccess_hash.v;
+	result._flags = data.vflags.v;
+	result._slug = qs(data.vslug);
+	result._document = document;
+	if (data.has_settings()) {
+		const auto isPattern = ((result._flags & Flag::f_pattern) != 0);
+		data.vsettings.match([&](const MTPDwallPaperSettings &data) {
+			using Flag = MTPDwallPaperSettings::Flag;
+
+			result._settings = data.vflags.v;
+			if (isPattern && data.has_background_color()) {
+				result._backgroundColor = MaybeColorFromSerialized(
+					data.vbackground_color.v);
+			} else {
+				result._settings &= ~Flag::f_background_color;
+			}
+			if (isPattern && data.has_intensity()) {
+				result._intensity = data.vintensity.v;
+			} else {
+				result._settings &= ~Flag::f_intensity;
+			}
+		});
+	}
+	return result;
+}
+
+QByteArray WallPaper::serialize() const {
+	auto size = sizeof(quint64) // _id
+		+ sizeof(quint64) // _accessHash
+		+ sizeof(qint32) // _flags
+		+ Serialize::stringSize(_slug)
+		+ sizeof(qint32) // _settings
+		+ sizeof(quint32) // _backgroundColor
+		+ sizeof(qint32); // _intensity
+
+	auto result = QByteArray();
+	result.reserve(size);
+	{
+		auto stream = QDataStream(&result, QIODevice::WriteOnly);
+		stream.setVersion(QDataStream::Qt_5_1);
+		stream
+			<< quint64(_id)
+			<< quint64(_accessHash)
+			<< qint32(_flags)
+			<< _slug
+			<< qint32(_settings)
+			<< SerializeMaybeColor(_backgroundColor)
+			<< qint32(_intensity);
+	}
+	return result;
+}
+
+std::optional<WallPaper> WallPaper::FromSerialized(
+		const QByteArray &serialized) {
+	if (serialized.isEmpty()) {
+		return std::nullopt;
+	}
+
+	auto id = quint64();
+	auto accessHash = quint64();
+	auto flags = qint32();
+	auto slug = QString();
+	auto settings = qint32();
+	auto backgroundColor = quint32();
+	auto intensity = qint32();
+
+	auto stream = QDataStream(serialized);
+	stream.setVersion(QDataStream::Qt_5_1);
+	stream
+		>> id
+		>> accessHash
+		>> flags
+		>> slug
+		>> settings
+		>> backgroundColor
+		>> intensity;
+	if (stream.status() != QDataStream::Ok) {
+		return std::nullopt;
+	} else if (intensity < 0 || intensity > 100) {
+		return std::nullopt;
+	}
+	auto result = WallPaper(id);
+	result._accessHash = accessHash;
+	result._flags = MTPDwallPaper::Flags::from_raw(flags);
+	result._slug = slug;
+	result._settings = MTPDwallPaperSettings::Flags::from_raw(settings);
+	result._backgroundColor = MaybeColorFromSerialized(backgroundColor);
+	result._intensity = intensity;
+	return result;
+}
+
+std::optional<WallPaper> WallPaper::FromLegacySerialized(
+		quint64 id,
+		quint64 accessHash,
+		quint32 flags,
+		QString slug) {
+	auto result = WallPaper(id);
+	result._accessHash = accessHash;
+	result._flags = MTPDwallPaper::Flags::from_raw(flags);
+	result._slug = slug;
+	result._backgroundColor = ColorFromString(slug);
+	return result;
+}
+
+std::optional<WallPaper> WallPaper::FromLegacyId(qint32 legacyId) {
+	auto result = WallPaper(FromLegacyBackgroundId(legacyId));
+	if (!IsCustomWallPaper(result)) {
+		result._flags = MTPDwallPaper::Flag::f_default;
+	}
+	return result;
+}
+
+std::optional<WallPaper> WallPaper::FromColorSlug(const QString &slug) {
+	if (const auto color = ColorFromString(slug)) {
+		auto result = CustomWallPaper();
+		result._slug = slug;
+		result._backgroundColor = color;
+		return result;
+	}
+	return std::nullopt;
+}
+
+WallPaper ThemeWallPaper() {
+	return WallPaper(kThemeBackground);
+}
+
+bool IsThemeWallPaper(const WallPaper &paper) {
+	return (paper.id() == kThemeBackground);
+}
+
+WallPaper CustomWallPaper() {
+	return WallPaper(kCustomBackground);
+}
+
+bool IsCustomWallPaper(const WallPaper &paper) {
+	return (paper.id() == kCustomBackground);
+}
+
+WallPaper Legacy1DefaultWallPaper() {
+	return WallPaper(kLegacy1DefaultBackground);
+}
+
+bool IsLegacy1DefaultWallPaper(const WallPaper &paper) {
+	return (paper.id() == kLegacy1DefaultBackground);
+}
+
+WallPaper DefaultWallPaper() {
+	return WallPaper(kDefaultBackground);
+}
+
+bool IsDefaultWallPaper(const WallPaper &paper) {
+	return (paper.id() == kDefaultBackground)
+		|| (paper.id() == kIncorrectDefaultBackground);
+}
+
+QColor PatternColor(QColor background) {
+	const auto hue = background.hueF();
+	const auto saturation = background.saturationF();
+	const auto value = background.valueF();
+	return QColor::fromHsvF(
+		hue,
+		std::min(1.0, saturation + 0.05 + 0.1 * (1. - saturation)),
+		(value > 0.5
+			? std::max(0., value * 0.65)
+			: std::max(0., std::min(1., 1. - value * 0.65))),
+		0.4
+	).toRgb();
+}
+
+QImage PreparePatternImage(
+		QImage image,
+		QColor bg,
+		QColor fg,
+		int intensity) {
+	if (image.format() != QImage::Format_ARGB32_Premultiplied) {
+		image = std::move(image).convertToFormat(
+			QImage::Format_ARGB32_Premultiplied);
+	}
+	// Similar to ColorizePattern.
+	// But here we set bg to all 'alpha=0' pixels and fg to opaque ones.
+
+	const auto width = image.width();
+	const auto height = image.height();
+	const auto alpha = anim::interpolate(
+		0,
+		255,
+		fg.alphaF() * std::clamp(intensity / 100., 0., 1.));
+	if (!alpha) {
+		image.fill(bg);
+		return image;
+	}
+	fg.setAlpha(255);
+	const auto patternBg = anim::shifted(bg);
+	const auto patternFg = anim::shifted(fg);
+
+	const auto resultBytesPerPixel = (image.depth() >> 3);
+	constexpr auto resultIntsPerPixel = 1;
+	const auto resultIntsPerLine = (image.bytesPerLine() >> 2);
+	const auto resultIntsAdded = resultIntsPerLine - width * resultIntsPerPixel;
+	auto resultInts = reinterpret_cast<uint32*>(image.bits());
+	Assert(resultIntsAdded >= 0);
+	Assert(image.depth() == static_cast<int>((resultIntsPerPixel * sizeof(uint32)) << 3));
+	Assert(image.bytesPerLine() == (resultIntsPerLine << 2));
+
+	const auto maskBytesPerPixel = (image.depth() >> 3);
+	const auto maskBytesPerLine = image.bytesPerLine();
+	const auto maskBytesAdded = maskBytesPerLine - width * maskBytesPerPixel;
+
+	// We want to read the last byte of four available.
+	// This is the difference with style::colorizeImage.
+	auto maskBytes = image.constBits() + (maskBytesPerPixel - 1);
+	Assert(maskBytesAdded >= 0);
+	Assert(image.depth() == (maskBytesPerPixel << 3));
+	for (auto y = 0; y != height; ++y) {
+		for (auto x = 0; x != width; ++x) {
+			const auto maskOpacity = static_cast<anim::ShiftedMultiplier>(
+				*maskBytes) + 1;
+			const auto fgOpacity = (maskOpacity * alpha) >> 8;
+			const auto bgOpacity = 256 - fgOpacity;
+			*resultInts = anim::unshifted(
+				patternBg * bgOpacity + patternFg * fgOpacity);
+			maskBytes += maskBytesPerPixel;
+			resultInts += resultIntsPerPixel;
+		}
+		maskBytes += maskBytesAdded;
+		resultInts += resultIntsAdded;
+	}
+	return image;
+}
+
+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);
+}
+
+namespace details {
+
+WallPaper UninitializedWallPaper() {
+	return WallPaper(kUninitializedBackground);
+}
+
+bool IsUninitializedWallPaper(const WallPaper &paper) {
+	return (paper.id() == kUninitializedBackground);
+}
+
+WallPaper TestingThemeWallPaper() {
+	return WallPaper(kTestingThemeBackground);
+}
+
+bool IsTestingThemeWallPaper(const WallPaper &paper) {
+	return (paper.id() == kTestingThemeBackground);
+}
+
+WallPaper TestingDefaultWallPaper() {
+	return WallPaper(kTestingDefaultBackground);
+}
+
+bool IsTestingDefaultWallPaper(const WallPaper &paper) {
+	return (paper.id() == kTestingDefaultBackground);
+}
+
+WallPaper TestingEditorWallPaper() {
+	return WallPaper(kTestingEditorBackground);
+}
+
+bool IsTestingEditorWallPaper(const WallPaper &paper) {
+	return (paper.id() == kTestingEditorBackground);
+}
+
+} // namespace details
+} // namespace Data
diff --git a/Telegram/SourceFiles/data/data_wall_paper.h b/Telegram/SourceFiles/data/data_wall_paper.h
new file mode 100644
index 000000000..dc1445d66
--- /dev/null
+++ b/Telegram/SourceFiles/data/data_wall_paper.h
@@ -0,0 +1,113 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+class Image;
+
+namespace Data {
+
+struct FileOrigin;
+
+class WallPaper {
+public:
+	explicit WallPaper(WallPaperId id);
+
+	void setLocalImageAsThumbnail(std::shared_ptr<Image> image);
+
+	[[nodiscard]] WallPaperId id() const;
+	[[nodiscard]] std::optional<QColor> backgroundColor() const;
+	[[nodiscard]] DocumentData *document() const;
+	[[nodiscard]] Image *thumbnail() const;
+	[[nodiscard]] bool isPattern() const;
+	[[nodiscard]] bool isDefault() const;
+	[[nodiscard]] bool isCreator() const;
+	[[nodiscard]] bool isDark() const;
+	[[nodiscard]] bool isLocal() const;
+	[[nodiscard]] bool isBlurred() const;
+	[[nodiscard]] int patternIntensity() const;
+	[[nodiscard]] bool hasShareUrl() const;
+	[[nodiscard]] QString shareUrl() const;
+
+	void loadDocument() const;
+	void loadThumbnail() const;
+	[[nodiscard]] FileOrigin fileOrigin() const;
+	[[nodiscard]] MTPWallPaperSettings mtpSettings() const;
+
+	[[nodiscard]] WallPaper withUrlParams(
+		const QMap<QString, QString> &params) const;
+	[[nodiscard]] WallPaper withBlurred(bool blurred) const;
+	[[nodiscard]] WallPaper withPatternIntensity(int intensity) const;
+	[[nodiscard]] WallPaper withBackgroundColor(QColor color) const;
+	[[nodiscard]] WallPaper withParamsFrom(const WallPaper &other) const;
+	[[nodiscard]] WallPaper withoutImageData() const;
+
+	[[nodiscard]] static std::optional<WallPaper> Create(
+		const MTPWallPaper &data);
+	[[nodiscard]] static std::optional<WallPaper> Create(
+		const MTPDwallPaper &data);
+
+	[[nodiscard]] QByteArray serialize() const;
+	[[nodiscard]] static std::optional<WallPaper> FromSerialized(
+		const QByteArray &serialized);
+	[[nodiscard]] static std::optional<WallPaper> FromLegacySerialized(
+		quint64 id,
+		quint64 accessHash,
+		quint32 flags,
+		QString slug);
+	[[nodiscard]] static std::optional<WallPaper> FromLegacyId(
+		qint32 legacyId);
+	[[nodiscard]] static std::optional<WallPaper> FromColorSlug(
+		const QString &slug);
+
+private:
+	static constexpr auto kDefaultIntensity = 40;
+
+	WallPaperId _id = WallPaperId();
+	uint64 _accessHash = 0;
+	MTPDwallPaper::Flags _flags;
+	QString _slug;
+
+	MTPDwallPaperSettings::Flags _settings;
+	std::optional<QColor> _backgroundColor;
+	int _intensity = kDefaultIntensity;
+
+	DocumentData *_document = nullptr;
+	std::shared_ptr<Image> _thumbnail;
+
+};
+
+[[nodiscard]] WallPaper ThemeWallPaper();
+[[nodiscard]] bool IsThemeWallPaper(const WallPaper &paper);
+[[nodiscard]] WallPaper CustomWallPaper();
+[[nodiscard]] bool IsCustomWallPaper(const WallPaper &paper);
+[[nodiscard]] WallPaper Legacy1DefaultWallPaper();
+[[nodiscard]] bool IsLegacy1DefaultWallPaper(const WallPaper &paper);
+[[nodiscard]] WallPaper DefaultWallPaper();
+[[nodiscard]] bool IsDefaultWallPaper(const WallPaper &paper);
+
+QColor PatternColor(QColor background);
+QImage PreparePatternImage(
+	QImage image,
+	QColor bg,
+	QColor fg,
+	int intensity);
+QImage PrepareBlurredBackground(QImage image);
+
+namespace details {
+
+[[nodiscard]] WallPaper UninitializedWallPaper();
+[[nodiscard]] bool IsUninitializedWallPaper(const WallPaper &paper);
+[[nodiscard]] WallPaper TestingThemeWallPaper();
+[[nodiscard]] bool IsTestingThemeWallPaper(const WallPaper &paper);
+[[nodiscard]] WallPaper TestingDefaultWallPaper();
+[[nodiscard]] bool IsTestingDefaultWallPaper(const WallPaper &paper);
+[[nodiscard]] WallPaper TestingEditorWallPaper();
+[[nodiscard]] bool IsTestingEditorWallPaper(const WallPaper &paper);
+
+} // namespace details
+} // namespace Data
diff --git a/Telegram/SourceFiles/storage/file_upload.cpp b/Telegram/SourceFiles/storage/file_upload.cpp
index 846a51424..522ff6330 100644
--- a/Telegram/SourceFiles/storage/file_upload.cpp
+++ b/Telegram/SourceFiles/storage/file_upload.cpp
@@ -75,7 +75,9 @@ struct Uploader::File {
 
 Uploader::File::File(const SendMediaReady &media) : media(media) {
 	partsCount = media.parts.size();
-	if (type() == SendMediaType::File || type() == SendMediaType::Audio) {
+	if (type() == SendMediaType::File
+		|| type() == SendMediaType::WallPaper
+		|| type() == SendMediaType::Audio) {
 		setDocSize(media.file.isEmpty()
 			? media.data.size()
 			: media.filesize);
@@ -89,7 +91,9 @@ Uploader::File::File(const std::shared_ptr<FileLoadResult> &file)
 		|| type() == SendMediaType::Secure)
 		? file->fileparts.size()
 		: file->thumbparts.size();
-	if (type() == SendMediaType::File || type() == SendMediaType::Audio) {
+	if (type() == SendMediaType::File
+		|| type() == SendMediaType::WallPaper
+		|| type() == SendMediaType::Audio) {
 		setDocSize(file->filesize);
 	} else {
 		docSize = docPartSize = docPartsCount = 0;
@@ -149,6 +153,7 @@ void Uploader::uploadMedia(
 	if (media.type == SendMediaType::Photo) {
 		Auth().data().processPhoto(media.photo, media.photoThumbs);
 	} else if (media.type == SendMediaType::File
+		|| media.type == SendMediaType::WallPaper
 		|| media.type == SendMediaType::Audio) {
 		const auto document = media.photoThumbs.empty()
 			? Auth().data().processDocument(media.document)
@@ -157,6 +162,9 @@ void Uploader::uploadMedia(
 				base::duplicate(media.photoThumbs.front().second));
 		if (!media.data.isEmpty()) {
 			document->setData(media.data);
+			if (media.type == SendMediaType::WallPaper) {
+				document->checkWallPaperProperties();
+			}
 			if (document->saveToCache()
 				&& media.data.size() <= Storage::kMaxFileInMemory) {
 				Auth().data().cache().put(
@@ -184,6 +192,7 @@ void Uploader::upload(
 		photo->uploadingData = std::make_unique<Data::UploadState>(
 			file->partssize);
 	} else if (file->type == SendMediaType::File
+		|| file->type == SendMediaType::WallPaper
 		|| file->type == SendMediaType::Audio) {
 		const auto document = file->thumb.isNull()
 			? Auth().data().processDocument(file->document)
@@ -197,6 +206,9 @@ void Uploader::upload(
 			std::move(file->goodThumbnailBytes));
 		if (!file->content.isEmpty()) {
 			document->setData(file->content);
+			if (file->type == SendMediaType::WallPaper) {
+				document->checkWallPaperProperties();
+			}
 			if (document->saveToCache()
 				&& file->content.size() <= Storage::kMaxFileInMemory) {
 				Auth().data().cache().put(
@@ -220,6 +232,7 @@ void Uploader::currentFailed() {
 		if (j->second.type() == SendMediaType::Photo) {
 			_photoFailed.fire_copy(j->first);
 		} else if (j->second.type() == SendMediaType::File
+			|| j->second.type() == SendMediaType::WallPaper
 			|| j->second.type() == SendMediaType::Audio) {
 			const auto document = Auth().data().document(j->second.id());
 			if (document->uploading()) {
@@ -318,6 +331,7 @@ void Uploader::sendNext() {
 						MTP_bytes(md5));
 					_photoReady.fire({ uploadingId, silent, file });
 				} else if (uploadingData.type() == SendMediaType::File
+					|| uploadingData.type() == SendMediaType::WallPaper
 					|| uploadingData.type() == SendMediaType::Audio) {
 					QByteArray docMd5(32, Qt::Uninitialized);
 					hashMd5Hex(uploadingData.md5Hash.result(), docMd5.data());
@@ -389,6 +403,7 @@ void Uploader::sendNext() {
 				* uploadingData.docPartSize;
 			toSend = content.mid(offset, uploadingData.docPartSize);
 			if ((uploadingData.type() == SendMediaType::File
+				|| uploadingData.type() == SendMediaType::WallPaper
 				|| uploadingData.type() == SendMediaType::Audio)
 				&& uploadingData.docSentParts <= kUseBigFilesFrom) {
 				uploadingData.md5Hash.feed(toSend.constData(), toSend.size());
@@ -530,6 +545,7 @@ void Uploader::partLoaded(const MTPBool &result, mtpRequestId requestId) {
 				}
 				_photoProgress.fire_copy(fullId);
 			} else if (file.type() == SendMediaType::File
+				|| file.type() == SendMediaType::WallPaper
 				|| file.type() == SendMediaType::Audio) {
 				const auto document = Auth().data().document(file.id());
 				if (document->uploading()) {
diff --git a/Telegram/SourceFiles/storage/localimageloader.cpp b/Telegram/SourceFiles/storage/localimageloader.cpp
index a07e36d55..afb362fc8 100644
--- a/Telegram/SourceFiles/storage/localimageloader.cpp
+++ b/Telegram/SourceFiles/storage/localimageloader.cpp
@@ -234,6 +234,71 @@ SendMediaReady PreparePeerPhoto(PeerId peerId, QImage &&image) {
 		0);
 }
 
+SendMediaReady PrepareWallPaper(const QImage &image) {
+	PreparedPhotoThumbs thumbnails;
+	QVector<MTPPhotoSize> sizes;
+
+	QByteArray jpeg;
+	QBuffer jpegBuffer(&jpeg);
+	image.save(&jpegBuffer, "JPG", 87);
+
+	const auto scaled = [&](int size) {
+		return image.scaled(
+			size,
+			size,
+			Qt::KeepAspectRatio,
+			Qt::SmoothTransformation);
+	};
+	const auto push = [&](const char *type, QImage &&image) {
+		sizes.push_back(MTP_photoSize(
+			MTP_string(type),
+			MTP_fileLocationUnavailable(
+				MTP_long(0),
+				MTP_int(0),
+				MTP_long(0)),
+			MTP_int(image.width()),
+			MTP_int(image.height()), MTP_int(0)));
+		thumbnails.emplace(type[0], std::move(image));
+	};
+	push("s", scaled(320));
+
+	const auto filename = qsl("wallpaper.jpg");
+	auto attributes = QVector<MTPDocumentAttribute>(
+		1,
+		MTP_documentAttributeFilename(MTP_string(filename)));
+	attributes.push_back(MTP_documentAttributeImageSize(
+		MTP_int(image.width()),
+		MTP_int(image.height())));
+	const auto id = rand_value<DocumentId>();
+	const auto document = MTP_document(
+		MTP_flags(0),
+		MTP_long(id),
+		MTP_long(0),
+		MTP_bytes(QByteArray()),
+		MTP_int(unixtime()),
+		MTP_string("image/jpeg"),
+		MTP_int(jpeg.size()),
+		MTP_vector<MTPPhotoSize>(sizes),
+		MTP_int(MTP::maindc()),
+		MTP_vector<MTPDocumentAttribute>(attributes));
+
+	return SendMediaReady(
+		SendMediaType::WallPaper,
+		QString(), // filepath
+		filename,
+		jpeg.size(),
+		jpeg,
+		id,
+		0,
+		QString(),
+		PeerId(),
+		MTP_photoEmpty(MTP_long(0)),
+		thumbnails,
+		document,
+		QByteArray(),
+		0);
+}
+
 TaskQueue::TaskQueue(TimeMs stopTimeoutMs) {
 	if (stopTimeoutMs > 0) {
 		_stopTimer = new QTimer(this);
diff --git a/Telegram/SourceFiles/storage/localimageloader.h b/Telegram/SourceFiles/storage/localimageloader.h
index 6faff9fde..6e02a7ca3 100644
--- a/Telegram/SourceFiles/storage/localimageloader.h
+++ b/Telegram/SourceFiles/storage/localimageloader.h
@@ -20,6 +20,7 @@ enum class SendMediaType {
 	Photo,
 	Audio,
 	File,
+	WallPaper,
 	Secure,
 };
 
@@ -83,6 +84,7 @@ struct SendMediaReady {
 };
 
 SendMediaReady PreparePeerPhoto(PeerId peerId, QImage &&image);
+SendMediaReady PrepareWallPaper(const QImage &image);
 
 using TaskId = void*; // no interface, just id
 
diff --git a/Telegram/SourceFiles/window/themes/window_theme.cpp b/Telegram/SourceFiles/window/themes/window_theme.cpp
index 01f8d7680..57d9184a9 100644
--- a/Telegram/SourceFiles/window/themes/window_theme.cpp
+++ b/Telegram/SourceFiles/window/themes/window_theme.cpp
@@ -10,592 +10,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "window/themes/window_theme_preview.h"
 #include "mainwidget.h"
 #include "auth_session.h"
-#include "core/application.h"
-#include "storage/serialize_common.h"
-#include "data/data_document.h"
-#include "data/data_session.h"
+#include "apiwrap.h"
 #include "storage/localstorage.h"
+#include "storage/localimageloader.h"
+#include "storage/file_upload.h"
 #include "base/parse_helper.h"
 #include "base/zlib_help.h"
+#include "data/data_session.h"
 #include "ui/image/image.h"
 #include "boxes/background_box.h"
+#include "core/application.h"
 #include "styles/style_widgets.h"
 #include "styles/style_history.h"
 
-namespace Data {
-namespace {
-
-constexpr auto FromLegacyBackgroundId(int32 legacyId) -> WallPaperId {
-	return uint64(0xFFFFFFFF00000000ULL) | uint64(uint32(legacyId));
-}
-
-constexpr auto kUninitializedBackground = FromLegacyBackgroundId(-999);
-constexpr auto kTestingThemeBackground = FromLegacyBackgroundId(-666);
-constexpr auto kTestingDefaultBackground = FromLegacyBackgroundId(-665);
-constexpr auto kTestingEditorBackground = FromLegacyBackgroundId(-664);
-constexpr auto kThemeBackground = FromLegacyBackgroundId(-2);
-constexpr auto kCustomBackground = FromLegacyBackgroundId(-1);
-constexpr auto kLegacy1DefaultBackground = FromLegacyBackgroundId(0);
-constexpr auto kDefaultBackground = 5947530738516623361;
-constexpr auto kIncorrectDefaultBackground = FromLegacyBackgroundId(105);
-
-quint32 SerializeMaybeColor(std::optional<QColor> color) {
-	return color
-		? ((quint32(std::clamp(color->red(), 0, 255)) << 16)
-			| (quint32(std::clamp(color->green(), 0, 255)) << 8)
-			| quint32(std::clamp(color->blue(), 0, 255)))
-		: quint32(-1);
-}
-
-std::optional<QColor> MaybeColorFromSerialized(quint32 serialized) {
-	return (serialized == quint32(-1))
-		? std::nullopt
-		: std::make_optional(QColor(
-			int((serialized >> 16) & 0xFFU),
-			int((serialized >> 8) & 0xFFU),
-			int(serialized & 0xFFU)));
-}
-
-std::optional<QColor> ColorFromString(const QString &string) {
-	if (string.size() != 6) {
-		return {};
-	} else if (ranges::find_if(string, [](QChar ch) {
-		return (ch < 'a' || ch > 'f')
-			&& (ch < 'A' || ch > 'F')
-			&& (ch < '0' || ch > '9');
-	}) != string.end()) {
-		return {};
-	}
-	const auto component = [](const QString &text, int index) {
-		const auto decimal = [](QChar hex) {
-			const auto code = hex.unicode();
-			return (code >= '0' && code <= '9')
-				? int(code - '0')
-				: (code >= 'a' && code <= 'f')
-				? int(code - 'a' + 0x0a)
-				: int(code - 'A' + 0x0a);
-		};
-		index *= 2;
-		return decimal(text[index]) * 0x10 + decimal(text[index + 1]);
-	};
-	return QColor(
-		component(string, 0),
-		component(string, 1),
-		component(string, 2),
-		255);
-}
-
-QString StringFromColor(QColor color) {
-	const auto component = [](int value) {
-		const auto hex = [](int value) {
-			value = std::clamp(value, 0, 15);
-			return (value > 9)
-				? ('a' + (value - 10))
-				: ('0' + value);
-		};
-		return QString() + hex(value / 16) + hex(value % 16);
-	};
-	return component(color.red())
-		+ component(color.green())
-		+ component(color.blue());
-}
-
-} // namespace
-
-WallPaper::WallPaper(WallPaperId id) : _id(id) {
-}
-
-void WallPaper::setLocalImageAsThumbnail(std::shared_ptr<Image> image) {
-	Expects(IsDefaultWallPaper(*this)
-		|| IsLegacy1DefaultWallPaper(*this)
-		|| IsCustomWallPaper(*this));
-	Expects(_thumbnail == nullptr);
-
-	_thumbnail = std::move(image);
-}
-
-WallPaperId WallPaper::id() const {
-	return _id;
-}
-
-std::optional<QColor> WallPaper::backgroundColor() const {
-	return _backgroundColor;
-}
-
-DocumentData *WallPaper::document() const {
-	return _document;
-}
-
-Image *WallPaper::thumbnail() const {
-	return _thumbnail
-		? _thumbnail.get()
-		: _document
-		? _document->thumbnail()
-		: nullptr;
-}
-
-bool WallPaper::isPattern() const {
-	return _flags & MTPDwallPaper::Flag::f_pattern;
-}
-
-bool WallPaper::isDefault() const {
-	return _flags & MTPDwallPaper::Flag::f_default;
-}
-
-bool WallPaper::isCreator() const {
-	return _flags & MTPDwallPaper::Flag::f_creator;
-}
-
-bool WallPaper::isDark() const {
-	return _flags & MTPDwallPaper::Flag::f_dark;
-}
-
-bool WallPaper::isLocal() const {
-	return !document() && thumbnail();
-}
-
-bool WallPaper::isBlurred() const {
-	return _settings & MTPDwallPaperSettings::Flag::f_blur;
-}
-
-int WallPaper::patternIntensity() const {
-	return _intensity;
-}
-
-bool WallPaper::hasShareUrl() const {
-	return !_slug.isEmpty();
-}
-
-QString WallPaper::shareUrl() const {
-	if (!hasShareUrl()) {
-		return QString();
-	}
-	const auto base = Core::App().createInternalLinkFull("bg/" + _slug);
-	auto params = QStringList();
-	if (isPattern()) {
-		if (_backgroundColor) {
-			params.push_back("bg_color=" + StringFromColor(*_backgroundColor));
-		}
-		if (_intensity) {
-			params.push_back("intensity=" + QString::number(_intensity));
-		}
-	}
-	auto mode = QStringList();
-	if (_settings & MTPDwallPaperSettings::Flag::f_blur) {
-		mode.push_back("blur");
-	}
-	if (_settings & MTPDwallPaperSettings::Flag::f_motion) {
-		mode.push_back("motion");
-	}
-	if (!mode.isEmpty()) {
-		params.push_back("mode=" + mode.join('+'));
-	}
-	return params.isEmpty()
-		? base
-		: base + '?' + params.join('&');
-}
-
-void WallPaper::loadThumbnail() const {
-	if (_thumbnail) {
-		_thumbnail->load(fileOrigin());
-	}
-	if (_document) {
-		_document->loadThumbnail(fileOrigin());
-	}
-}
-
-void WallPaper::loadDocument() const {
-	if (_document) {
-		_document->save(fileOrigin(), QString());
-	}
-}
-
-FileOrigin WallPaper::fileOrigin() const {
-	return FileOriginWallpaper(_id, _accessHash);
-}
-
-WallPaper WallPaper::withUrlParams(
-		const QMap<QString, QString> &params) const {
-	using Flag = MTPDwallPaperSettings::Flag;
-
-	auto result = *this;
-	result._settings = Flag(0);
-	result._backgroundColor = ColorFromString(_slug);
-	result._intensity = kDefaultIntensity;
-
-	if (auto mode = params.value("mode"); !mode.isEmpty()) {
-		const auto list = mode.replace('+', ' ').split(' ');
-		for (const auto &change : list) {
-			if (change == qstr("blur")) {
-				result._settings |= Flag::f_blur;
-			} else if (change == qstr("motion")) {
-				result._settings |= Flag::f_motion;
-			}
-		}
-	}
-	if (const auto color = ColorFromString(params.value("bg_color"))) {
-		result._settings |= Flag::f_background_color;
-		result._backgroundColor = color;
-	}
-	if (const auto string = params.value("intensity"); !string.isEmpty()) {
-		auto ok = false;
-		const auto intensity = string.toInt(&ok);
-		if (ok && base::in_range(intensity, 0, 101)) {
-			result._settings |= Flag::f_intensity;
-			result._intensity = intensity;
-		}
-	}
-
-	return result;
-}
-
-WallPaper WallPaper::withBlurred(bool blurred) const {
-	using Flag = MTPDwallPaperSettings::Flag;
-
-	auto result = *this;
-	if (blurred) {
-		result._settings |= Flag::f_blur;
-	} else {
-		result._settings &= ~Flag::f_blur;
-	}
-	return result;
-}
-
-WallPaper WallPaper::withPatternIntensity(int intensity) const {
-	using Flag = MTPDwallPaperSettings::Flag;
-
-	auto result = *this;
-	result._settings |= Flag::f_intensity;
-	result._intensity = intensity;
-	return result;
-}
-
-WallPaper WallPaper::withBackgroundColor(QColor color) const {
-	using Flag = MTPDwallPaperSettings::Flag;
-
-	auto result = *this;
-	result._settings |= Flag::f_background_color;
-	result._backgroundColor = color;
-	if (ColorFromString(_slug)) {
-		result._slug = StringFromColor(color);
-	}
-	return result;
-}
-
-WallPaper WallPaper::withParamsFrom(const WallPaper &other) const {
-	auto result = *this;
-	result._settings = other._settings;
-	if (other._backgroundColor || !ColorFromString(_slug)) {
-		result._backgroundColor = other._backgroundColor;
-		if (ColorFromString(_slug)) {
-			result._slug = StringFromColor(*result._backgroundColor);
-		}
-	}
-	result._intensity = other._intensity;
-	return result;
-}
-
-WallPaper WallPaper::withoutImageData() const {
-	auto result = *this;
-	result._thumbnail = nullptr;
-	return result;
-}
-
-std::optional<WallPaper> WallPaper::Create(const MTPWallPaper &data) {
-	return data.match([](const MTPDwallPaper &data) {
-		return Create(data);
-	});
-}
-
-std::optional<WallPaper> WallPaper::Create(const MTPDwallPaper &data) {
-	using Flag = MTPDwallPaper::Flag;
-
-	const auto document = Auth().data().processDocument(
-		data.vdocument);
-	if (!document->checkWallPaperProperties()) {
-		return std::nullopt;
-	}
-	auto result = WallPaper(data.vid.v);
-	result._accessHash = data.vaccess_hash.v;
-	result._flags = data.vflags.v;
-	result._slug = qs(data.vslug);
-	result._document = document;
-	if (data.has_settings()) {
-		const auto isPattern = ((result._flags & Flag::f_pattern) != 0);
-		data.vsettings.match([&](const MTPDwallPaperSettings &data) {
-			using Flag = MTPDwallPaperSettings::Flag;
-
-			result._settings = data.vflags.v;
-			if (isPattern && data.has_background_color()) {
-				result._backgroundColor = MaybeColorFromSerialized(
-					data.vbackground_color.v);
-			} else {
-				result._settings &= ~Flag::f_background_color;
-			}
-			if (isPattern && data.has_intensity()) {
-				result._intensity = data.vintensity.v;
-			} else {
-				result._settings &= ~Flag::f_intensity;
-			}
-		});
-	}
-	return result;
-}
-
-QByteArray WallPaper::serialize() const {
-	auto size = sizeof(quint64) // _id
-		+ sizeof(quint64) // _accessHash
-		+ sizeof(qint32) // _flags
-		+ Serialize::stringSize(_slug)
-		+ sizeof(qint32) // _settings
-		+ sizeof(quint32) // _backgroundColor
-		+ sizeof(qint32); // _intensity
-
-	auto result = QByteArray();
-	result.reserve(size);
-	{
-		auto stream = QDataStream(&result, QIODevice::WriteOnly);
-		stream.setVersion(QDataStream::Qt_5_1);
-		stream
-			<< quint64(_id)
-			<< quint64(_accessHash)
-			<< qint32(_flags)
-			<< _slug
-			<< qint32(_settings)
-			<< SerializeMaybeColor(_backgroundColor)
-			<< qint32(_intensity);
-	}
-	return result;
-}
-
-std::optional<WallPaper> WallPaper::FromSerialized(
-		const QByteArray &serialized) {
-	if (serialized.isEmpty()) {
-		return std::nullopt;
-	}
-
-	auto id = quint64();
-	auto accessHash = quint64();
-	auto flags = qint32();
-	auto slug = QString();
-	auto settings = qint32();
-	auto backgroundColor = quint32();
-	auto intensity = qint32();
-
-	auto stream = QDataStream(serialized);
-	stream.setVersion(QDataStream::Qt_5_1);
-	stream
-		>> id
-		>> accessHash
-		>> flags
-		>> slug
-		>> settings
-		>> backgroundColor
-		>> intensity;
-	if (stream.status() != QDataStream::Ok) {
-		return std::nullopt;
-	} else if (intensity < 0 || intensity > 100) {
-		return std::nullopt;
-	}
-	auto result = WallPaper(id);
-	result._accessHash = accessHash;
-	result._flags = MTPDwallPaper::Flags::from_raw(flags);
-	result._slug = slug;
-	result._settings = MTPDwallPaperSettings::Flags::from_raw(settings);
-	result._backgroundColor = MaybeColorFromSerialized(backgroundColor);
-	result._intensity = intensity;
-	return result;
-}
-
-std::optional<WallPaper> WallPaper::FromLegacySerialized(
-		quint64 id,
-		quint64 accessHash,
-		quint32 flags,
-		QString slug) {
-	auto result = WallPaper(id);
-	result._accessHash = accessHash;
-	result._flags = MTPDwallPaper::Flags::from_raw(flags);
-	result._slug = slug;
-	result._backgroundColor = ColorFromString(slug);
-	return result;
-}
-
-std::optional<WallPaper> WallPaper::FromLegacyId(qint32 legacyId) {
-	auto result = WallPaper(FromLegacyBackgroundId(legacyId));
-	if (!IsCustomWallPaper(result)) {
-		result._flags = MTPDwallPaper::Flag::f_default;
-	}
-	return result;
-}
-
-std::optional<WallPaper> WallPaper::FromColorSlug(const QString &slug) {
-	if (const auto color = ColorFromString(slug)) {
-		auto result = CustomWallPaper();
-		result._slug = slug;
-		result._backgroundColor = color;
-		return result;
-	}
-	return std::nullopt;
-}
-
-WallPaper ThemeWallPaper() {
-	return WallPaper(kThemeBackground);
-}
-
-bool IsThemeWallPaper(const WallPaper &paper) {
-	return (paper.id() == kThemeBackground);
-}
-
-WallPaper CustomWallPaper() {
-	return WallPaper(kCustomBackground);
-}
-
-bool IsCustomWallPaper(const WallPaper &paper) {
-	return (paper.id() == kCustomBackground);
-}
-
-WallPaper Legacy1DefaultWallPaper() {
-	return WallPaper(kLegacy1DefaultBackground);
-}
-
-bool IsLegacy1DefaultWallPaper(const WallPaper &paper) {
-	return (paper.id() == kLegacy1DefaultBackground);
-}
-
-WallPaper DefaultWallPaper() {
-	return WallPaper(kDefaultBackground);
-}
-
-bool IsDefaultWallPaper(const WallPaper &paper) {
-	return (paper.id() == kDefaultBackground)
-		|| (paper.id() == kIncorrectDefaultBackground);
-}
-
-QColor PatternColor(QColor background) {
-	const auto hue = background.hueF();
-	const auto saturation = background.saturationF();
-	const auto value = background.valueF();
-	return QColor::fromHsvF(
-		hue,
-		std::min(1.0, saturation + 0.05 + 0.1 * (1. - saturation)),
-		(value > 0.5
-			? std::max(0., value * 0.65)
-			: std::max(0., std::min(1., 1. - value * 0.65))),
-		0.4
-	).toRgb();
-}
-
-QImage PreparePatternImage(
-		QImage image,
-		QColor bg,
-		QColor fg,
-		int intensity) {
-	if (image.format() != QImage::Format_ARGB32_Premultiplied) {
-		image = std::move(image).convertToFormat(
-			QImage::Format_ARGB32_Premultiplied);
-	}
-	// Similar to ColorizePattern.
-	// But here we set bg to all 'alpha=0' pixels and fg to opaque ones.
-
-	const auto width = image.width();
-	const auto height = image.height();
-	const auto alpha = anim::interpolate(
-		0,
-		255,
-		fg.alphaF() * std::clamp(intensity / 100., 0., 1.));
-	if (!alpha) {
-		image.fill(bg);
-		return image;
-	}
-	fg.setAlpha(255);
-	const auto patternBg = anim::shifted(bg);
-	const auto patternFg = anim::shifted(fg);
-
-	const auto resultBytesPerPixel = (image.depth() >> 3);
-	constexpr auto resultIntsPerPixel = 1;
-	const auto resultIntsPerLine = (image.bytesPerLine() >> 2);
-	const auto resultIntsAdded = resultIntsPerLine - width * resultIntsPerPixel;
-	auto resultInts = reinterpret_cast<uint32*>(image.bits());
-	Assert(resultIntsAdded >= 0);
-	Assert(image.depth() == static_cast<int>((resultIntsPerPixel * sizeof(uint32)) << 3));
-	Assert(image.bytesPerLine() == (resultIntsPerLine << 2));
-
-	const auto maskBytesPerPixel = (image.depth() >> 3);
-	const auto maskBytesPerLine = image.bytesPerLine();
-	const auto maskBytesAdded = maskBytesPerLine - width * maskBytesPerPixel;
-
-	// We want to read the last byte of four available.
-	// This is the difference with style::colorizeImage.
-	auto maskBytes = image.constBits() + (maskBytesPerPixel - 1);
-	Assert(maskBytesAdded >= 0);
-	Assert(image.depth() == (maskBytesPerPixel << 3));
-	for (auto y = 0; y != height; ++y) {
-		for (auto x = 0; x != width; ++x) {
-			const auto maskOpacity = static_cast<anim::ShiftedMultiplier>(
-				*maskBytes) + 1;
-			const auto fgOpacity = (maskOpacity * alpha) >> 8;
-			const auto bgOpacity = 256 - fgOpacity;
-			*resultInts = anim::unshifted(
-				patternBg * bgOpacity + patternFg * fgOpacity);
-			maskBytes += maskBytesPerPixel;
-			resultInts += resultIntsPerPixel;
-		}
-		maskBytes += maskBytesAdded;
-		resultInts += resultIntsAdded;
-	}
-	return image;
-}
-
-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);
-}
-
-namespace details {
-
-WallPaper UninitializedWallPaper() {
-	return WallPaper(kUninitializedBackground);
-}
-
-bool IsUninitializedWallPaper(const WallPaper &paper) {
-	return (paper.id() == kUninitializedBackground);
-}
-
-WallPaper TestingThemeWallPaper() {
-	return WallPaper(kTestingThemeBackground);
-}
-
-bool IsTestingThemeWallPaper(const WallPaper &paper) {
-	return (paper.id() == kTestingThemeBackground);
-}
-
-WallPaper TestingDefaultWallPaper() {
-	return WallPaper(kTestingDefaultBackground);
-}
-
-bool IsTestingDefaultWallPaper(const WallPaper &paper) {
-	return (paper.id() == kTestingDefaultBackground);
-}
-
-WallPaper TestingEditorWallPaper() {
-	return WallPaper(kTestingEditorBackground);
-}
-
-bool IsTestingEditorWallPaper(const WallPaper &paper) {
-	return (paper.id() == kTestingEditorBackground);
-}
-
-} // namespace details
-} // namespace Data
-
 namespace Window {
 namespace Theme {
 namespace {
@@ -954,9 +381,71 @@ void ChatBackground::start() {
 		if (!Local::readBackground()) {
 			set(Data::ThemeWallPaper());
 		}
+		refreshSession();
+		subscribe(Core::App().authSessionChanged(), [=] {
+			refreshSession();
+		});
 	}
 }
 
+void ChatBackground::refreshSession() {
+	const auto session = AuthSession::Exists() ? &Auth() : nullptr;
+	if (_session != session) {
+		_session = session;
+		checkUploadWallPaper();
+	}
+}
+
+void ChatBackground::checkUploadWallPaper() {
+	if (!_session) {
+		_wallPaperUploadLifetime = rpl::lifetime();
+		_wallPaperUploadId = FullMsgId();
+		_wallPaperRequestId = 0;
+		return;
+	}
+	if (const auto id = base::take(_wallPaperUploadId)) {
+		_session->uploader().cancel(id);
+	}
+	if (const auto id = base::take(_wallPaperRequestId)) {
+		_session->api().request(id).cancel();
+	}
+	if (!Data::IsCustomWallPaper(_paper) || _original.isNull()) {
+		return;
+	}
+
+	const auto ready = PrepareWallPaper(_original);
+	const auto documentId = ready.id;
+	_wallPaperUploadId = FullMsgId(0, clientMsgId());
+	_session->uploader().uploadMedia(_wallPaperUploadId, ready);
+	if (_wallPaperUploadLifetime) {
+		return;
+	}
+	_wallPaperUploadLifetime = _session->uploader().documentReady(
+	) | rpl::start_with_next([=](const Storage::UploadedDocument &data) {
+		if (data.fullId != _wallPaperUploadId) {
+			return;
+		}
+		_wallPaperUploadId = FullMsgId();
+		_wallPaperRequestId = _session->api().request(
+			MTPaccount_UploadWallPaper(
+				data.file,
+				MTP_string("image/jpeg"),
+				_paper.mtpSettings()
+			)
+		).done([=](const MTPWallPaper &result) {
+			result.match([&](const MTPDwallPaper &data) {
+				_session->data().documentConvert(
+					_session->data().document(documentId),
+					data.vdocument);
+			});
+			if (const auto paper = Data::WallPaper::Create(result)) {
+				_paper = *paper;
+				writeNewBackgroundSettings();
+			}
+		}).send();
+	});
+}
+
 void ChatBackground::set(const Data::WallPaper &paper, QImage image) {
 	image = ProcessBackgroundImage(std::move(image));
 
@@ -1041,6 +530,7 @@ void ChatBackground::set(const Data::WallPaper &paper, QImage image) {
 		notify(BackgroundUpdate(BackgroundUpdate::Type::TestingTheme, tile()), true);
 		notify(BackgroundUpdate(BackgroundUpdate::Type::ApplyingTheme, tile()), true);
 	}
+	checkUploadWallPaper();
 }
 
 void ChatBackground::setPreparedImage(QImage original, QImage prepared) {
diff --git a/Telegram/SourceFiles/window/themes/window_theme.h b/Telegram/SourceFiles/window/themes/window_theme.h
index 9fb49b7ce..fe41be72e 100644
--- a/Telegram/SourceFiles/window/themes/window_theme.h
+++ b/Telegram/SourceFiles/window/themes/window_theme.h
@@ -7,109 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #pragma once
 
-class Image;
+#include "data/data_wall_paper.h"
 
-namespace Data {
-
-struct FileOrigin;
-
-class WallPaper {
-public:
-	explicit WallPaper(WallPaperId id);
-
-	void setLocalImageAsThumbnail(std::shared_ptr<Image> image);
-
-	[[nodiscard]] WallPaperId id() const;
-	[[nodiscard]] std::optional<QColor> backgroundColor() const;
-	[[nodiscard]] DocumentData *document() const;
-	[[nodiscard]] Image *thumbnail() const;
-	[[nodiscard]] bool isPattern() const;
-	[[nodiscard]] bool isDefault() const;
-	[[nodiscard]] bool isCreator() const;
-	[[nodiscard]] bool isDark() const;
-	[[nodiscard]] bool isLocal() const;
-	[[nodiscard]] bool isBlurred() const;
-	[[nodiscard]] int patternIntensity() const;
-	[[nodiscard]] bool hasShareUrl() const;
-	[[nodiscard]] QString shareUrl() const;
-
-	void loadDocument() const;
-	void loadThumbnail() const;
-	[[nodiscard]] FileOrigin fileOrigin() const;
-
-	[[nodiscard]] WallPaper withUrlParams(
-		const QMap<QString, QString> &params) const;
-	[[nodiscard]] WallPaper withBlurred(bool blurred) const;
-	[[nodiscard]] WallPaper withPatternIntensity(int intensity) const;
-	[[nodiscard]] WallPaper withBackgroundColor(QColor color) const;
-	[[nodiscard]] WallPaper withParamsFrom(const WallPaper &other) const;
-	[[nodiscard]] WallPaper withoutImageData() const;
-
-	[[nodiscard]] static std::optional<WallPaper> Create(
-		const MTPWallPaper &data);
-	[[nodiscard]] static std::optional<WallPaper> Create(
-		const MTPDwallPaper &data);
-
-	[[nodiscard]] QByteArray serialize() const;
-	[[nodiscard]] static std::optional<WallPaper> FromSerialized(
-		const QByteArray &serialized);
-	[[nodiscard]] static std::optional<WallPaper> FromLegacySerialized(
-		quint64 id,
-		quint64 accessHash,
-		quint32 flags,
-		QString slug);
-	[[nodiscard]] static std::optional<WallPaper> FromLegacyId(
-		qint32 legacyId);
-	[[nodiscard]] static std::optional<WallPaper> FromColorSlug(
-		const QString &slug);
-
-private:
-	static constexpr auto kDefaultIntensity = 40;
-
-	WallPaperId _id = WallPaperId();
-	uint64 _accessHash = 0;
-	MTPDwallPaper::Flags _flags;
-	QString _slug;
-
-	MTPDwallPaperSettings::Flags _settings;
-	std::optional<QColor> _backgroundColor;
-	int _intensity = kDefaultIntensity;
-
-	DocumentData *_document = nullptr;
-	std::shared_ptr<Image> _thumbnail;
-
-};
-
-[[nodiscard]] WallPaper ThemeWallPaper();
-[[nodiscard]] bool IsThemeWallPaper(const WallPaper &paper);
-[[nodiscard]] WallPaper CustomWallPaper();
-[[nodiscard]] bool IsCustomWallPaper(const WallPaper &paper);
-[[nodiscard]] WallPaper Legacy1DefaultWallPaper();
-[[nodiscard]] bool IsLegacy1DefaultWallPaper(const WallPaper &paper);
-[[nodiscard]] WallPaper DefaultWallPaper();
-[[nodiscard]] bool IsDefaultWallPaper(const WallPaper &paper);
-
-QColor PatternColor(QColor background);
-QImage PreparePatternImage(
-	QImage image,
-	QColor bg,
-	QColor fg,
-	int intensity);
-QImage PrepareBlurredBackground(QImage image);
-
-namespace details {
-
-[[nodiscard]] WallPaper UninitializedWallPaper();
-[[nodiscard]] bool IsUninitializedWallPaper(const WallPaper &paper);
-[[nodiscard]] WallPaper TestingThemeWallPaper();
-[[nodiscard]] bool IsTestingThemeWallPaper(const WallPaper &paper);
-[[nodiscard]] WallPaper TestingDefaultWallPaper();
-[[nodiscard]] bool IsTestingDefaultWallPaper(const WallPaper &paper);
-[[nodiscard]] WallPaper TestingEditorWallPaper();
-[[nodiscard]] bool IsTestingEditorWallPaper(const WallPaper &paper);
-
-} // namespace details
-} // namespace Data
+class AuthSession;
 
 namespace Window {
 namespace Theme {
@@ -185,7 +85,9 @@ struct BackgroundUpdate {
 	bool tiled;
 };
 
-class ChatBackground : public base::Observable<BackgroundUpdate> {
+class ChatBackground
+	: public base::Observable<BackgroundUpdate>
+	, private base::Subscriber {
 public:
 	ChatBackground();
 
@@ -251,6 +153,8 @@ private:
 	void keepApplied(const QString &path, bool write);
 	[[nodiscard]] bool isNonDefaultThemeOrBackground();
 	[[nodiscard]] bool isNonDefaultBackground();
+	void refreshSession();
+	void checkUploadWallPaper();
 
 	friend bool IsNightMode();
 	friend void SetNightModeValue(bool nightMode);
@@ -259,6 +163,7 @@ private:
 	friend void KeepApplied();
 	friend bool IsNonDefaultBackground();
 
+	AuthSession *_session = nullptr;
 	Data::WallPaper _paper = Data::details::UninitializedWallPaper();
 	std::optional<QColor> _paperColor;
 	QImage _original;
@@ -278,6 +183,9 @@ private:
 	bool _tileForRevert = false;
 
 	std::vector<AdjustableColor> _adjustableColors;
+	FullMsgId _wallPaperUploadId;
+	mtpRequestId _wallPaperRequestId = 0;
+	rpl::lifetime _wallPaperUploadLifetime;
 
 };
 
diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt
index 7a95b0947..09e216394 100644
--- a/Telegram/gyp/telegram_sources.txt
+++ b/Telegram/gyp/telegram_sources.txt
@@ -207,6 +207,8 @@
 <(src_loc)/data/data_user.h
 <(src_loc)/data/data_user_photos.cpp
 <(src_loc)/data/data_user_photos.h
+<(src_loc)/data/data_wall_paper.cpp
+<(src_loc)/data/data_wall_paper.h
 <(src_loc)/data/data_web_page.cpp
 <(src_loc)/data/data_web_page.h
 <(src_loc)/dialogs/dialogs_entry.cpp