620 lines
16 KiB
C++
620 lines
16 KiB
C++
/*
|
|
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 "main/main_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::localThumbnail() const {
|
|
return _thumbnail.get();
|
|
}
|
|
|
|
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::loadLocalThumbnail() const {
|
|
if (_thumbnail) {
|
|
_thumbnail->load(fileOrigin());
|
|
}
|
|
}
|
|
|
|
void WallPaper::loadDocumentThumbnail() const {
|
|
if (_document) {
|
|
_document->loadThumbnail(fileOrigin());
|
|
}
|
|
}
|
|
|
|
void WallPaper::loadDocument() const {
|
|
if (_document) {
|
|
_document->save(fileOrigin(), QString());
|
|
}
|
|
}
|
|
|
|
FileOrigin WallPaper::fileOrigin() const {
|
|
return FileOriginWallpaper(_id, _accessHash);
|
|
}
|
|
|
|
MTPInputWallPaper WallPaper::mtpInput() const {
|
|
return MTP_inputWallPaper(MTP_long(_id), MTP_long(_accessHash));
|
|
}
|
|
|
|
MTPWallPaperSettings WallPaper::mtpSettings() const {
|
|
return MTP_wallPaperSettings(
|
|
MTP_flags(_settings),
|
|
(_backgroundColor
|
|
? MTP_int(SerializeMaybeColor(_backgroundColor))
|
|
: MTP_int(0)),
|
|
MTP_int(0), // second_background_color
|
|
MTP_int(_intensity),
|
|
MTP_int(0) // rotation
|
|
);
|
|
}
|
|
|
|
WallPaper WallPaper::withUrlParams(
|
|
const QMap<QString, QString> ¶ms) 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);
|
|
}, [](const MTPDwallPaperNoFile &data) {
|
|
return std::optional<WallPaper>(); // #TODO themes
|
|
});
|
|
}
|
|
|
|
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 (const auto settings = data.vsettings()) {
|
|
const auto isPattern = ((result._flags & Flag::f_pattern) != 0);
|
|
settings->match([&](const MTPDwallPaperSettings &data) {
|
|
using Flag = MTPDwallPaperSettings::Flag;
|
|
|
|
result._settings = data.vflags().v;
|
|
const auto backgroundColor = data.vbackground_color();
|
|
if (isPattern && backgroundColor) {
|
|
result._backgroundColor = MaybeColorFromSerialized(
|
|
backgroundColor->v);
|
|
} else {
|
|
result._settings &= ~Flag::f_background_color;
|
|
}
|
|
const auto intensity = data.vintensity();
|
|
if (isPattern && intensity) {
|
|
result._intensity = intensity->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);
|
|
}
|
|
|
|
bool IsCloudWallPaper(const WallPaper &paper) {
|
|
return (paper.id() != kIncorrectDefaultBackground)
|
|
&& !IsThemeWallPaper(paper)
|
|
&& !IsCustomWallPaper(paper)
|
|
&& !IsLegacy1DefaultWallPaper(paper)
|
|
&& !details::IsUninitializedWallPaper(paper)
|
|
&& !details::IsTestingThemeWallPaper(paper)
|
|
&& !details::IsTestingDefaultWallPaper(paper)
|
|
&& !details::IsTestingEditorWallPaper(paper);
|
|
}
|
|
|
|
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
|