From 1d7ad701b48fe677b934025a4f552d9edca43f38 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 21 Dec 2022 19:12:58 +0400 Subject: [PATCH] Implement blurred background for photo editor. --- .../editor/editor_layer_widget.cpp | 160 +++++++++++++++++- .../SourceFiles/editor/editor_layer_widget.h | 21 ++- 2 files changed, 177 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/editor/editor_layer_widget.cpp b/Telegram/SourceFiles/editor/editor_layer_widget.cpp index 92a9157ea..daeddf2d1 100644 --- a/Telegram/SourceFiles/editor/editor_layer_widget.cpp +++ b/Telegram/SourceFiles/editor/editor_layer_widget.cpp @@ -7,32 +7,188 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "editor/editor_layer_widget.h" +#include "ui/painter.h" + #include namespace Editor { +namespace { + +constexpr auto kCacheBackgroundFastTimeout = crl::time(200); +constexpr auto kCacheBackgroundFullTimeout = crl::time(1000); +constexpr auto kFadeBackgroundDuration = crl::time(200); + +// Thread: Main. +[[nodiscard]] bool IsNightMode() { + return (st::windowBg->c.lightnessF() < 0.5); +} + +[[nodiscard]] QColor BlurOverlayColor(bool night) { + return QColor(16, 16, 16, night ? 128 : 192); +} + +[[nodiscard]] QImage ProcessBackground(QImage image, bool night) { + const auto size = image.size(); + auto p = QPainter(&image); + p.fillRect( + QRect(QPoint(), image.size() / image.devicePixelRatio()), + BlurOverlayColor(night)); + p.end(); + return Images::DitherImage( + Images::BlurLargeImage( + std::move(image).scaled( + size / style::ConvertScale(4), + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation), + 24).scaled( + size, + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation)); +} + +} // namespace LayerWidget::LayerWidget( not_null parent, base::unique_qptr content) : Ui::LayerWidget(parent) -, _content(std::move(content)) { +, _content(std::move(content)) +, _backgroundTimer([=] { checkCacheBackground(); }) { _content->setParent(this); _content->show(); paintRequest( ) | rpl::start_with_next([=](const QRect &clip) { auto p = QPainter(this); - p.fillRect(clip, st::photoEditorBg); + const auto faded = _backgroundFade.value(1.); + if (faded < 1.) { + p.drawImage(rect(), _backgroundBack); + if (faded > 0.) { + p.setOpacity(faded); + p.drawImage(rect(), _background); + } + } else { + p.drawImage(rect(), _background); + } }, lifetime()); +} + +bool LayerWidget::eventHook(QEvent *e) { + return RpWidget::eventHook(e); +} + +void LayerWidget::start() { + _backgroundNight = IsNightMode(); + _background = ProcessBackground(renderBackground(), _backgroundNight); sizeValue( ) | rpl::start_with_next([=](const QSize &size) { + checkBackgroundStale(); _content->resize(size); }, lifetime()); + + style::PaletteChanged() | rpl::start_with_next([=] { + checkBackgroundStale(); + }, lifetime()); +} + +void LayerWidget::checkBackgroundStale() { + const auto ratio = style::DevicePixelRatio(); + const auto &ready = _backgroundNext.isNull() + ? _background + : _backgroundNext; + if (ready.size() == size() * ratio + && _backgroundNight == IsNightMode()) { + _backgroundTimer.cancel(); + } else if (!_backgroundCaching && !_backgroundTimer.isActive()) { + _lastAreaChangeTime = crl::now(); + _backgroundTimer.callOnce(kCacheBackgroundFastTimeout); + } +} + +QImage LayerWidget::renderBackground() { + const auto target = parentWidget()->parentWidget(); + Ui::SendPendingMoveResizeEvents(target); + + const auto ratio = style::DevicePixelRatio(); + auto image = QImage(size() * ratio, QImage::Format_ARGB32_Premultiplied); + image.setDevicePixelRatio(ratio); + + const auto shown = !parentWidget()->isHidden(); + if (shown) parentWidget()->hide(); + + auto p = QPainter(&image); + Ui::RenderWidget(p, target, QPoint(), geometry()); + p.end(); + + if (shown) parentWidget()->show(); + + return image; +} + +void LayerWidget::checkCacheBackground() { + if (_backgroundCaching || _backgroundTimer.isActive()) { + return; + } + const auto now = crl::now(); + if (now - _lastAreaChangeTime < kCacheBackgroundFullTimeout + && QGuiApplication::mouseButtons() != 0) { + _backgroundTimer.callOnce(kCacheBackgroundFastTimeout); + return; + } + cacheBackground(); +} + +void LayerWidget::cacheBackground() { + _backgroundCaching = true; + const auto weak = Ui::MakeWeak(this); + const auto night = IsNightMode(); + crl::async([weak, night, image = renderBackground()]() mutable { + auto result = ProcessBackground(image, night); + crl::on_main([weak, night, result = std::move(result)]() mutable { + if (const auto strong = weak.data()) { + strong->backgroundReady(std::move(result), night); + } + }); + }); +} + +void LayerWidget::backgroundReady(QImage background, bool night) { + _backgroundCaching = false; + + const auto required = size() * style::DevicePixelRatio(); + if (background.size() == required && night == IsNightMode()) { + _backgroundNext = std::move(background); + _backgroundNight = night; + if (!_backgroundFade.animating()) { + startBackgroundFade(); + } + update(); + } else if (_background.size() != required) { + _backgroundTimer.callOnce(kCacheBackgroundFastTimeout); + } +} + +void LayerWidget::startBackgroundFade() { + if (_backgroundNext.isNull()) { + return; + } + _backgroundBack = std::move(_background); + _background = base::take(_backgroundNext); + _backgroundFade.start([=] { + update(); + if (!_backgroundFade.animating()) { + _backgroundBack = QImage(); + startBackgroundFade(); + } + }, 0., 1., kFadeBackgroundDuration); } void LayerWidget::parentResized() { resizeToWidth(parentWidget()->width()); + if (_background.isNull()) { + start(); + } } void LayerWidget::keyPressEvent(QKeyEvent *e) { diff --git a/Telegram/SourceFiles/editor/editor_layer_widget.h b/Telegram/SourceFiles/editor/editor_layer_widget.h index ffcf6e1e4..ba8cb72bf 100644 --- a/Telegram/SourceFiles/editor/editor_layer_widget.h +++ b/Telegram/SourceFiles/editor/editor_layer_widget.h @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/image/image.h" #include "editor/photo_editor_common.h" #include "base/unique_qptr.h" +#include "base/timer.h" enum class ImageRoundRadius; @@ -30,12 +31,28 @@ public: void parentResized() override; bool closeByOutsideClick() const override; -protected: +private: + bool eventHook(QEvent *e) override; void keyPressEvent(QKeyEvent *e) override; int resizeGetHeight(int newWidth) override; -private: + void start(); + void cacheBackground(); + void checkBackgroundStale(); + void checkCacheBackground(); + [[nodiscard]] QImage renderBackground(); + void backgroundReady(QImage background, bool night); + void startBackgroundFade(); + const base::unique_qptr _content; + QImage _backgroundBack; + QImage _background; + QImage _backgroundNext; + Ui::Animations::Simple _backgroundFade; + base::Timer _backgroundTimer; + crl::time _lastAreaChangeTime = 0; + bool _backgroundCaching = false; + bool _backgroundNight = false; };