Start support of linear gradient wallpapers.

This commit is contained in:
John Preston 2021-08-12 17:51:44 +03:00
parent 1fd28d5cfb
commit c2b1187948
14 changed files with 379 additions and 227 deletions

View file

@ -336,13 +336,11 @@ void BackgroundBox::Inner::validatePaperThumbnail(
: paper.dataMedia->thumbnail();
auto original = thumbnail->original();
if (paper.data.isPattern()) {
// #TODO themes gradients
const auto color = *paper.data.backgroundColor();
original = Data::PreparePatternImage(
std::move(original),
color,
Data::PatternColor(color),
paper.data.patternIntensity());
paper.data.backgroundColors(),
paper.data.gradientRotation(),
paper.data.patternOpacity());
}
paper.thumbnail = Ui::PixmapFromImage(TakeMiddleSample(
original,

View file

@ -379,13 +379,17 @@ QImage ColorizePattern(QImage image, QColor color) {
QImage PrepareScaledFromFull(
const QImage &image,
std::optional<QColor> patternBackground,
const std::vector<QColor> &patternBackground,
int gradientRotation,
float64 patternOpacity,
Images::Option blur = Images::Option(0)) {
auto result = PrepareScaledNonPattern(image, blur);
if (patternBackground) {
result = ColorizePattern(
if (!patternBackground.empty()) {
result = Data::PreparePatternImage(
std::move(result),
Data::PatternColor(*patternBackground));
patternBackground,
gradientRotation,
patternOpacity);
}
return std::move(result).convertToFormat(
QImage::Format_ARGB32_Premultiplied);
@ -433,7 +437,7 @@ void BackgroundPreviewBox::prepare() {
if (_paper.hasShareUrl()) {
addLeftButton(tr::lng_background_share(), [=] { share(); });
}
updateServiceBg(_paper.backgroundColor());
updateServiceBg(_paper.backgroundColors());
_paper.loadDocument();
const auto document = _paper.document();
@ -669,7 +673,9 @@ bool BackgroundPreviewBox::setScaledFromThumb() {
}
auto scaled = PrepareScaledFromFull(
thumbnail->original(),
patternBackgroundColor(),
patternBackgroundColors(),
_paper.gradientRotation(),
_paper.patternOpacity(),
_paper.document() ? Images::Option::Blurred : Images::Option(0));
auto blurred = (_paper.document() || _paper.isPattern())
? QImage()
@ -683,7 +689,7 @@ bool BackgroundPreviewBox::setScaledFromThumb() {
void BackgroundPreviewBox::setScaledFromImage(
QImage &&image,
QImage &&blurred) {
updateServiceBg(Window::Theme::CountAverageColor(image));
updateServiceBg({ Window::Theme::CountAverageColor(image) });
if (!_full.isNull()) {
startFadeInFrom(std::move(_scaled));
}
@ -710,16 +716,26 @@ void BackgroundPreviewBox::checkBlurAnimationStart() {
startFadeInFrom(_paper.isBlurred() ? _scaled : _blurred);
}
void BackgroundPreviewBox::updateServiceBg(std::optional<QColor> background) {
if (background) {
_serviceBg = Window::Theme::AdjustedColor(
st::msgServiceBg->c,
*background);
void BackgroundPreviewBox::updateServiceBg(const std::vector<QColor> &bg) {
const auto count = int(bg.size());
if (!count) {
return;
}
auto red = 0, green = 0, blue = 0;
for (const auto &color : bg) {
red += color.red();
green += color.green();
blue += color.blue();
}
_serviceBg = Window::Theme::AdjustedColor(
st::msgServiceBg->c,
QColor(red / count, green / count, blue / count));
}
std::optional<QColor> BackgroundPreviewBox::patternBackgroundColor() const {
return _paper.isPattern() ? _paper.backgroundColor() : std::nullopt;
std::vector<QColor> BackgroundPreviewBox::patternBackgroundColors() const {
return _paper.isPattern()
? _paper.backgroundColors()
: std::vector<QColor>();
}
void BackgroundPreviewBox::checkLoadedDocument() {
@ -737,15 +753,21 @@ void BackgroundPreviewBox::checkLoadedDocument() {
crl::async([
this,
image = std::move(image),
patternBackground = patternBackgroundColor(),
patternBackground = patternBackgroundColors(),
gradientRotation = _paper.gradientRotation(),
patternOpacity = _paper.patternOpacity(),
guard = _generating.make_guard()
]() mutable {
auto scaled = PrepareScaledFromFull(image, patternBackground);
auto blurred = patternBackground
? QImage()
: PrepareScaledNonPattern(
auto scaled = PrepareScaledFromFull(
image,
patternBackground,
gradientRotation,
patternOpacity);
auto blurred = patternBackground.empty()
? PrepareScaledNonPattern(
Data::PrepareBlurredBackground(image),
Images::Option(0));
Images::Option(0))
: QImage();
crl::on_main(std::move(guard), [
this,
image = std::move(image),

View file

@ -59,8 +59,8 @@ private:
void checkLoadedDocument();
bool setScaledFromThumb();
void setScaledFromImage(QImage &&image, QImage &&blurred);
void updateServiceBg(std::optional<QColor> background);
std::optional<QColor> patternBackgroundColor() const;
void updateServiceBg(const std::vector<QColor> &bg);
std::vector<QColor> patternBackgroundColors() const;
void paintImage(Painter &p);
void paintRadial(Painter &p);
void paintTexts(Painter &p, crl::time ms);

View file

@ -232,17 +232,20 @@ bool ShowWallPaper(
match->captured(1),
qthelp::UrlParamNameTransform::ToLower);
const auto bg = params.value("bg_color");
if (!params.value("gradient").isEmpty()
|| bg.contains('~')
|| bg.contains('-')) {
const auto color = params.value("color");
const auto gradient = params.value("gradient");
if (gradient.contains('~') || bg.contains('~')) {
Ui::show(Box<InformBox>(
tr::lng_background_gradient_unsupported(tr::now)));
return false;
}
const auto color = params.value("color");
return BackgroundPreviewBox::Start(
controller,
(color.isEmpty() ? params.value(qsl("slug")) : color),
(!color.isEmpty()
? color
: !gradient.isEmpty()
? gradient
: params.value(qsl("slug"))),
params);
}

View file

@ -117,7 +117,7 @@ constexpr auto kVersion = 1;
}
[[nodiscard]] std::vector<QColor> ColorsFromString(const QString &string) {
constexpr auto kMaxColors = 1; // #TODO themes gradients replace to 4
constexpr auto kMaxColors = 2; // #TODO themes gradients replace to 4
const auto view = QStringView(string);
const auto count = int(view.size() / 6);
if (!count || count > kMaxColors || view.size() != count * 7 - 1) {
@ -237,6 +237,14 @@ int WallPaper::patternIntensity() const {
return _intensity;
}
float64 WallPaper::patternOpacity() const {
return _intensity / 100.;
}
int WallPaper::gradientRotation() const {
return _rotation;
}
bool WallPaper::hasShareUrl() const {
return !_slug.isEmpty();
}
@ -350,6 +358,8 @@ WallPaper WallPaper::withUrlParams(
result._intensity = intensity;
}
}
result._rotation = params.value("rotation").toInt();
result._rotation = (std::clamp(result._rotation, 0, 315) / 45) * 45;
return result;
}
@ -420,8 +430,7 @@ std::optional<WallPaper> WallPaper::Create(
}
const auto unsupported = data.vsettings()
&& data.vsettings()->match([&](const MTPDwallPaperSettings &data) {
return data.vsecond_background_color()
|| data.vthird_background_color()
return data.vthird_background_color()
|| data.vfourth_background_color(); // #TODO themes gradients
});
if (unsupported) {
@ -450,14 +459,16 @@ std::optional<WallPaper> WallPaper::Create(
}
});
}
if (result.isPattern() && result.backgroundColors().empty()) {
return std::nullopt;
}
return result;
}
std::optional<WallPaper> WallPaper::Create(const MTPDwallPaperNoFile &data) {
const auto unsupported = data.vsettings()
&& data.vsettings()->match([&](const MTPDwallPaperSettings &data) {
return data.vsecond_background_color()
|| data.vthird_background_color()
return data.vthird_background_color()
|| data.vfourth_background_color(); // #TODO themes gradients
});
if (unsupported) {
@ -477,6 +488,9 @@ std::optional<WallPaper> WallPaper::Create(const MTPDwallPaperNoFile &data) {
}
});
}
if (result.backgroundColors().empty()) {
return std::nullopt;
}
return result;
}
@ -554,7 +568,7 @@ std::optional<WallPaper> WallPaper::FromSerialized(
>> blurred
>> backgroundColorsCount;
// #TODO themes gradients replace with 4
if (backgroundColorsCount < 0 || backgroundColorsCount > 1) {
if (backgroundColorsCount < 0 || backgroundColorsCount > 2) {
return std::nullopt;
}
backgroundColors.reserve(backgroundColorsCount);
@ -610,6 +624,9 @@ std::optional<WallPaper> WallPaper::FromSerialized(
result._backgroundColors = std::move(backgroundColors);
result._intensity = intensity;
result._rotation = rotation;
if (result.isPattern() && result.backgroundColors().empty()) {
return std::nullopt;
}
return result;
}
@ -625,6 +642,9 @@ std::optional<WallPaper> WallPaper::FromLegacySerialized(
if (const auto color = ColorFromString(slug)) {
result._backgroundColors.push_back(*color);
}
if (result.isPattern() && result.backgroundColors().empty()) {
return std::nullopt;
}
return result;
}
@ -695,78 +715,69 @@ bool IsCloudWallPaper(const WallPaper &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();
[[nodiscard]] QImage FillDitheredGradient(
QImage image,
const std::vector<QColor> &colors,
int rotation) {
Expects(colors.size() > 1);
image.setDevicePixelRatio(1.);
const auto width = image.width();
const auto height = image.height();
if (!width || !height) {
return image;
}
auto p = QPainter(&image);
const auto [start, finalStop] = [&]() -> std::pair<QPoint, QPoint> {
const auto type = std::clamp(rotation, 0, 315) / 45;
switch (type) {
case 0: return { { 0, 0 }, { 0, height } };
case 1: return { { width, 0 }, { 0, height } };
case 2: return { { width, 0 }, { 0, 0 } };
case 3: return { { width, height }, { 0, 0 } };
case 4: return { { 0, height }, { 0, 0 } };
case 5: return { { 0, height }, { width, 0 } };
case 6: return { { 0, 0 }, { width, 0 } };
case 7: return { { 0, 0 }, { width, height } };
}
Unexpected("Rotation value in GenerateDitheredGradient.");
}();
auto gradient = QLinearGradient(start, finalStop);
gradient.setStops(QGradientStops{
{ 0.0, colors[0] },
{ 1.0, colors[1] }
});
p.fillRect(0, 0, width, height, QBrush(std::move(gradient)));
p.end();
return image;
}
QImage PreparePatternImage(
QImage image,
QColor bg,
QColor fg,
int intensity) {
const std::vector<QColor> &bg,
int rotation,
float64 opacity) {
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;
auto result = QImage(image.size(), QImage::Format_ARGB32_Premultiplied);
if (bg.size() < 2) {
result.fill(bg.empty() ? QColor(213, 223, 233) : bg.front());
} else {
result = FillDitheredGradient(std::move(result), bg, rotation);
}
fg.setAlpha(255);
const auto patternBg = anim::shifted(bg);
const auto patternFg = anim::shifted(fg);
auto p = QPainter(&result);
p.setCompositionMode(QPainter::CompositionMode_SoftLight);
p.setOpacity(opacity);
p.drawImage(QRect(QPoint(), image.size()), image);
p.end();
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;
image = QImage();
return result;
}
QImage PrepareBlurredBackground(QImage image) {
@ -782,6 +793,22 @@ QImage PrepareBlurredBackground(QImage image) {
return Images::BlurLargeImage(image, kRadius);
}
QImage GenerateDitheredGradient(
const std::vector<QColor> &colors,
int rotation) {
constexpr auto kSize = 512;
return FillDitheredGradient(
QImage(kSize, kSize, QImage::Format_ARGB32_Premultiplied),
colors,
rotation);
}
QImage GenerateDitheredGradient(const Data::WallPaper &paper) {
return GenerateDitheredGradient(
paper.backgroundColors(),
paper.gradientRotation());
}
namespace details {
WallPaper UninitializedWallPaper() {

View file

@ -46,6 +46,8 @@ public:
[[nodiscard]] bool isLocal() const;
[[nodiscard]] bool isBlurred() const;
[[nodiscard]] int patternIntensity() const;
[[nodiscard]] float64 patternOpacity() const;
[[nodiscard]] int gradientRotation() const;
[[nodiscard]] bool hasShareUrl() const;
[[nodiscard]] QString shareUrl(not_null<Main::Session*> session) const;
@ -120,13 +122,16 @@ private:
[[nodiscard]] bool IsDefaultWallPaper(const WallPaper &paper);
[[nodiscard]] bool IsCloudWallPaper(const WallPaper &paper);
QColor PatternColor(QColor background);
QImage PreparePatternImage(
[[nodiscard]] QImage PreparePatternImage(
QImage image,
QColor bg,
QColor fg,
int intensity);
QImage PrepareBlurredBackground(QImage image);
const std::vector<QColor> &bg,
int rotation,
float64 opacity);
[[nodiscard]] QImage PrepareBlurredBackground(QImage image);
[[nodiscard]] QImage GenerateDitheredGradient(
const std::vector<QColor> &colors,
int rotation);
[[nodiscard]] QImage GenerateDitheredGradient(const WallPaper &paper);
namespace details {

View file

@ -59,10 +59,10 @@ void ThemeDocument::fillPatternFieldsFrom(const QString &url) {
const auto params = qthelp::url_parse_params(
paramsString,
qthelp::UrlParamNameTransform::ToLower);
const auto kDefaultBackground = QColor(213, 223, 233);
const auto paper = Data::DefaultWallPaper().withUrlParams(params);
_intensity = paper.patternIntensity();
_background = paper.backgroundColor().value_or(kDefaultBackground);
_background = paper.backgroundColors();
_patternOpacity = paper.patternOpacity();
_gradientRotation = paper.gradientRotation();
}
QSize ThemeDocument::countOptimalSize() {
@ -262,8 +262,8 @@ void ThemeDocument::prepareThumbnailFrom(
original = Data::PreparePatternImage(
std::move(original),
_background,
Data::PatternColor(_background),
_intensity);
_gradientRotation,
_patternOpacity);
}
_thumbnail = Ui::PixmapFromImage(std::move(original));
_thumbnailGood = good;

View file

@ -74,8 +74,9 @@ private:
mutable std::shared_ptr<Data::DocumentMedia> _dataMedia;
// For wallpaper documents.
QColor _background;
int _intensity = 0;
std::vector<QColor> _background;
float64 _patternOpacity = 0.;
int _gradientRotation = 0;
};

View file

@ -781,24 +781,39 @@ bool MainWidget::selectingPeer() const {
}
void MainWidget::cacheBackground() {
if (Window::Theme::Background()->colorForFill()) {
const auto background = Window::Theme::Background();
if (background->colorForFill()) {
return;
} else if (Window::Theme::Background()->tile()) {
auto &bg = Window::Theme::Background()->pixmapForTiled();
auto result = QImage(_willCacheFor.width() * cIntRetinaFactor(), _willCacheFor.height() * cIntRetinaFactor(), QImage::Format_RGB32);
}
const auto gradient = background->gradientForFill();
const auto patternOpacity = background->paper().patternOpacity();
const auto &bg = background->pixmap();
if (background->tile() || bg.isNull()) {
auto result = gradient.isNull()
? QImage(
_willCacheFor.size() * cIntRetinaFactor(),
QImage::Format_ARGB32_Premultiplied)
: gradient.scaled(
_willCacheFor.size() * cIntRetinaFactor(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
result.setDevicePixelRatio(cRetinaFactor());
{
if (!bg.isNull()) {
QPainter p(&result);
auto w = bg.width() / cRetinaFactor();
auto h = bg.height() / cRetinaFactor();
if (!gradient.isNull()) {
p.setCompositionMode(QPainter::CompositionMode_SoftLight);
p.setOpacity(patternOpacity);
}
const auto &tiled = background->pixmapForTiled();
auto w = tiled.width() / cRetinaFactor();
auto h = tiled.height() / cRetinaFactor();
auto sx = 0;
auto sy = 0;
auto cx = qCeil(_willCacheFor.width() / w);
auto cy = qCeil(_willCacheFor.height() / h);
for (int i = sx; i < cx; ++i) {
for (int j = sy; j < cy; ++j) {
p.drawPixmap(QPointF(i * w, j * h), bg);
p.drawPixmap(QPointF(i * w, j * h), tiled);
}
}
}
@ -806,18 +821,30 @@ void MainWidget::cacheBackground() {
_cachedY = 0;
_cachedBackground = Ui::PixmapFromImage(std::move(result));
} else {
auto &bg = Window::Theme::Background()->pixmap();
QRect to, from;
Window::Theme::ComputeBackgroundRects(_willCacheFor, bg.size(), to, from);
auto image = bg.toImage().copy(from).scaled(
to.width() * cIntRetinaFactor(),
to.height() * cIntRetinaFactor(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
auto result = gradient.isNull()
? std::move(image)
: gradient.scaled(
image.size(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
result.setDevicePixelRatio(cRetinaFactor());
if (!gradient.isNull()) {
QPainter p(&result);
p.setCompositionMode(QPainter::CompositionMode_SoftLight);
p.setOpacity(patternOpacity);
p.drawImage(QRect(QPoint(), to.size()), image);
}
image = QImage();
_cachedX = to.x();
_cachedY = to.y();
_cachedBackground = Ui::PixmapFromImage(
bg.toImage().copy(from).scaled(
to.width() * cIntRetinaFactor(),
to.height() * cIntRetinaFactor(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation));
_cachedBackground = Ui::PixmapFromImage(std::move(result));
_cachedBackground.setDevicePixelRatio(cRetinaFactor());
}
_cachedFor = _willCacheFor;

View file

@ -551,41 +551,39 @@ void BackgroundRow::radialAnimationCallback(crl::time now) {
}
void BackgroundRow::updateImage() {
int32 size = st::settingsBackgroundThumb * cIntRetinaFactor();
QImage back(size, size, QImage::Format_ARGB32_Premultiplied);
const auto size = st::settingsBackgroundThumb;
const auto fullsize = size * cIntRetinaFactor();
QImage back(fullsize, fullsize, QImage::Format_ARGB32_Premultiplied);
back.setDevicePixelRatio(cRetinaFactor());
{
Painter p(&back);
PainterHighQualityEnabler hq(p);
if (const auto color = Window::Theme::Background()->colorForFill()) {
p.fillRect(
0,
0,
st::settingsBackgroundThumb,
st::settingsBackgroundThumb,
*color);
const auto background = Window::Theme::Background();
if (const auto color = background->colorForFill()) {
p.fillRect(0, 0, size, size, *color);
} else {
const auto &pix = Window::Theme::Background()->pixmap();
const auto sx = (pix.width() > pix.height())
? ((pix.width() - pix.height()) / 2)
: 0;
const auto sy = (pix.height() > pix.width())
? ((pix.height() - pix.width()) / 2)
: 0;
const auto s = (pix.width() > pix.height())
? pix.height()
: pix.width();
p.drawPixmap(
0,
0,
st::settingsBackgroundThumb,
st::settingsBackgroundThumb,
pix,
sx,
sy,
s,
s);
const auto gradient = background->gradientForFill();
const auto patternOpacity = background->paper().patternOpacity();
if (!gradient.isNull()) {
auto hq = PainterHighQualityEnabler(p);
p.drawImage(QRect(0, 0, size, size), gradient);
p.setCompositionMode(QPainter::CompositionMode_SoftLight);
p.setOpacity(patternOpacity);
}
const auto &pix = background->pixmap();
if (!pix.isNull()) {
const auto sx = (pix.width() > pix.height())
? ((pix.width() - pix.height()) / 2)
: 0;
const auto sy = (pix.height() > pix.width())
? ((pix.height() - pix.width()) / 2)
: 0;
const auto s = (pix.width() > pix.height())
? pix.height()
: pix.width();
p.drawPixmap(0, 0, size, size, pix, sx, sy, s, s);
}
}
}
Images::prepareRound(back, ImageRoundRadius::Small);

View file

@ -90,8 +90,9 @@ void SectionWidget::PaintBackground(
QRect clip) {
Painter p(widget);
const auto background = Window::Theme::Background();
auto fill = QRect(0, 0, widget->width(), controller->content()->height());
if (const auto color = Window::Theme::Background()->colorForFill()) {
if (const auto color = background->colorForFill()) {
p.fillRect(fill, *color);
return;
}
@ -99,31 +100,45 @@ void SectionWidget::PaintBackground(
auto x = 0, y = 0;
auto cached = controller->content()->cachedBackground(fill, x, y);
if (cached.isNull()) {
if (Window::Theme::Background()->tile()) {
auto &pix = Window::Theme::Background()->pixmapForTiled();
auto left = clip.left();
auto top = clip.top();
auto right = clip.left() + clip.width();
auto bottom = clip.top() + clip.height();
auto w = pix.width() / cRetinaFactor();
auto h = pix.height() / cRetinaFactor();
auto sx = qFloor(left / w);
auto sy = qFloor((top - fromy) / h);
auto cx = qCeil(right / w);
auto cy = qCeil((bottom - fromy) / h);
for (auto i = sx; i < cx; ++i) {
for (auto j = sy; j < cy; ++j) {
p.drawPixmap(QPointF(i * w, fromy + j * h), pix);
const auto gradient = background->gradientForFill();
const auto patternOpacity = background->paper().patternOpacity();
const auto &bg = background->pixmap();
if (background->tile() || bg.isNull()) {
if (!gradient.isNull()) {
auto hq = PainterHighQualityEnabler(p);
p.drawImage(fill, gradient);
p.setCompositionMode(QPainter::CompositionMode_SoftLight);
p.setOpacity(patternOpacity);
}
if (!bg.isNull()) {
auto &tiled = background->pixmapForTiled();
auto left = clip.left();
auto top = clip.top();
auto right = clip.left() + clip.width();
auto bottom = clip.top() + clip.height();
auto w = tiled.width() / cRetinaFactor();
auto h = tiled.height() / cRetinaFactor();
auto sx = qFloor(left / w);
auto sy = qFloor((top - fromy) / h);
auto cx = qCeil(right / w);
auto cy = qCeil((bottom - fromy) / h);
for (auto i = sx; i < cx; ++i) {
for (auto j = sy; j < cy; ++j) {
p.drawPixmap(QPointF(i * w, fromy + j * h), tiled);
}
}
}
} else {
PainterHighQualityEnabler hq(p);
auto &pix = Window::Theme::Background()->pixmap();
auto hq = PainterHighQualityEnabler(p);
QRect to, from;
Window::Theme::ComputeBackgroundRects(fill, pix.size(), to, from);
Window::Theme::ComputeBackgroundRects(fill, bg.size(), to, from);
if (!gradient.isNull()) {
p.drawImage(to, gradient);
p.setCompositionMode(QPainter::CompositionMode_SoftLight);
p.setOpacity(patternOpacity);
}
to.moveTop(to.top() + fromy);
p.drawPixmap(to, pix, from);
p.drawPixmap(to, bg, from);
}
} else {
p.drawPixmap(x, fromy + y, cached);

View file

@ -58,18 +58,23 @@ inline bool AreTestingTheme() {
return !GlobalApplying.paletteForRevert.isEmpty();
}
bool CalculateIsMonoColorImage(const QImage &image) {
if (!image.isNull()) {
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 false;
}
}
return true;
std::optional<QColor> CalculateImageMonoColor(const QImage &image) {
if (image.isNull()) {
return std::nullopt;
}
return false;
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.format() == QImage::Format_ARGB32_Premultiplied)
&& !image.size().isEmpty();
}
QByteArray readThemeContent(const QString &path) {
@ -731,7 +736,7 @@ void ChatBackground::set(const Data::WallPaper &paper, QImage image) {
}
if (Data::IsThemeWallPaper(_paper)) {
(nightMode() ? _tileNightValue : _tileDayValue) = _themeTile;
setPreparedImage(_themeImage, _themeImage);
setPrepared(_themeImage, _themeImage, QImage());
} else if (Data::details::IsTestingThemeWallPaper(_paper)
|| Data::details::IsTestingDefaultWallPaper(_paper)
|| Data::details::IsTestingEditorWallPaper(_paper)) {
@ -741,8 +746,9 @@ void ChatBackground::set(const Data::WallPaper &paper, QImage image) {
setPaper(Data::details::TestingDefaultWallPaper());
}
image = postprocessBackgroundImage(std::move(image));
setPreparedImage(image, image);
setPrepared(image, image, QImage());
} else {
const auto &bgColors = _paper.backgroundColors();
if (Data::IsLegacy1DefaultWallPaper(_paper)) {
image.load(qsl(":/gui/art/bg_initial.jpg"));
const auto scale = cScale() * cIntRetinaFactor();
@ -752,7 +758,7 @@ void ChatBackground::set(const Data::WallPaper &paper, QImage image) {
Qt::SmoothTransformation);
}
} else if (Data::IsDefaultWallPaper(_paper)
|| (_paper.backgroundColors().empty() && image.isNull())) {
|| (bgColors.empty() && image.isNull())) {
setPaper(Data::DefaultWallPaper().withParamsFrom(_paper));
image.load(qsl(":/gui/art/background.jpg"));
}
@ -762,29 +768,38 @@ void ChatBackground::set(const Data::WallPaper &paper, QImage image) {
|| Data::IsLegacy1DefaultWallPaper(_paper))
? QImage()
: image));
if (const auto fill = _paper.backgroundColor()) {
if (_paper.isPattern() && !image.isNull()) {
if (_paper.isPattern() && !image.isNull()) {
if (bgColors.size() < 2) {
auto prepared = postprocessBackgroundImage(
Data::PreparePatternImage(
image,
*fill,
Data::PatternColor(*fill),
_paper.patternIntensity()));
setPreparedImage(std::move(image), std::move(prepared));
bgColors,
_paper.gradientRotation(),
_paper.patternOpacity()));
setPrepared(
std::move(image),
std::move(prepared),
QImage());
} else {
_original = QImage();
_pixmap = QPixmap();
_pixmapForTiled = QPixmap();
if (adjustPaletteRequired()) {
adjustPaletteUsingColor(*fill);
}
setPrepared(
image,
image,
Data::GenerateDitheredGradient(_paper));
}
} else if (bgColors.size() == 1) {
setPrepared(QImage(), QImage(), QImage());
} else if (!bgColors.empty()) {
setPrepared(
QImage(),
QImage(),
Data::GenerateDitheredGradient(_paper));
} else {
image = postprocessBackgroundImage(std::move(image));
setPreparedImage(image, image);
setPrepared(image, image, QImage());
}
}
Assert(colorForFill()
|| !_gradient.isNull()
|| (!_original.isNull()
&& !_pixmap.isNull()
&& !_pixmapForTiled.isNull()));
@ -797,27 +812,37 @@ void ChatBackground::set(const Data::WallPaper &paper, QImage image) {
checkUploadWallPaper();
}
void ChatBackground::setPreparedImage(QImage original, QImage prepared) {
Expects(original.format() == QImage::Format_ARGB32_Premultiplied);
Expects(original.width() > 0 && original.height() > 0);
Expects(prepared.format() == QImage::Format_ARGB32_Premultiplied);
Expects(prepared.width() > 0 && prepared.height() > 0);
void ChatBackground::setPrepared(
QImage original,
QImage prepared,
QImage gradient) {
Expects(original.isNull() || GoodImageFormatAndSize(original));
Expects(prepared.isNull() || GoodImageFormatAndSize(prepared));
Expects(gradient.isNull() || GoodImageFormatAndSize(gradient));
_original = std::move(original);
if (!_paper.isPattern() && _paper.isBlurred()) {
if (!prepared.isNull() && !_paper.isPattern() && _paper.isBlurred()) {
prepared = Data::PrepareBlurredBackground(std::move(prepared));
}
if (adjustPaletteRequired()) {
adjustPaletteUsingBackground(prepared);
if (!gradient.isNull()) {
adjustPaletteUsingBackground(gradient);
} else if (!prepared.isNull()) {
adjustPaletteUsingBackground(prepared);
} else if (!_paper.backgroundColors().empty()) {
adjustPaletteUsingColor(_paper.backgroundColors().front());
}
}
_original = std::move(original);
_gradient = std::move(gradient);
preparePixmaps(std::move(prepared));
}
void ChatBackground::preparePixmaps(QImage image) {
const auto width = image.width();
const auto height = image.height();
const auto isSmallForTiled = (width < kMinimumTiledSize)
|| (height < kMinimumTiledSize);
const auto isSmallForTiled = (width > 0 && height > 0)
&& (width < kMinimumTiledSize || height < kMinimumTiledSize);
if (isSmallForTiled) {
const auto repeatTimesX = qCeil(kMinimumTiledSize / (1. * width));
const auto repeatTimesY = qCeil(kMinimumTiledSize / (1. * height));
@ -841,8 +866,12 @@ void ChatBackground::preparePixmaps(QImage image) {
}
_pixmapForTiled = Ui::PixmapFromImage(std::move(imageForTiled));
}
_isMonoColorImage = CalculateIsMonoColorImage(image);
_pixmap = Ui::PixmapFromImage(std::move(image));
_imageMonoColor = _gradient.isNull()
? CalculateImageMonoColor(image)
: std::nullopt;
_pixmap = image.isNull()
? QPixmap()
: Ui::PixmapFromImage(std::move(image));
if (!isSmallForTiled) {
_pixmapForTiled = _pixmap;
}
@ -910,7 +939,15 @@ void ChatBackground::adjustPaletteUsingColor(QColor color) {
}
std::optional<QColor> ChatBackground::colorForFill() const {
return _pixmap.isNull() ? _paper.backgroundColor() : std::nullopt;
return !_pixmap.isNull()
? imageMonoColor()
: !_gradient.isNull()
? std::nullopt
: _paper.backgroundColor();
}
QImage ChatBackground::gradientForFill() const {
return _gradient;
}
QImage ChatBackground::createCurrentImage() const {
@ -921,8 +958,24 @@ QImage ChatBackground::createCurrentImage() const {
QImage::Format_ARGB32_Premultiplied);
result.fill(*fill);
return result;
} else if (_gradient.isNull()) {
return pixmap().toImage();
} else if (pixmap().isNull()) {
return _gradient;
}
return pixmap().toImage();
const auto &pattern = pixmap();
auto result = _gradient.scaled(
pattern.size(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
result.setDevicePixelRatio(1.);
{
auto p = QPainter(&result);
p.setCompositionMode(QPainter::CompositionMode_SoftLight);
p.setOpacity(paper().patternOpacity());
p.drawPixmap(QRect(QPoint(), pattern.size()), pattern);
}
return result;
}
bool ChatBackground::tile() const {
@ -949,8 +1002,8 @@ bool ChatBackground::tileNight() const {
return _tileNightValue;
}
bool ChatBackground::isMonoColorImage() const {
return _isMonoColorImage;
std::optional<QColor> ChatBackground::imageMonoColor() const {
return _imageMonoColor;
}
void ChatBackground::setTile(bool tile) {

View file

@ -164,7 +164,7 @@ public:
void appliedEditedPalette();
void downloadingStarted(bool tile);
[[nodiscard]] Data::WallPaper paper() const {
[[nodiscard]] const Data::WallPaper &paper() const {
return _paper;
}
[[nodiscard]] WallPaperId id() const {
@ -177,11 +177,12 @@ public:
return _pixmapForTiled;
}
[[nodiscard]] std::optional<QColor> colorForFill() const;
[[nodiscard]] QImage gradientForFill() const;
[[nodiscard]] QImage createCurrentImage() const;
[[nodiscard]] bool tile() const;
[[nodiscard]] bool tileDay() const;
[[nodiscard]] bool tileNight() const;
[[nodiscard]] bool isMonoColorImage() const;
[[nodiscard]] std::optional<QColor> imageMonoColor() const;
[[nodiscard]] bool nightModeChangeAllowed() const;
private:
@ -195,7 +196,7 @@ private:
[[nodiscard]] bool started() const;
void initialRead();
void saveForRevert();
void setPreparedImage(QImage original, QImage prepared);
void setPrepared(QImage original, QImage prepared, QImage gradient);
void preparePixmaps(QImage image);
void writeNewBackgroundSettings();
void setPaper(const Data::WallPaper &paper);
@ -236,6 +237,7 @@ private:
rpl::event_stream<BackgroundUpdate> _updates;
Data::WallPaper _paper = Data::details::UninitializedWallPaper();
std::optional<QColor> _paperColor;
QImage _gradient;
QImage _original;
QPixmap _pixmap;
QPixmap _pixmapForTiled;
@ -245,7 +247,7 @@ private:
std::optional<bool> _localStoredTileDayValue;
std::optional<bool> _localStoredTileNightValue;
bool _isMonoColorImage = false;
std::optional<QColor> _imageMonoColor;
Object _themeObject;
QImage _themeImage;

View file

@ -87,9 +87,10 @@ constexpr auto kMinDiffIntensity = 0.25;
[[nodiscard]] bool IsFilledCover() {
const auto background = Window::Theme::Background();
return false; AssertIsDebug();
return background->tile()
|| background->colorForFill().has_value()
|| background->isMonoColorImage()
|| !background->gradientForFill().isNull()
|| background->paper().isPattern()
|| Data::IsLegacy1DefaultWallPaper(background->paper());
}