diff --git a/Telegram/SourceFiles/boxes/background_box.cpp b/Telegram/SourceFiles/boxes/background_box.cpp index 9f81c209d..674580fee 100644 --- a/Telegram/SourceFiles/boxes/background_box.cpp +++ b/Telegram/SourceFiles/boxes/background_box.cpp @@ -514,7 +514,10 @@ void BackgroundPreviewBox::paintImage(Painter &p) { (size - height()) / 2 * factor, size * factor, height() * factor); - p.drawPixmap(rect(), _scaled, from); + p.drawPixmap( + rect(), + (!_blurred.isNull() && _paper.isBlurred()) ? _blurred : _scaled, + from); } void BackgroundPreviewBox::paintRadial(Painter &p, TimeMs ms) { @@ -621,9 +624,12 @@ bool BackgroundPreviewBox::setScaledFromThumb() { return true; } -void BackgroundPreviewBox::setScaledFromImage(QImage &&image) { +void BackgroundPreviewBox::setScaledFromImage( + QImage &&image, + QImage &&blurred) { updateServiceBg(Window::Theme::CountAverageColor(image)); _scaled = App::pixmapFromImageInPlace(std::move(image)); + _blurred = App::pixmapFromImageInPlace(std::move(blurred)); } void BackgroundPreviewBox::updateServiceBg(std::optional background) { @@ -655,16 +661,23 @@ void BackgroundPreviewBox::checkLoadedDocument() { guard = std::move(right) ]() mutable { auto scaled = PrepareScaledFromFull(image, patternBackground); + const auto ms = getms(); + auto blurred = patternBackground + ? QImage() + : PrepareScaledNonPattern( + Data::PrepareBlurredBackground(image), + Images::Option(0)); crl::on_main([ this, image = std::move(image), scaled = std::move(scaled), + blurred = std::move(blurred), guard = std::move(guard) ]() mutable { if (!guard) { return; } - setScaledFromImage(std::move(scaled)); + setScaledFromImage(std::move(scaled), std::move(blurred)); _full = std::move(image); update(); }); diff --git a/Telegram/SourceFiles/boxes/background_box.h b/Telegram/SourceFiles/boxes/background_box.h index 50052d1ec..8692ba9eb 100644 --- a/Telegram/SourceFiles/boxes/background_box.h +++ b/Telegram/SourceFiles/boxes/background_box.h @@ -67,7 +67,7 @@ private: void checkLoadedDocument(); bool setScaledFromThumb(); - void setScaledFromImage(QImage &&image); + void setScaledFromImage(QImage &&image, QImage &&blurred = QImage()); void updateServiceBg(std::optional background); std::optional patternBackgroundColor() const; void paintImage(Painter &p); @@ -79,7 +79,7 @@ private: AdminLog::OwnedItem _text2; Data::WallPaper _paper; QImage _full; - QPixmap _scaled; + QPixmap _scaled, _blurred; Ui::RadialAnimation _radial; base::binary_guard _generating; std::optional _serviceBg; diff --git a/Telegram/SourceFiles/ui/image/image_prepare.cpp b/Telegram/SourceFiles/ui/image/image_prepare.cpp index fad61a291..07455a90b 100644 --- a/Telegram/SourceFiles/ui/image/image_prepare.cpp +++ b/Telegram/SourceFiles/ui/image/image_prepare.cpp @@ -52,12 +52,11 @@ QImage prepareBlur(QImage img) { if (img.isNull()) { return img; } - auto ratio = img.devicePixelRatio(); - auto fmt = img.format(); + const auto ratio = img.devicePixelRatio(); + const auto fmt = img.format(); if (fmt != QImage::Format_RGB32 && fmt != QImage::Format_ARGB32_Premultiplied) { - img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied); + img = std::move(img).convertToFormat(QImage::Format_ARGB32_Premultiplied); img.setDevicePixelRatio(ratio); - Assert(!img.isNull()); } uchar *pix = img.bits(); @@ -170,6 +169,238 @@ yi += stride; return img; } +QImage BlurLargeImage(QImage image, int radius) { + const auto width = image.width(); + const auto height = image.height(); + if (width <= radius || height <= radius || radius < 1) { + return image; + } + + if (image.format() != QImage::Format_RGB32 + && image.format() != QImage::Format_ARGB32_Premultiplied) { + image = std::move(image).convertToFormat( + QImage::Format_ARGB32_Premultiplied); + } + const auto pixels = image.bits(); + + const auto widthm = width - 1; + const auto heightm = height - 1; + const auto area = width * height; + const auto div = 2 * radius + 1; + const auto radius1 = radius + 1; + const auto divsum = radius1 * radius1; + + const auto dvcount = 256 * divsum; + const auto buffers = (div * 3) // stack + + std::max(width, height) // vmin + + area * 3 // rgb + + dvcount; // dv + auto storage = std::vector(buffers); + auto taken = 0; + const auto take = [&](int size) { + const auto result = gsl::make_span(storage).subspan(taken, size); + taken += size; + return result; + }; + + // Small buffers + const auto stack = take(div * 3).data(); + const auto vmin = take(std::max(width, height)).data(); + + // Large buffers + const auto rgb = take(area * 3).data(); + const auto dvs = take(dvcount); + + auto &&ints = ranges::view::ints(0); + for (auto &&[value, index] : ranges::view::zip(dvs, ints)) { + value = (index / divsum); + } + const auto dv = dvs.data(); + + // Variables + auto yp = 0; + auto stackpointer = 0; + auto stackstart = 0; + auto rbs = 0; + auto yw = 0; + auto yi = 0; + auto yi3 = 0; + for (const auto y : ranges::view::ints(0, height)) { + const auto yw = y * width; + auto rinsum = 0; + auto ginsum = 0; + auto binsum = 0; + auto routsum = 0; + auto goutsum = 0; + auto boutsum = 0; + auto rsum = 0; + auto gsum = 0; + auto bsum = 0; + + for (const auto i : ranges::view::ints(-radius, radius + 1)) { + const auto sir = &stack[(i + radius) * 3]; + const auto offset = (yi + std::min(area, std::max(i, 0))) * 4; + sir[0] = pixels[offset]; + sir[1] = pixels[offset + 1]; + sir[2] = pixels[offset + 2]; + + rbs = radius1 - abs(i); + rsum += sir[0] * rbs; + gsum += sir[1] * rbs; + bsum += sir[2] * rbs; + if (i > 0) { + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + } else { + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + } + } + stackpointer = radius; + + for (const auto x : ranges::view::ints(0, width)) { + rgb[yi3] = dv[rsum]; + rgb[yi3 + 1] = dv[gsum]; + rgb[yi3 + 2] = dv[bsum]; + + rsum -= routsum; + gsum -= goutsum; + bsum -= boutsum; + + stackstart = stackpointer - radius + div; + const auto sir = &stack[(stackstart % div) * 3]; + + routsum -= sir[0]; + goutsum -= sir[1]; + boutsum -= sir[2]; + + if (y == 0) { + vmin[x] = std::min(x + radius + 1, area); + } + + const auto offset = (yw + vmin[x]) * 4; + sir[0] = pixels[offset]; + sir[1] = pixels[offset + 1]; + sir[2] = pixels[offset + 2]; + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + + rsum += rinsum; + gsum += ginsum; + bsum += binsum; + { + stackpointer = (stackpointer + 1) % div; + const auto sir = &stack[(stackpointer % div) * 3]; + + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + + rinsum -= sir[0]; + ginsum -= sir[1]; + binsum -= sir[2]; + } + yi++; + yi3 = yi * 3; + } + } + + for (const auto x : ranges::view::ints(0, width)) { + auto rinsum = 0; + auto ginsum = 0; + auto binsum = 0; + auto routsum = 0; + auto goutsum = 0; + auto boutsum = 0; + auto rsum = 0; + auto gsum = 0; + auto bsum = 0; + yp = -radius * width; + for (const auto i : ranges::view::ints(-radius, radius + 1)) { + yi = std::max(0, yp) + x; + yi3 = yi * 3; + + const auto sir = &stack[(i + radius) * 3]; + + sir[0] = rgb[yi3]; + sir[1] = rgb[yi3 + 1]; + sir[2] = rgb[yi3 + 2]; + + rbs = radius1 - std::abs(i); + + rsum += rgb[yi3] * rbs; + gsum += rgb[yi3 + 1] * rbs; + bsum += rgb[yi3 + 2] * rbs; + + if (i > 0) { + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + } else { + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + } + + if (i < heightm) { + yp += width; + } + } + yi = x; + stackpointer = radius; + for (const auto y : ranges::view::ints(0, height)) { + const auto offset = yi * 4; + pixels[offset] = dv[rsum]; + pixels[offset + 1] = dv[gsum]; + pixels[offset + 2] = dv[bsum]; + rsum -= routsum; + gsum -= goutsum; + bsum -= boutsum; + + stackstart = stackpointer - radius + div; + const auto sir = &stack[(stackstart % div) * 3]; + + routsum -= sir[0]; + goutsum -= sir[1]; + boutsum -= sir[2]; + + if (x == 0) { + vmin[y] = std::min(y + radius1, heightm) * width; + } + const auto p = (x + vmin[y]) * 3; + + sir[0] = rgb[p]; + sir[1] = rgb[p + 1]; + sir[2] = rgb[p + 2]; + + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + + rsum += rinsum; + gsum += ginsum; + bsum += binsum; + { + stackpointer = (stackpointer + 1) % div; + const auto sir = &stack[stackpointer * 3]; + + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + + rinsum -= sir[0]; + ginsum -= sir[1]; + binsum -= sir[2]; + } + yi += width; + } + } + return image; +} + void prepareCircle(QImage &img) { Assert(!img.isNull()); diff --git a/Telegram/SourceFiles/ui/image/image_prepare.h b/Telegram/SourceFiles/ui/image/image_prepare.h index a0584d6c8..a6de2e7c2 100644 --- a/Telegram/SourceFiles/ui/image/image_prepare.h +++ b/Telegram/SourceFiles/ui/image/image_prepare.h @@ -26,6 +26,8 @@ namespace Images { QPixmap PixmapFast(QImage &&image); +QImage BlurLargeImage(QImage image, int radius); + QImage prepareBlur(QImage image); void prepareRound( QImage &image, diff --git a/Telegram/SourceFiles/window/themes/window_theme.cpp b/Telegram/SourceFiles/window/themes/window_theme.cpp index aaa232fd9..2d4089bec 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme.cpp @@ -150,6 +150,10 @@ bool WallPaper::isLocal() const { return !document() && thumbnail(); } +bool WallPaper::isBlurred() const { + return _settings & MTPDwallPaperSettings::Flag::f_blur; +} + int WallPaper::patternIntensity() const { return _intensity; } @@ -482,6 +486,19 @@ QImage PreparePatternImage( 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() { @@ -973,6 +990,9 @@ void ChatBackground::setPreparedImage(QImage original, QImage prepared) { Expects(prepared.width() > 0 && prepared.height() > 0); _original = std::move(original); + if (!_paper.isPattern() && _paper.isBlurred()) { + prepared = Data::PrepareBlurredBackground(std::move(prepared)); + } if (adjustPaletteRequired()) { adjustPaletteUsingBackground(prepared); } diff --git a/Telegram/SourceFiles/window/themes/window_theme.h b/Telegram/SourceFiles/window/themes/window_theme.h index d8e3e55e1..d3362f7a0 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.h +++ b/Telegram/SourceFiles/window/themes/window_theme.h @@ -28,6 +28,7 @@ public: [[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; @@ -89,6 +90,7 @@ QImage PreparePatternImage( QColor bg, QColor fg, int intensity); +QImage PrepareBlurredBackground(QImage image); namespace details {