chromium/ash/display/root_window_transformers.cc

// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/display/root_window_transformers.h"

#include <cmath>

#include "ash/accessibility/magnifier/fullscreen_magnifier_controller.h"
#include "ash/display/display_util.h"
#include "ash/host/root_window_transformer.h"
#include "ash/shell.h"
#include "ash/utility/transformer_util.h"
#include "base/command_line.h"
#include "base/memory/ptr_util.h"
#include "base/system/sys_info.h"
#include "ui/display/display.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/geometry/transform.h"

namespace ash {
namespace {

// TODO(oshima): Transformers should be able to adjust itself
// when the device scale factor is changed, instead of
// precalculating the transform using fixed value.

// Creates rotation transform for |root_window| to |new_rotation|. This will
// call |CreateRotationTransform()|, the |old_rotation| will implicitly be
// |display::Display::ROTATE_0|.
gfx::Transform CreateRootWindowRotationTransform(
    const display::Display& display) {
  gfx::SizeF size(display.GetSizeInPixel());

  // Use SizeF so that the origin of translated layer will be
  // aligned when scaled back at pixels.
  size.Scale(1.f / display.device_scale_factor());
  return CreateRotationTransform(display::Display::ROTATE_0,
                                 display.panel_rotation(), size);
}

gfx::Transform CreateInsetsTransform(const gfx::Insets& insets,
                                     float device_scale_factor) {
  gfx::Transform transform;
  if (insets.top() != 0 || insets.left() != 0) {
    float x_offset = insets.left() / device_scale_factor;
    float y_offset = insets.top() / device_scale_factor;
    transform.Translate(x_offset, y_offset);
  }
  return transform;
}

// Returns a transform with rotation adjusted |insets_in_pixel|. The transform
// is applied to the root window so that |insets_in_pixel| looks correct after
// the rotation applied at the output.
gfx::Transform CreateReverseRotatedInsetsTransform(
    display::Display::Rotation rotation,
    const gfx::Insets& insets_in_pixel,
    float device_scale_factor) {
  float x_offset = 0;
  float y_offset = 0;

  switch (rotation) {
    case display::Display::ROTATE_0:
      x_offset = insets_in_pixel.left();
      y_offset = insets_in_pixel.top();
      break;
    case display::Display::ROTATE_90:
      x_offset = insets_in_pixel.top();
      y_offset = insets_in_pixel.right();
      break;
    case display::Display::ROTATE_180:
      x_offset = insets_in_pixel.right();
      y_offset = insets_in_pixel.bottom();
      break;
    case display::Display::ROTATE_270:
      x_offset = insets_in_pixel.bottom();
      y_offset = insets_in_pixel.left();
      break;
  }

  gfx::Transform transform;
  if (x_offset != 0 || y_offset != 0) {
    x_offset /= device_scale_factor;
    y_offset /= device_scale_factor;
    transform.Translate(x_offset, y_offset);
  }
  return transform;
}

// RootWindowTransformer for ash environment.
class AshRootWindowTransformer : public RootWindowTransformer {
 public:
  AshRootWindowTransformer(const display::Display& display) {
    initial_root_bounds_ = gfx::Rect(display.size());
    display::DisplayManager* display_manager = Shell::Get()->display_manager();
    display::ManagedDisplayInfo info =
        display_manager->GetDisplayInfo(display.id());
    host_insets_ = info.GetOverscanInsetsInPixel();
    gfx::Transform insets_and_rotation_transform =
        CreateInsetsTransform(host_insets_, display.device_scale_factor()) *
        CreateRootWindowRotationTransform(display);
    transform_ = insets_and_rotation_transform;
    insets_and_scale_transform_ = CreateReverseRotatedInsetsTransform(
        display.panel_rotation(), host_insets_, display.device_scale_factor());
    FullscreenMagnifierController* magnifier =
        Shell::Get()->fullscreen_magnifier_controller();
    if (magnifier) {
      gfx::Transform magnifier_scale = magnifier->GetMagnifierTransform();
      transform_ *= magnifier_scale;
      insets_and_scale_transform_ *= magnifier_scale;
    }

    CHECK(transform_.GetInverse(&invert_transform_));
    CHECK(insets_and_rotation_transform.GetInverse(
        &root_window_bounds_transform_));

    root_window_bounds_transform_.Scale(1.f / display.device_scale_factor(),
                                        1.f / display.device_scale_factor());

    initial_host_size_ = info.bounds_in_native().size();
  }

  AshRootWindowTransformer(const AshRootWindowTransformer&) = delete;
  AshRootWindowTransformer& operator=(const AshRootWindowTransformer&) = delete;

  // aura::RootWindowTransformer overrides:
  gfx::Transform GetTransform() const override { return transform_; }
  gfx::Transform GetInverseTransform() const override {
    return invert_transform_;
  }
  gfx::Rect GetRootWindowBounds(const gfx::Size& host_size) const override {
    if (base::SysInfo::IsRunningOnChromeOS())
      return initial_root_bounds_;

    // If we're running on linux desktop for dev purpose, the host window
    // may be updated to new size. Recompute the root window bounds based
    // on the host size if the host size changed.
    if (initial_host_size_ == host_size)
      return initial_root_bounds_;

    gfx::RectF new_bounds = gfx::RectF(gfx::SizeF(host_size));
    new_bounds.Inset(gfx::InsetsF(host_insets_));
    new_bounds = root_window_bounds_transform_.MapRect(new_bounds);

    // Root window origin will be (0,0) except during bounds changes.
    // Set to exactly zero to avoid rounding issues.
    // Floor the size because the bounds is no longer aligned to
    // backing pixel when |root_window_scale_| is specified
    // (850 height at 1.25 scale becomes 1062.5 for example.)
    return gfx::Rect(gfx::ToFlooredSize(new_bounds.size()));
  }

  gfx::Insets GetHostInsets() const override { return host_insets_; }
  gfx::Transform GetInsetsAndScaleTransform() const override {
    return insets_and_scale_transform_;
  }

 private:
  ~AshRootWindowTransformer() override = default;

  gfx::Transform transform_;

  // The accurate representation of the inverse of the |transform_|.
  // This is used to avoid computation error caused by
  // |gfx::Transform::GetInverse|.
  gfx::Transform invert_transform_;

  // The transform of the root window bounds. This is used to calculate the size
  // of the root window. It is the composition of the following transforms
  //   - inverse of insets. Insets position the content area within the display.
  //   - inverse of rotation. Rotation changes orientation of the content area.
  //   - inverse of device scale. Scaling up content shrinks the content area.
  //
  // Insets also shrink the content area but this happens prior to applying the
  // transformation in GetRootWindowBounds().
  //
  // Magnification does not affect the window size. Content is clipped in this
  // case, but the magnifier allows panning to reach clipped areas.
  //
  // The transforms are inverted because GetTransform() is the transform from
  // root window coordinates to host coordinates, but this transform is used in
  // the reverse direction (derives root window bounds from display bounds).
  gfx::Transform root_window_bounds_transform_;

  gfx::Insets host_insets_;
  gfx::Transform insets_and_scale_transform_;
  gfx::Rect initial_root_bounds_;
  gfx::Size initial_host_size_;
};

// RootWindowTransformer for mirror root window. We simply copy the
// texture (bitmap) of the source display into the mirror window, so
// the root window bounds is the same as the source display's
// pixel size (excluding overscan insets).
class MirrorRootWindowTransformer : public RootWindowTransformer {
 public:
  MirrorRootWindowTransformer(
      const display::ManagedDisplayInfo& source_display_info,
      const display::ManagedDisplayInfo& mirror_display_info) {
    root_bounds_ =
        gfx::Rect(source_display_info.GetSizeInPixelWithPanelOrientation());
    display::Display::Rotation active_root_rotation =
        source_display_info.GetActiveRotation();

    const bool should_undo_rotation = ShouldUndoRotationForMirror();
    gfx::Transform rotation_transform;
    if (should_undo_rotation) {
      // Calculate the transform to undo the rotation and apply it to the
      // source display. Use GetActiveRotation() because |source_bounds_|
      // includes panel rotation and we only need to revert active rotation.
      rotation_transform = CreateRotationTransform(
          source_display_info.GetActiveRotation(), display::Display::ROTATE_0,
          gfx::SizeF(root_bounds_.size()));
      root_bounds_ = gfx::ToNearestRect(
          rotation_transform.MapRect(gfx::RectF(root_bounds_)));
      active_root_rotation = display::Display::ROTATE_0;
    }

    gfx::Rect mirror_display_rect =
        gfx::Rect(mirror_display_info.bounds_in_native().size());

    // When logical rotation is 90 or 270 degree, transpose is needed to apply
    // reverse rotation to `root_bounds_` and `mirror_display_rect` to exclude
    // the rotation. This is because the rotation happens at viz output and
    // `transform_` needs to be calculated without the rotation.
    // E.g. host native size 1600x1200. Rotation 90 degree. `transform_` needs
    // to fit 1200x1600 rather than 1600x1200.
    const bool need_transpose =
        active_root_rotation == display::Display::ROTATE_90 ||
        active_root_rotation == display::Display::ROTATE_270;
    if (need_transpose) {
      root_bounds_.Transpose();
      mirror_display_rect.Transpose();
    }

    bool letterbox = root_bounds_.width() * mirror_display_rect.height() >
                     root_bounds_.height() * mirror_display_rect.width();
    if (letterbox) {
      float mirror_scale_ratio =
          (static_cast<float>(root_bounds_.width()) /
           static_cast<float>(mirror_display_rect.width()));
      float inverted_scale = 1.0f / mirror_scale_ratio;
      int margin = static_cast<int>((mirror_display_rect.height() -
                                     root_bounds_.height() * inverted_scale) /
                                    2);
      insets_ = gfx::Insets::TLBR(0, margin, 0, margin);

      transform_.Translate(0, margin);
      transform_.Scale(inverted_scale, inverted_scale);
    } else {
      float mirror_scale_ratio =
          (static_cast<float>(root_bounds_.height()) /
           static_cast<float>(mirror_display_rect.height()));
      float inverted_scale = 1.0f / mirror_scale_ratio;
      int margin = static_cast<int>((mirror_display_rect.width() -
                                     root_bounds_.width() * inverted_scale) /
                                    2);
      insets_ = gfx::Insets::TLBR(margin, 0, margin, 0);

      transform_.Translate(margin, 0);
      transform_.Scale(inverted_scale, inverted_scale);
    }
  }

  MirrorRootWindowTransformer(const MirrorRootWindowTransformer&) = delete;
  MirrorRootWindowTransformer& operator=(const MirrorRootWindowTransformer&) =
      delete;

  // aura::RootWindowTransformer overrides:
  gfx::Transform GetTransform() const override { return transform_; }
  gfx::Transform GetInverseTransform() const override {
    gfx::Transform invert;
    CHECK(transform_.GetInverse(&invert));
    return invert;
  }
  gfx::Rect GetRootWindowBounds(const gfx::Size& host_size) const override {
    return root_bounds_;
  }
  gfx::Insets GetHostInsets() const override { return insets_; }
  gfx::Transform GetInsetsAndScaleTransform() const override {
    return transform_;
  }

 private:
  ~MirrorRootWindowTransformer() override = default;

  gfx::Transform transform_;
  gfx::Rect root_bounds_;
  gfx::Insets insets_;
};

class PartialBoundsRootWindowTransformer : public RootWindowTransformer {
 public:
  PartialBoundsRootWindowTransformer(const gfx::Rect& screen_bounds,
                                     const display::Display& display) {
    const display::DisplayManager* display_manager =
        Shell::Get()->display_manager();
    display::ManagedDisplayInfo display_info =
        display_manager->GetDisplayInfo(display.id());
    // Physical root bounds.
    root_bounds_ = gfx::Rect(display_info.bounds_in_native().size());

    display::Display::Rotation active_root_rotation =
        display_info.GetActiveRotation();
    const bool need_transpose =
        active_root_rotation == display::Display::ROTATE_90 ||
        active_root_rotation == display::Display::ROTATE_270;
    if (need_transpose)
      root_bounds_.Transpose();

    // |screen_bounds| is the unified desktop logical bounds.
    // Calculate the unified height scale value, and apply the same scale on the
    // row physical height to get the row logical height.
    display::Display unified_display =
        display::Screen::GetScreen()->GetPrimaryDisplay();
    const int unified_physical_height =
        unified_display.GetSizeInPixel().height();
    const int unified_logical_height = screen_bounds.height();
    const float unified_height_scale =
        static_cast<float>(unified_logical_height) / unified_physical_height;

    const int row_index =
        display_manager->GetMirroringDisplayRowIndexInUnifiedMatrix(
            display.id());
    const int row_physical_height =
        display_manager->GetUnifiedDesktopRowMaxHeight(row_index);
    const int row_logical_height = row_physical_height * unified_height_scale;
    const float dsf = unified_display.device_scale_factor();
    const float scale = root_bounds_.height() / (dsf * row_logical_height);

    transform_.Scale(scale, scale);
    transform_.Translate(-SkIntToScalar(display.bounds().x()),
                         -SkIntToScalar(display.bounds().y()));
    // Scaling using physical root bounds here, because rotation is applied
    // before device_scale_factor is applied.
    gfx::Transform rotation = CreateRotationTransform(
        display::Display::ROTATE_0, display.panel_rotation(),
        gfx::SizeF(root_bounds_.size()));
    CHECK((rotation * transform_).GetInverse(&invert_transform_));
  }

  PartialBoundsRootWindowTransformer(
      const PartialBoundsRootWindowTransformer&) = delete;
  PartialBoundsRootWindowTransformer& operator=(
      const PartialBoundsRootWindowTransformer&) = delete;

  // RootWindowTransformer:
  gfx::Transform GetTransform() const override { return transform_; }
  gfx::Transform GetInverseTransform() const override {
    return invert_transform_;
  }
  gfx::Rect GetRootWindowBounds(const gfx::Size& host_size) const override {
    return root_bounds_;
  }
  gfx::Insets GetHostInsets() const override { return gfx::Insets(); }
  gfx::Transform GetInsetsAndScaleTransform() const override {
    return transform_;
  }

 private:
  ~PartialBoundsRootWindowTransformer() override = default;

  gfx::Transform transform_;
  gfx::Transform invert_transform_;
  gfx::Rect root_bounds_;
};

}  // namespace

std::unique_ptr<RootWindowTransformer> CreateRootWindowTransformerForDisplay(
    const display::Display& display) {
  return base::WrapUnique<RootWindowTransformer>(
      new AshRootWindowTransformer(display));
}

std::unique_ptr<RootWindowTransformer>
CreateRootWindowTransformerForMirroredDisplay(
    const display::ManagedDisplayInfo& source_display_info,
    const display::ManagedDisplayInfo& mirror_display_info) {
  return base::WrapUnique<RootWindowTransformer>(
      new MirrorRootWindowTransformer(source_display_info,
                                      mirror_display_info));
}

std::unique_ptr<RootWindowTransformer>
CreateRootWindowTransformerForUnifiedDesktop(const gfx::Rect& screen_bounds,
                                             const display::Display& display) {
  return base::WrapUnique<RootWindowTransformer>(
      new PartialBoundsRootWindowTransformer(screen_bounds, display));
}

}  // namespace ash