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 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
|
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/gfx/shadow_util.h"
#include <map>
#include <vector>
#include "base/lazy_instance.h"
#include "base/memory/ptr_util.h"
#include "third_party/skia/include/core/SkRRect.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gfx/image/canvas_image_source.h"
#include "ui/gfx/shadow_value.h"
#include "ui/gfx/skia_paint_util.h"
namespace gfx {
namespace {
// Creates an image with the given shadows painted around a round rect with
// the given corner radius. The image will be just large enough to paint the
// shadows appropriately with a 1px square region reserved for "content".
class ShadowNineboxSource : public CanvasImageSource {
public:
ShadowNineboxSource(const std::vector<ShadowValue>& shadows,
float corner_radius)
: CanvasImageSource(CalculateSize(shadows, corner_radius)),
shadows_(shadows),
corner_radius_(corner_radius) {
DCHECK(!shadows.empty());
}
ShadowNineboxSource(const ShadowNineboxSource&) = delete;
ShadowNineboxSource& operator=(const ShadowNineboxSource&) = delete;
~ShadowNineboxSource() override {}
// CanvasImageSource overrides:
void Draw(Canvas* canvas) override {
cc::PaintFlags flags;
flags.setLooper(CreateShadowDrawLooper(shadows_));
Insets insets = -ShadowValue::GetMargin(shadows_);
gfx::Rect bounds(size());
bounds.Inset(insets);
SkRRect r_rect = SkRRect::MakeRectXY(gfx::RectToSkRect(bounds),
corner_radius_, corner_radius_);
// Clip out the center so it's not painted with the shadow.
canvas->sk_canvas()->clipRRect(r_rect, SkClipOp::kDifference, true);
// Clipping alone is not enough --- due to anti aliasing there will still be
// some of the fill color in the rounded corners. We must make the fill
// color transparent.
flags.setColor(SK_ColorTRANSPARENT);
canvas->sk_canvas()->drawRRect(r_rect, flags);
}
private:
static Size CalculateSize(const std::vector<ShadowValue>& shadows,
float corner_radius) {
// The "content" area (the middle tile in the 3x3 grid) is a single pixel.
gfx::Rect bounds(0, 0, 1, 1);
// We need enough space to render the full range of blur.
bounds.Inset(-ShadowValue::GetBlurRegion(shadows));
// We also need space for the full roundrect corner rounding.
bounds.Inset(-gfx::Insets(corner_radius));
return bounds.size();
}
const std::vector<ShadowValue> shadows_;
const float corner_radius_;
};
// A shadow's appearance is determined by its rounded corner radius and shadow
// values. Make these attributes as the key for shadow details.
struct ShadowDetailsKey {
bool operator==(const ShadowDetailsKey& other) const {
return (corner_radius == other.corner_radius) && (values == other.values);
}
bool operator<(const ShadowDetailsKey& other) const {
return (corner_radius < other.corner_radius) ||
((corner_radius == other.corner_radius) && (values < other.values));
}
int corner_radius;
ShadowValues values;
};
// Map from shadow details key to a cached shadow.
using ShadowDetailsMap = std::map<ShadowDetailsKey, ShadowDetails>;
base::LazyInstance<ShadowDetailsMap>::DestructorAtExit g_shadow_cache =
LAZY_INSTANCE_INITIALIZER;
} // namespace
ShadowDetails::ShadowDetails(const gfx::ShadowValues& values,
const gfx::ImageSkia& nine_patch_image)
: values(values), nine_patch_image(nine_patch_image) {}
ShadowDetails::ShadowDetails(const ShadowDetails& other) = default;
ShadowDetails::~ShadowDetails() {}
const ShadowDetails& ShadowDetails::Get(int elevation,
int corner_radius,
ShadowStyle style) {
switch (style) {
case ShadowStyle::kMaterialDesign:
return Get(corner_radius, ShadowValue::MakeMdShadowValues(elevation));
#if BUILDFLAG(IS_CHROMEOS)
case ShadowStyle::kChromeOSSystemUI:
return Get(corner_radius,
ShadowValue::MakeChromeOSSystemUIShadowValues(elevation));
#endif
}
}
const ShadowDetails& ShadowDetails::Get(int elevation,
int radius,
SkColor key_color,
SkColor ambient_color,
ShadowStyle style) {
switch (style) {
case ShadowStyle::kMaterialDesign:
return Get(radius, ShadowValue::MakeMdShadowValues(elevation, key_color,
ambient_color));
#if BUILDFLAG(IS_CHROMEOS)
case ShadowStyle::kChromeOSSystemUI:
return Get(radius, ShadowValue::MakeChromeOSSystemUIShadowValues(
elevation, key_color, ambient_color));
#endif
}
}
const ShadowDetails& ShadowDetails::Get(int radius,
const gfx::ShadowValues& values) {
ShadowDetailsKey key{radius, values};
auto iter = g_shadow_cache.Get().find(key);
if (iter != g_shadow_cache.Get().end()) {
return iter->second;
}
// Evict the details whose ninebox image does not have any shadow owners.
std::erase_if(g_shadow_cache.Get(), [](auto& pair) {
return pair.second.nine_patch_image.IsUniquelyOwned();
});
auto source =
std::make_unique<ShadowNineboxSource>(values, key.corner_radius);
const gfx::Size image_size = source->size();
auto nine_patch_image = ImageSkia(std::move(source), image_size);
auto insertion = g_shadow_cache.Get().emplace(
key, ShadowDetails(values, nine_patch_image));
DCHECK(insertion.second);
const std::pair<const ShadowDetailsKey, ShadowDetails>& inserted_item =
*(insertion.first);
return inserted_item.second;
}
size_t ShadowDetails::GetDetailsCacheSizeForTest() {
return g_shadow_cache.Get().size();
}
} // namespace gfx
|