1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128

ash / public / cpp / rounded_image_view.cc [blame]

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

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "ash/public/cpp/rounded_image_view.h"

#include "skia/ext/image_operations.h"
#include "third_party/skia/include/core/SkPath.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gfx/image/image_skia_operations.h"

namespace ash {

RoundedImageView::RoundedImageView()
    : RoundedImageView(/*corner_radius=*/0, Alignment::kLeading) {}

RoundedImageView::RoundedImageView(int corner_radius, Alignment alignment)
    : alignment_(alignment) {
  for (int i = 0; i < 4; ++i)
    corner_radius_[i] = corner_radius;
}

RoundedImageView::~RoundedImageView() = default;

void RoundedImageView::SetImage(const gfx::ImageSkia& image) {
  SetImage(image, image.size());
}

void RoundedImageView::SetImage(const gfx::ImageSkia& image,
                                const gfx::Size& size) {
  const bool is_size_same = GetImageSize() == size;
  const bool is_image_same = original_image_.BackedBySameObjectAs(image);
  if (is_size_same && is_image_same)
    return;

  if (!is_image_same)
    original_image_ = image;

  // Try to get the best image quality for the avatar.
  resized_image_ = gfx::ImageSkiaOperations::CreateResizedImage(
      original_image_, skia::ImageOperations::RESIZE_BEST, size);

  if (GetWidget() && GetVisible()) {
    PreferredSizeChanged();
    SchedulePaint();
  }
}

void RoundedImageView::SetCornerRadii(int top_left,
                                      int top_right,
                                      int bottom_right,
                                      int bottom_left) {
  corner_radius_[0] = top_left;
  corner_radius_[1] = top_right;
  corner_radius_[2] = bottom_right;
  corner_radius_[3] = bottom_left;
}

void RoundedImageView::SetCornerRadius(int corner_radius) {
  SetCornerRadii(corner_radius, corner_radius, corner_radius, corner_radius);
}

gfx::Size RoundedImageView::CalculatePreferredSize(
    const views::SizeBounds& available_size) const {
  return gfx::Size(GetImageSize().width() + GetInsets().width(),
                   GetImageSize().height() + GetInsets().height());
}

void RoundedImageView::OnPaint(gfx::Canvas* canvas) {
  View::OnPaint(canvas);
  gfx::Rect drawn_image_bounds(size());
  drawn_image_bounds.Inset(GetInsets());

  // It handles the situation that the size of the drawing space is greater
  // than that of the image to draw.
  drawn_image_bounds.ClampToCenteredSize(GetImageSize());

  const SkScalar kRadius[8] = {
      SkIntToScalar(corner_radius_[0]), SkIntToScalar(corner_radius_[0]),
      SkIntToScalar(corner_radius_[1]), SkIntToScalar(corner_radius_[1]),
      SkIntToScalar(corner_radius_[2]), SkIntToScalar(corner_radius_[2]),
      SkIntToScalar(corner_radius_[3]), SkIntToScalar(corner_radius_[3])};
  SkPath path;
  path.addRoundRect(gfx::RectToSkRect(drawn_image_bounds), kRadius);
  cc::PaintFlags flags;
  flags.setAntiAlias(true);

  gfx::ImageSkia image_to_draw;
  switch (alignment_) {
    case Alignment::kLeading:
      image_to_draw = resized_image_;
      break;
    case Alignment::kCenter:
      gfx::Rect image_size(GetImageSize());

      // It handles the situation that the size of the image to draw is greater
      // than that of the drawing space.
      image_size.ClampToCenteredSize(drawn_image_bounds.size());

      image_to_draw =
          gfx::ImageSkiaOperations::ExtractSubset(resized_image_, image_size);
      break;
  }

  // The size of the area to paint `image_to_draw` should be no greater than
  // that of `image_to_draw`. Otherwise, `image_to_draw` will be tiled.
  DCHECK_LE(drawn_image_bounds.width(), image_to_draw.width());
  DCHECK_LE(drawn_image_bounds.height(), image_to_draw.height());

  canvas->DrawImageInPath(image_to_draw, drawn_image_bounds.x(),
                          drawn_image_bounds.y(), path, flags);
}

gfx::Size RoundedImageView::GetImageSize() const {
  return resized_image_.size();
}

BEGIN_METADATA(RoundedImageView)
END_METADATA

}  // namespace ash