// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/exo/wayland/clients/blur.h" #include #include #include "base/command_line.h" #include "base/stl_util.h" #include "components/exo/wayland/clients/client_helper.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkImage.h" #include "third_party/skia/include/core/SkSurface.h" #include "third_party/skia/include/effects/SkBlurImageFilter.h" #include "third_party/skia/include/gpu/GrDirectContext.h" #include "ui/gl/gl_bindings.h" namespace exo { namespace wayland { namespace clients { namespace { // Rotation speed (degrees/second). const double kRotationSpeed = 360.0; // The opacity of the foreground. const double kForegroundOpacity = 0.7; // Rects grid size. const int kGridSize = 4; // Create grid image for |size| and |cell_size|. sk_sp CreateGridImage(const gfx::Size& size, const gfx::Size& cell_size) { sk_sp surface(SkSurface::MakeRaster( SkImageInfo::MakeN32(size.width(), size.height(), kOpaque_SkAlphaType))); SkCanvas* canvas = surface->getCanvas(); canvas->clear(SK_ColorWHITE); for (int y = 0; y < kGridSize; ++y) { for (int x = 0; x < kGridSize; ++x) { if ((y + x) % 2) continue; SkPaint paint; paint.setColor(SK_ColorLTGRAY); canvas->save(); canvas->translate(x * cell_size.width(), y * cell_size.height()); canvas->drawIRect(SkIRect::MakeWH(cell_size.width(), cell_size.height()), paint); canvas->restore(); } } return surface->makeImageSnapshot(); } // Adjust sigma by increasing the scale factor until less than |max_sigma|. // Returns the adjusted sigma value. double AdjustSigma(double sigma, double max_sigma, int* scale_factor) { *scale_factor = 1; while (sigma > max_sigma) { *scale_factor *= 2; sigma /= 2.0; } return sigma; } // Draw background contents to canvas. void DrawContents(SkImage* background_grid_image, const gfx::Size& cell_size, base::TimeDelta elapsed_time, SkCanvas* canvas) { // Draw background grid. SkPaint paint; paint.setBlendMode(SkBlendMode::kSrc); canvas->drawImage(background_grid_image, 0, 0, &paint); // Draw rotated rectangles. SkScalar rect_size = SkScalarHalf(std::min(cell_size.width(), cell_size.height())); SkIRect rect = SkIRect::MakeXYWH( -SkScalarHalf(rect_size), -SkScalarHalf(rect_size), rect_size, rect_size); SkScalar rotation = elapsed_time.InMilliseconds() * kRotationSpeed / 1000; for (int y = 0; y < kGridSize; ++y) { for (int x = 0; x < kGridSize; ++x) { const SkColor kColors[] = {SK_ColorBLUE, SK_ColorGREEN, SK_ColorRED, SK_ColorYELLOW, SK_ColorCYAN, SK_ColorMAGENTA}; SkPaint paint; paint.setColor(kColors[(y * kGridSize + x) % base::size(kColors)]); canvas->save(); canvas->translate( x * cell_size.width() + SkScalarHalf(cell_size.width()), y * cell_size.height() + SkScalarHalf(cell_size.height())); canvas->rotate(rotation / (y * kGridSize + x + 1)); canvas->drawIRect(rect, paint); canvas->restore(); } } } void FrameCallback(void* data, wl_callback* callback, uint32_t time) { bool* callback_pending = static_cast(data); *callback_pending = false; } } // namespace Blur::Blur() = default; Blur::~Blur() = default; void Blur::Run(double sigma_x, double sigma_y, double max_sigma, bool offscreen, int frames) { Buffer* buffer = buffers_.front().get(); gfx::Size cell_size(size_.width() / kGridSize, size_.height() / kGridSize); // Create grid image. Simulates a wallpaper. if (!grid_image_) grid_image_ = CreateGridImage(size_, cell_size); // Create blur surfaces if needed. sk_sp blur_filter; std::vector> blur_surfaces; std::vector> content_surfaces; int blur_scale_factor_x = 1; int blur_scale_factor_y = 1; if (sigma_x > 0.0 || sigma_y > 0.0) { sigma_x = AdjustSigma(sigma_x, max_sigma, &blur_scale_factor_x); sigma_y = AdjustSigma(sigma_y, max_sigma, &blur_scale_factor_y); blur_filter = SkBlurImageFilter::Make(sigma_x, sigma_y, nullptr, nullptr, SkBlurImageFilter::kClamp_TileMode); auto size = SkISize::Make(size_.width() / blur_scale_factor_x, size_.height() / blur_scale_factor_y); do { blur_surfaces.push_back( buffer->sk_surface->makeSurface(SkImageInfo::MakeN32( size.width(), size.height(), kOpaque_SkAlphaType))); size = SkISize::Make(std::min(size_.width(), size.width() * 2), std::min(size_.height(), size.height() * 2)); } while (size.width() < size_.width() || size.height() < size_.height()); } bool callback_pending = false; std::unique_ptr frame_callback; wl_callback_listener frame_listener = {FrameCallback}; int frame_count = 0; base::TimeTicks initial_time = base::TimeTicks::Now(); while (frame_count < frames) { base::TimeDelta elapsed_time = base::TimeTicks::Now() - initial_time; if (blur_filter) { // Create contents surfaces. while (!blur_surfaces.empty()) { sk_sp surface = blur_surfaces.back(); blur_surfaces.pop_back(); SkSize size = SkSize::Make(surface->width(), surface->height()); SkCanvas* canvas = surface->getCanvas(); canvas->save(); // Draw contents if this is the first surface. if (content_surfaces.empty()) { canvas->scale(size.width() / size_.width(), size.height() / size_.height()); DrawContents(grid_image_.get(), cell_size, elapsed_time, canvas); } else { // Otherwise, scale larger surface to produce surface. canvas->scale(size.width() / content_surfaces.back()->width(), size.height() / content_surfaces.back()->height()); SkPaint paint; paint.setBlendMode(SkBlendMode::kSrc); paint.setFilterQuality(SkFilterQuality::kLow_SkFilterQuality); content_surfaces.back()->draw(canvas, 0, 0, &paint); } canvas->restore(); content_surfaces.push_back(surface); } // Make blur image from last content surface. SkIRect subset; SkIPoint offset; sk_sp blur_image = content_surfaces.back()->makeImageSnapshot(); sk_sp blurred_image = blur_image->makeWithFilter( gr_context_.get(), blur_filter.get(), blur_image->bounds(), blur_image->bounds(), &subset, &offset); SkCanvas* canvas = buffer->sk_surface->getCanvas(); canvas->save(); SkSize size = SkSize::Make(size_.width(), size_.height()); canvas->scale(size.width() / blur_image->width(), size.height() / blur_image->height()); SkPaint paint; paint.setBlendMode(SkBlendMode::kSrc); paint.setFilterQuality(SkFilterQuality::kLow_SkFilterQuality); // Simulate multi-texturing by adding foreground opacity. int alpha = (1.0 - kForegroundOpacity) * 255.0 + 0.5; paint.setColor(SkColorSetA(SK_ColorBLACK, alpha)); canvas->drawImage(blurred_image, offset.x() - subset.x(), offset.y() - subset.y(), &paint); canvas->restore(); // Restore blur surfaces for next frame. std::swap(content_surfaces, blur_surfaces); std::reverse(blur_surfaces.begin(), blur_surfaces.end()); } else { // !blur_filter SkCanvas* canvas = buffer->sk_surface->getCanvas(); DrawContents(grid_image_.get(), cell_size, elapsed_time, canvas); SkPaint paint; int alpha = kForegroundOpacity * 255.0 + 0.5; paint.setColor(SkColorSetA(SK_ColorBLACK, alpha)); canvas->drawIRect(SkIRect::MakeWH(size_.width(), size_.height()), paint); } if (gr_context_) { gr_context_->flushAndSubmit(); glFinish(); } // Submit 1 of 50 frames for onscreen display when in offscreen mode. if ((frame_count++ % 50) && offscreen) continue; while (callback_pending) wl_display_dispatch(display_.get()); callback_pending = true; wl_surface_set_buffer_scale(surface_.get(), scale_); wl_surface_set_buffer_transform(surface_.get(), transform_); wl_surface_damage(surface_.get(), 0, 0, surface_size_.width(), surface_size_.height()); wl_surface_attach(surface_.get(), buffer->buffer.get(), 0, 0); frame_callback.reset(wl_surface_frame(surface_.get())); wl_callback_add_listener(frame_callback.get(), &frame_listener, &callback_pending); wl_surface_commit(surface_.get()); wl_display_flush(display_.get()); } } } // namespace clients } // namespace wayland } // namespace exo