Don't blend SoftLight patterns in realtime.

This commit is contained in:
John Preston 2021-08-13 20:19:06 +03:00
parent 2b46f87d7b
commit b9a9520ef5
7 changed files with 92 additions and 59 deletions

View file

@ -91,36 +91,61 @@ void SectionWidget::PaintBackground(
Painter p(widget); Painter p(widget);
const auto background = Window::Theme::Background(); const auto background = Window::Theme::Background();
auto fill = QRect(0, 0, widget->width(), controller->content()->height()); const auto fullHeight = controller->content()->height();
if (const auto color = background->colorForFill()) { if (const auto color = background->colorForFill()) {
p.fillRect(fill, *color); p.fillRect(clip, *color);
return;
}
auto fromy = controller->content()->backgroundFromY();
auto cached = controller->cachedBackground(fill);
if (!cached.pixmap.isNull()) {
p.drawPixmap(cached.x, fromy + cached.y, cached.pixmap);
return; return;
} }
const auto gradient = background->gradientForFill(); const auto gradient = background->gradientForFill();
const auto fill = QSize(widget->width(), fullHeight);
auto fromy = controller->content()->backgroundFromY();
auto cached = controller->cachedBackground(fill);
const auto goodCache = (cached.area == fill);
const auto useCached = goodCache || !gradient.isNull();
if (!cached.pixmap.isNull()) {
const auto to = QRect(
QPoint(cached.x, fromy + cached.y),
cached.pixmap.size() / cIntRetinaFactor());
if (goodCache) {
p.drawPixmap(to, cached.pixmap);
} else {
const auto sx = fill.width() / float64(cached.area.width());
const auto sy = fill.height() / float64(cached.area.height());
const auto round = [](float64 value) -> int {
return (value >= 0.)
? int(std::ceil(value))
: int(std::floor(value));
};
const auto sto = QPoint(round(to.x() * sx), round(to.y() * sy));
p.drawPixmap(
sto.x(),
sto.y(),
round((to.x() + to.width()) * sx) - sto.x(),
round((to.y() + to.height()) * sy) - sto.y(),
cached.pixmap);
}
return;
}
const auto patternOpacity = background->paper().patternOpacity(); const auto patternOpacity = background->paper().patternOpacity();
const auto &bg = background->pixmap(); const auto &bg = background->pixmap();
if (!bg.isNull() && !background->tile()) { if (!bg.isNull() && !background->tile()) {
auto hq = PainterHighQualityEnabler(p); auto hq = PainterHighQualityEnabler(p);
QRect to, from; const auto rects = Window::Theme::ComputeBackgroundRects(
Window::Theme::ComputeBackgroundRects(fill, bg.size(), to, from); fill,
bg.size());
if (!gradient.isNull()) { if (!gradient.isNull()) {
p.drawImage(to, gradient); p.drawImage(rects.to, gradient);
p.setCompositionMode(QPainter::CompositionMode_SoftLight); p.setCompositionMode(QPainter::CompositionMode_SoftLight);
p.setOpacity(patternOpacity); p.setOpacity(patternOpacity);
} }
auto to = rects.to;
to.moveTop(to.top() + fromy); to.moveTop(to.top() + fromy);
p.drawPixmap(to, bg, from); p.drawPixmap(to, bg, rects.from);
return; return;
} }
if (!gradient.isNull()) { if (!gradient.isNull()) {
auto hq = PainterHighQualityEnabler(p); auto hq = PainterHighQualityEnabler(p);
p.drawImage(fill, gradient); p.drawImage(QRect(QPoint(0, fromy), fill), gradient);
p.setCompositionMode(QPainter::CompositionMode_SoftLight); p.setCompositionMode(QPainter::CompositionMode_SoftLight);
p.setOpacity(patternOpacity); p.setOpacity(patternOpacity);
} }

View file

@ -1494,27 +1494,31 @@ QImage PreprocessBackgroundImage(QImage image) {
return image; return image;
} }
void ComputeBackgroundRects(QRect wholeFill, QSize imageSize, QRect &to, QRect &from) { BackgroundRects ComputeBackgroundRects(QSize fillSize, QSize imageSize) {
if (uint64(imageSize.width()) * wholeFill.height() > uint64(imageSize.height()) * wholeFill.width()) { if (uint64(imageSize.width()) * fillSize.height() > uint64(imageSize.height()) * fillSize.width()) {
float64 pxsize = wholeFill.height() / float64(imageSize.height()); float64 pxsize = fillSize.height() / float64(imageSize.height());
int takewidth = qCeil(wholeFill.width() / pxsize); int takewidth = qCeil(fillSize.width() / pxsize);
if (takewidth > imageSize.width()) { if (takewidth > imageSize.width()) {
takewidth = imageSize.width(); takewidth = imageSize.width();
} else if ((imageSize.width() % 2) != (takewidth % 2)) { } else if ((imageSize.width() % 2) != (takewidth % 2)) {
++takewidth; ++takewidth;
} }
to = QRect(int((wholeFill.width() - takewidth * pxsize) / 2.), 0, qCeil(takewidth * pxsize), wholeFill.height()); return {
from = QRect((imageSize.width() - takewidth) / 2, 0, takewidth, imageSize.height()); .from = QRect((imageSize.width() - takewidth) / 2, 0, takewidth, imageSize.height()),
.to = QRect(int((fillSize.width() - takewidth * pxsize) / 2.), 0, qCeil(takewidth * pxsize), fillSize.height()),
};
} else { } else {
float64 pxsize = wholeFill.width() / float64(imageSize.width()); float64 pxsize = fillSize.width() / float64(imageSize.width());
int takeheight = qCeil(wholeFill.height() / pxsize); int takeheight = qCeil(fillSize.height() / pxsize);
if (takeheight > imageSize.height()) { if (takeheight > imageSize.height()) {
takeheight = imageSize.height(); takeheight = imageSize.height();
} else if ((imageSize.height() % 2) != (takeheight % 2)) { } else if ((imageSize.height() % 2) != (takeheight % 2)) {
++takeheight; ++takeheight;
} }
to = QRect(0, int((wholeFill.height() - takeheight * pxsize) / 2.), wholeFill.width(), qCeil(takeheight * pxsize)); return {
from = QRect(0, (imageSize.height() - takeheight) / 2, imageSize.width(), takeheight); .from = QRect(0, (imageSize.height() - takeheight) / 2, imageSize.width(), takeheight),
.to = QRect(0, int((fillSize.height() - takeheight * pxsize) / 2.), fillSize.width(), qCeil(takeheight * pxsize)),
};
} }
} }

View file

@ -270,7 +270,13 @@ private:
[[nodiscard]] ChatBackground *Background(); [[nodiscard]] ChatBackground *Background();
void ComputeBackgroundRects(QRect wholeFill, QSize imageSize, QRect &to, QRect &from); struct BackgroundRects {
QRect from;
QRect to;
};
[[nodiscard]] BackgroundRects ComputeBackgroundRects(
QSize fillSize,
QSize imageSize);
bool ReadPaletteValues(const QByteArray &content, Fn<bool(QLatin1String name, QLatin1String value)> callback); bool ReadPaletteValues(const QByteArray &content, Fn<bool(QLatin1String name, QLatin1String value)> callback);

View file

@ -455,12 +455,12 @@ void Generator::paintHistoryBackground() {
} else { } else {
PainterHighQualityEnabler hq(*_p); PainterHighQualityEnabler hq(*_p);
auto fill = QRect(_topBar.x(), _topBar.y(), _topBar.width(), _body.height()); auto fill = QSize(_topBar.width(), _body.height());
QRect to, from; const auto rects = ComputeBackgroundRects(fill, background.size());
ComputeBackgroundRects(fill, background.size(), to, from); auto to = rects.to;
to.moveTop(to.top() + fromy); to.moveTop(to.top() + fromy);
to.moveTopLeft(to.topLeft() + _history.topLeft()); to.moveTopLeft(to.topLeft() + _history.topLeft());
_p->drawImage(to, background, from); _p->drawImage(to, background, rects.from);
} }
_p->setClipping(false); _p->setClipping(false);
} }

View file

@ -984,24 +984,24 @@ void MainMenu::refreshMenu() {
} }
void MainMenu::refreshBackground() { void MainMenu::refreshBackground() {
const auto fill = QRect(0, 0, st::mainMenuWidth, st::mainMenuCoverHeight); const auto fill = QSize(st::mainMenuWidth, st::mainMenuCoverHeight);
const auto intensityText = IntensityOfColor(st::mainMenuCoverFg->c); const auto intensityText = IntensityOfColor(st::mainMenuCoverFg->c);
const auto background = Window::Theme::Background(); const auto background = Window::Theme::Background();
const auto &paper = background->paper(); const auto &paper = background->paper();
const auto &pixmap = background->pixmap(); const auto &pixmap = background->pixmap();
QRect to, from; QRect to, from;
Window::Theme::ComputeBackgroundRects(fill, pixmap.size(), to, from); const auto rects = Window::Theme::ComputeBackgroundRects(fill, pixmap.size());
auto backgroundImage = !paper.backgroundColors().empty() auto backgroundImage = !paper.backgroundColors().empty()
? Data::GenerateWallPaper( ? Data::GenerateWallPaper(
fill.size() * cIntRetinaFactor(), fill * cIntRetinaFactor(),
paper.backgroundColors(), paper.backgroundColors(),
paper.gradientRotation(), paper.gradientRotation(),
paper.patternOpacity(), paper.patternOpacity(),
[&](QPainter &p) { p.drawPixmap(to, pixmap, from); }) [&](QPainter &p) { p.drawPixmap(to, pixmap, from); })
: QImage( : QImage(
fill.size() * cIntRetinaFactor(), fill * cIntRetinaFactor(),
QImage::Format_ARGB32_Premultiplied); QImage::Format_ARGB32_Premultiplied);
QPainter p(&backgroundImage); QPainter p(&backgroundImage);
@ -1019,7 +1019,7 @@ void MainMenu::refreshBackground() {
// Solid color. // Solid color.
if (const auto color = background->colorForFill()) { if (const auto color = background->colorForFill()) {
const auto intensity = IntensityOfColor(*color); const auto intensity = IntensityOfColor(*color);
p.fillRect(fill, *color); p.fillRect(QRect(QPoint(), fill), *color);
if (std::abs(intensity - intensityText) < kMinDiffIntensity) { if (std::abs(intensity - intensityText) < kMinDiffIntensity) {
drawShadow(p); drawShadow(p);
} }

View file

@ -1318,17 +1318,15 @@ void SessionController::openDocument(
session().data().message(contextId)); session().data().message(contextId));
} }
CachedBackground SessionController::cachedBackground(QRect area) { CachedBackground SessionController::cachedBackground(QSize area) {
if (!_cachedBackground.pixmap.isNull() if (_cachedBackground.area != area) {
&& area == _cachedBackground.area) { if (_willCacheForArea != area || !_cacheBackgroundTimer.isActive()) {
return _cachedBackground; _willCacheForArea = area;
_lastAreaChangeTime = crl::now();
_cacheBackgroundTimer.callOnce(kCacheBackgroundFastTimeout);
}
} }
if (_willCacheForArea != area || !_cacheBackgroundTimer.isActive()) { return _cachedBackground;
_willCacheForArea = area;
_lastAreaChangeTime = crl::now();
_cacheBackgroundTimer.callOnce(kCacheBackgroundFastTimeout);
}
return {};
} }
void SessionController::cacheBackground() { void SessionController::cacheBackground() {
@ -1348,10 +1346,10 @@ void SessionController::cacheBackground() {
if (background->tile() || bg.isNull()) { if (background->tile() || bg.isNull()) {
auto result = gradient.isNull() auto result = gradient.isNull()
? QImage( ? QImage(
_willCacheForArea.size() * cIntRetinaFactor(), _willCacheForArea * cIntRetinaFactor(),
QImage::Format_ARGB32_Premultiplied) QImage::Format_ARGB32_Premultiplied)
: gradient.scaled( : gradient.scaled(
_willCacheForArea.size() * cIntRetinaFactor(), _willCacheForArea * cIntRetinaFactor(),
Qt::IgnoreAspectRatio, Qt::IgnoreAspectRatio,
Qt::SmoothTransformation); Qt::SmoothTransformation);
result.setDevicePixelRatio(cRetinaFactor()); result.setDevicePixelRatio(cRetinaFactor());
@ -1379,15 +1377,12 @@ void SessionController::cacheBackground() {
.area = _willCacheForArea, .area = _willCacheForArea,
}; };
} else { } else {
QRect to, from; const auto rects = Window::Theme::ComputeBackgroundRects(
Window::Theme::ComputeBackgroundRects(
_willCacheForArea, _willCacheForArea,
bg.size(), bg.size());
to, auto image = bg.toImage().copy(rects.from).scaled(
from); rects.to.width() * cIntRetinaFactor(),
auto image = bg.toImage().copy(from).scaled( rects.to.height() * cIntRetinaFactor(),
to.width() * cIntRetinaFactor(),
to.height() * cIntRetinaFactor(),
Qt::IgnoreAspectRatio, Qt::IgnoreAspectRatio,
Qt::SmoothTransformation); Qt::SmoothTransformation);
auto result = gradient.isNull() auto result = gradient.isNull()
@ -1401,16 +1396,19 @@ void SessionController::cacheBackground() {
QPainter p(&result); QPainter p(&result);
p.setCompositionMode(QPainter::CompositionMode_SoftLight); p.setCompositionMode(QPainter::CompositionMode_SoftLight);
p.setOpacity(patternOpacity); p.setOpacity(patternOpacity);
p.drawImage(QRect(QPoint(), to.size()), image); p.drawImage(QRect(QPoint(), rects.to.size()), image);
} }
image = QImage(); image = QImage();
_cachedBackground = { _cachedBackground = {
.pixmap = Ui::PixmapFromImage(std::move(result)), .pixmap = Ui::PixmapFromImage(std::move(result)),
.area = _willCacheForArea, .area = _willCacheForArea,
.x = to.x(), .x = rects.to.x(),
.y = to.y(), .y = rects.to.y(),
}; };
} }
if (!gradient.isNull()) {
content()->update();
}
} }
void SessionController::clearCachedBackground() { void SessionController::clearCachedBackground() {

View file

@ -58,7 +58,7 @@ class FiltersMenu;
struct CachedBackground { struct CachedBackground {
QPixmap pixmap; QPixmap pixmap;
QRect area; QSize area;
int x = 0; int x = 0;
int y = 0; int y = 0;
}; };
@ -400,7 +400,7 @@ public:
void toggleFiltersMenu(bool enabled); void toggleFiltersMenu(bool enabled);
[[nodiscard]] rpl::producer<> filtersMenuChanged() const; [[nodiscard]] rpl::producer<> filtersMenuChanged() const;
[[nodiscard]] CachedBackground cachedBackground(QRect area); [[nodiscard]] CachedBackground cachedBackground(QSize area);
rpl::lifetime &lifetime() { rpl::lifetime &lifetime() {
return _lifetime; return _lifetime;
@ -462,7 +462,7 @@ private:
rpl::event_stream<> _filtersMenuChanged; rpl::event_stream<> _filtersMenuChanged;
CachedBackground _cachedBackground; CachedBackground _cachedBackground;
QRect _willCacheForArea; QSize _willCacheForArea;
crl::time _lastAreaChangeTime = 0; crl::time _lastAreaChangeTime = 0;
base::Timer _cacheBackgroundTimer; base::Timer _cacheBackgroundTimer;